Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Do not hand-edit or commit generated runtime/tooling directories: `.nuxt/`, `.ou

- Follow **“Variante 1 – klar & kartenbasiert”**: light, clear and friendly, with whitespace, subtle borders/shadows and small radii. No dark theme, neon, glassmorphism or pill buttons.
- Reuse Tailwind tokens and shared classes in `app/assets/css/main.css`; avoid arbitrary inline styling, a second CSS system or large UI frameworks. Keep the local Manrope font. See [design-reference.md](docs/design-reference.md) and [typography.md](docs/typography.md).
- Body and descriptions: at least 16 px, important desktop copy 18 px with 28/32 px line height. Use the existing `.prose` (max 48rem), not `prose-sm`. Card titles are 20 px; desktop navigation/buttons at least 16 px, mobile navigation 18 px. Keep secondary copy/tags/footer at 14 px; `text-xs` is reserved for genuine microinformation (currently map attribution). Do not shrink copy to force it into a layout; allow reflow and preserve text contrast.
- Body and descriptions: at least 16 px, important desktop copy 18 px with 28/32 px line height. Use the existing `.prose` (max 48rem), not `prose-sm`. Card titles are 20 px; desktop navigation/buttons at least 16 px, mobile navigation 18 px. Keep secondary copy/tags/footer metadata at 14 px; main footer navigation uses 16 px; `text-xs` is reserved for genuine microinformation (currently map attribution). Do not shrink copy to force it into a layout; allow reflow and preserve text contrast.
- The sole organisation master is [public/brand/oklabflensburg-logo.png](public/brand/oklabflensburg-logo.png), unchanged from the [canonical upstream PNG](https://github.com/oklabflensburg/.github/blob/main/resources/oklabflensburg_logo_quadrat.png). Use `site.logo` through `AppLogo.vue`, including header/footer/mobile navigation. No redraw, vectorization, crop, stretch, filters, recolouring, duplicate source or extra wordmark.
- Keep organisation, individual project signets, supporting association and network identities distinct. The OK Lab logo is neither every project's icon nor the association/network logo.
- Keep images/fonts local and retain provenance/licences. Prefer `NuxtImg` with explicit dimensions and meaningful alt text; existing direct SVG project images remain valid. Use existing assets; the approved Plausible integration in `app/app.vue` is the sole analytics integration (see [analytics.md](docs/analytics.md)). Do not add other trackers or unnecessary third-party embeds.
Expand Down
23 changes: 16 additions & 7 deletions app/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,22 @@
.join-card p { @apply mb-5 text-base leading-7 text-text-muted md:text-lg md:leading-8; }
.blog-grid { @apply grid gap-6 md:grid-cols-2; }
.blog-card .byline { @apply mb-0 mt-4 text-sm leading-6; }
.footer { @apply border-t border-border bg-white py-6; }
.footer-inner { @apply flex flex-wrap items-center justify-between gap-x-8 gap-y-5; }
.footer-tagline { @apply m-0 max-w-72 text-sm leading-6 text-text-muted; }
.footer-links { @apply flex flex-col gap-2 md:items-end; }
.footer-social { @apply flex items-center gap-2; }
.footer nav { @apply flex flex-wrap gap-x-4 gap-y-2; }
.footer nav a { @apply inline-flex min-h-11 items-center py-2 text-sm text-text-muted no-underline hover:text-brand hover:underline; }
.footer { @apply border-t border-border bg-surface-muted; }
.footer-inner { @apply grid gap-10 py-10 md:grid-cols-2 md:py-12 xl:grid-cols-[2fr_1fr_1fr_1fr] xl:gap-12; }
.footer-inner > * { @apply min-w-0; }
.footer-identity .brand { @apply mb-5 min-h-11 w-fit; }
.footer-tagline { @apply mb-5 max-w-md text-lg leading-8 font-semibold; }
.footer-meeting { @apply mb-6 flex items-start gap-3 text-base leading-7 text-text-muted; }
.footer-meeting svg { @apply mt-1; }
.footer-group-title { @apply mb-3 text-base leading-7 font-bold; }
.footer-group ul { @apply m-0 list-none p-0; }
.footer-group a { @apply inline-flex min-h-11 items-center gap-3 py-2 text-base leading-7 text-text-muted no-underline hover:text-brand hover:underline; }
.footer-meta { @apply flex flex-col gap-3 border-t border-border py-5 text-sm leading-6 text-text-muted xl:flex-row xl:items-center xl:justify-between xl:gap-8; }
.footer-affiliations { @apply flex flex-col sm:flex-row sm:flex-wrap sm:gap-x-6; }
.footer-affiliations p { @apply m-0; }
.footer-meta nav { @apply flex flex-wrap gap-x-5; }
.footer-meta a { @apply inline-flex min-h-11 items-center py-2 text-text-muted underline hover:text-brand; }

.breadcrumbs { @apply pt-6 text-sm text-text-muted; }
.breadcrumbs ol { @apply m-0 flex list-none flex-wrap gap-2 p-0; }
.breadcrumbs li:not(:last-child)::after { @apply ml-2 text-text-muted; content: '/'; }
Expand Down
64 changes: 50 additions & 14 deletions app/components/AppFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,58 @@ const localePath = useLocalePath()
</script>
<template>
<footer class="footer">
<div class="shell footer-inner">
<NuxtLink :to="localePath('/')" class="brand">
<AppLogo size="sm" loading="lazy" />
</NuxtLink>
<p class="footer-tagline">{{ $t('footer.tagline') }}</p>
<div class="footer-links">
<div class="footer-social">
<SiteLink :href="site.github" class="icon-button" aria-label="GitHub"><AppIcon name="github" :size="18" /></SiteLink>
<SiteLink :href="site.social.mastodon" class="icon-button" aria-label="Mastodon" rel="me"><AppIcon name="social" :size="18" /></SiteLink>
<SiteLink :href="`mailto:${site.contact.email}`" class="icon-button" :aria-label="$t('common.contact')"><AppIcon name="mail" :size="18" /></SiteLink>
<SiteLink :href="`/rss.xml?lang=${$i18n.locale}`" class="icon-button" aria-label="RSS"><AppIcon name="rss" :size="18" /></SiteLink>
<div class="shell">
<div class="footer-inner">
<div class="footer-identity">
<NuxtLink :to="localePath('/')" class="brand">
<AppLogo loading="lazy" />
</NuxtLink>
<p class="footer-tagline">{{ $t('footer.tagline') }}</p>
<p class="footer-meeting">
<AppIcon name="calendar" />
<span>{{ $t('footer.meeting', { day: $t('meeting.every', { day: $t(`weekdays.${site.meeting.weekday}`) }), start: site.meeting.start, end: site.meeting.end }) }}<br />{{ site.meeting.location }}</span>
</p>
<div class="button-row">
<NuxtLink :to="localePath('/mitmachen')" class="button">{{ $t('footer.joinCta') }} <AppIcon name="arrow" :size="16" /></NuxtLink>
<SiteLink :href="site.github" class="button button-secondary"><AppIcon name="github" :size="18" />{{ $t('footer.githubCta') }}</SiteLink>
</div>
</div>
<nav :aria-label="$t('footer.group0')">
<NuxtLink v-for="item in ['mitmachen', 'veranstaltungen', 'team', 'daten-sind-daten']" :key="item" :to="localePath(`/${item}`)">{{ $t(`nav.${item}`) }}</NuxtLink>
<nav class="footer-group" :aria-label="$t('footer.community')">
<p class="footer-group-title">{{ $t('footer.community') }}</p>
<ul>
<li v-for="item in ['mitmachen', 'veranstaltungen', 'team', 'daten-sind-daten']" :key="item">
<NuxtLink :to="localePath(`/${item}`)">{{ $t(`nav.${item}`) }}</NuxtLink>
</li>
</ul>
</nav>
<nav :aria-label="$t('footer.group2')">
<nav class="footer-group" :aria-label="$t('footer.explore')">
<p class="footer-group-title">{{ $t('footer.explore') }}</p>
<ul>
<li v-for="item in ['projekte', 'blog', 'ueber-uns', 'kontakt']" :key="item">
<NuxtLink :to="localePath(`/${item}`)">{{ $t(`nav.${item}`) }}</NuxtLink>
</li>
</ul>
</nav>
<nav class="footer-group" :aria-label="$t('footer.follow')">
<p class="footer-group-title">{{ $t('footer.follow') }}</p>
<ul>
<li><SiteLink :href="site.github"><AppIcon name="github" :size="18" />GitHub</SiteLink></li>
<li><SiteLink :href="site.social.mastodon" rel="me"><AppIcon name="social" :size="18" />Mastodon</SiteLink></li>
<li><SiteLink :href="`/rss.xml?lang=${$i18n.locale}`"><AppIcon name="rss" :size="18" />RSS</SiteLink></li>
<li><SiteLink :href="`mailto:${site.contact.email}`"><AppIcon name="mail" :size="18" />{{ $t('footer.email') }}</SiteLink></li>
</ul>
</nav>
</div>
<div class="footer-meta">
<div class="footer-affiliations">
<i18n-t keypath="footer.network" tag="p">
<template #network><SiteLink :href="site.network.url">{{ site.network.name }}</SiteLink></template>
</i18n-t>
<i18n-t keypath="footer.carrier" tag="p">
<template #association><NuxtLink :to="localePath('/daten-sind-daten')">{{ site.association.name }}</NuxtLink></template>
</i18n-t>
</div>
<nav :aria-label="$t('footer.legal')">
<NuxtLink v-for="item in ['impressum', 'datenschutz', 'code-of-conduct']" :key="item" :to="localePath(`/${item}`)">{{ $t(`nav.${item}`) }}</NuxtLink>
</nav>
</div>
Expand Down
4 changes: 2 additions & 2 deletions docs/design-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ Die Vorlage zeigt eine kompakte neutrale Sans-Serif, kräftige dunkelblaue Über
| Projektkarten | drei Spalten, 4:3-Bildfeld mit quadratischem Signet, Titel, Beschreibung, Themen-Badges und Link; mobil eine Spalte |
| Infoleiste | vier Spalten Desktop, zwei Spalten Mobil; Projektzahl aus Content |
| News / Mitmachen | schlichte Nachrichtenliste neben kleiner heller Mitmachen-Karte |
| Footer | Weiß, obere Border, kompaktes Logo, kleine Links und lineare Social-Icons |
| Footer | Helle Fläche, Identität und Mitmach-CTA, drei benannte Linkgruppen, ruhige Netzwerk-/Rechtszeile; mobil eine, auf Tablets zwei und auf großen Bildschirmen vier Spalten |

Gemeinsame Tailwind-Tokens und mit `@apply` zusammengesetzte Komponentenklassen liegen in `app/assets/css/main.css`. Seiten und Komponenten verwenden dieselben Klassen. Es gibt keine zweite Designimplementierung, keinen Theme-Schalter und keine zusätzlich geladene Schrift. Die frühere abstrakte Kartenzeichnung, der blaue CTA-Banner, die großflächige Vereinsleiste, der mehrspaltige Footer und deren Styles sind ersetzt.
Gemeinsame Tailwind-Tokens und mit `@apply` zusammengesetzte Komponentenklassen liegen in `app/assets/css/main.css`. Seiten und Komponenten verwenden dieselben Klassen. Es gibt keine zweite Designimplementierung, keinen Theme-Schalter und keine zusätzlich geladene Schrift. Die frühere abstrakte Kartenzeichnung, der blaue CTA-Banner, die großflächige Vereinsleiste, der frühere Footer und deren Styles sind ersetzt. Der Community-Footer nutzt das bestehende Logo, zentrale Treffendaten, 16-px-Navigationslinks und beschriftete Social-Links; seine Rechtszeile bleibt bei 14 px.

Die anschließende systematische Lesbarkeitsverbesserung ist in [typography.md](typography.md) dokumentiert: größere Inhalts- und Bedientexte, dunklerer Sekundärton, mehrzeiliger Umbruch und Prüfung schmaler Ansichten.

Expand Down
Binary file modified docs/screenshots/desktop-blog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-da-datenschutz.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-da-impressum.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-da-project-badestellenkarte.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-da-project-kulturbytes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-da-project-kulturnacht-karte.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-da-project-open-city-planner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-da-project-open-data-api.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-daten-sind-daten.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-de-datenschutz.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-de-impressum.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-de-project-badestellenkarte.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-de-project-kulturbytes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-de-project-kulturnacht-karte.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-de-project-open-city-planner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-de-project-open-data-api.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-en-datenschutz.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-en-impressum.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-en-project-badestellenkarte.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-en-project-kulturbytes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-en-project-kulturnacht-karte.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-en-project-open-city-planner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-en-project-open-data-api.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/desktop-home-footer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-home.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/desktop-mitmachen.png
Binary file modified docs/screenshots/desktop-projekte.png
Binary file added docs/screenshots/desktop-ueber-uns-footer.png
Binary file modified docs/screenshots/desktop-ueber-uns.png
Binary file modified docs/screenshots/mobile-blog.png
Binary file modified docs/screenshots/mobile-da-datenschutz.png
Binary file modified docs/screenshots/mobile-da-impressum.png
Binary file modified docs/screenshots/mobile-da-project-badestellenkarte.png
Binary file modified docs/screenshots/mobile-da-project-kulturbytes.png
Binary file modified docs/screenshots/mobile-da-project-kulturnacht-karte.png
Binary file modified docs/screenshots/mobile-da-project-open-city-planner.png
Binary file modified docs/screenshots/mobile-da-project-open-data-api.png
Binary file modified docs/screenshots/mobile-daten-sind-daten.png
Binary file modified docs/screenshots/mobile-de-datenschutz.png
Binary file modified docs/screenshots/mobile-de-impressum.png
Binary file modified docs/screenshots/mobile-de-project-badestellenkarte.png
Binary file modified docs/screenshots/mobile-de-project-kulturbytes.png
Binary file modified docs/screenshots/mobile-de-project-kulturnacht-karte.png
Binary file modified docs/screenshots/mobile-de-project-open-city-planner.png
Binary file modified docs/screenshots/mobile-de-project-open-data-api.png
Binary file modified docs/screenshots/mobile-en-datenschutz.png
Binary file modified docs/screenshots/mobile-en-impressum.png
Binary file modified docs/screenshots/mobile-en-project-badestellenkarte.png
Binary file modified docs/screenshots/mobile-en-project-kulturbytes.png
Binary file modified docs/screenshots/mobile-en-project-kulturnacht-karte.png
Binary file modified docs/screenshots/mobile-en-project-open-city-planner.png
Binary file modified docs/screenshots/mobile-en-project-open-data-api.png
Binary file added docs/screenshots/mobile-home-footer.png
Binary file modified docs/screenshots/mobile-home.png
Binary file modified docs/screenshots/mobile-mitmachen.png
Binary file modified docs/screenshots/mobile-projekte.png
Binary file added docs/screenshots/mobile-ueber-uns-footer.png
Binary file modified docs/screenshots/mobile-ueber-uns.png
5 changes: 3 additions & 2 deletions docs/typography.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Das Audit umfasste globale Styles, alle Vue-Seiten und Komponenten, Nuxt-Content
| Kartenüberschrift | 16 px | 20 px |
| Projekt-/Blog-/Team-/Eventbeschreibung | 13 px | 16/28 px, ab `md` 18/32 px |
| Meeting-Angaben und Hinweise | 12–14 px | 16/28 px |
| Tags, Daten, Byline, Footer | 11–12 px | 14 px |
| Tags, Daten, Byline, Footer-Metazeile | 11–12 px | 14 px |
| Footer-Identität / Navigation | 14 px | 18/32 px / 16/28 px |

Die Hero- und Seitentitel bleiben unverändert. Abschnittstitel nutzen 24/30 px; längere Absätze bekommen 24 px Abstand. Die redaktionelle Breite begrenzt lange Zeilen auf ungefähr 60–80 Zeichen, abhängig vom Text. Blockquotes, Listen, Links, Tabellen und Codeblöcke teilen das Content-Styling; breite Tabellen und Codeblöcke dürfen innerhalb ihres eigenen Bereichs scrollen.

Expand Down Expand Up @@ -53,7 +54,7 @@ Alle verbleibenden `text-sm`-Treffer sind geprüfte Sekundärrollen:
- `.pillars p`: kurze Kennzahlen-/Eigenschaftslabels und Zeitangabe.
- `.tags span`: Kategorien und technische Tags.
- `.card-meta`, `.news-list time`, `.blog-card .byline`, `.article-meta`: Datum, Kategorie und Autor.
- `.footer-tagline`, `.footer nav a`: ergänzender Footer und rechtliche Navigation.
- `.footer-meta`: Netzwerkhinweise und rechtliche Navigation.
- `.breadcrumbs`: Standortnavigation.
- `.filter-bar p`: Ergebnisanzahl, kein Eingabefeld.
- Projekt-Absender: kurzer Organisationshinweis neben dem kanonischen Logo.
Expand Down
15 changes: 11 additions & 4 deletions i18n/locales/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,17 @@
"3": "Onsdag"
},
"footer": {
"tagline": "Åbne data. Stærkt fællesskab. Flensborg.",
"group0": "Udforsk",
"group1": "Vores lab",
"group2": "Godt at vide"
"tagline": "Åbne data, fri software og civic tech for Flensborg og Slesvig-Holsten.",
"community": "Vær med",
"explore": "Gå på opdagelse",
"follow": "Følg os",
"legal": "Juridisk information",
"meeting": "{day} kl. {start}–{end}",
"joinCta": "Kom og vær med",
"githubCta": "Bidrag på GitHub",
"email": "E-mail",
"network": "En del af {network}",
"carrier": "Drevet af {association}"
},
"common": {
"all": "Alle",
Expand Down
15 changes: 11 additions & 4 deletions i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,17 @@
"3": "Mittwoch"
},
"footer": {
"tagline": "Offene Daten. Starke Gemeinschaft. Flensburg.",
"group0": "Entdecken",
"group1": "Das Lab",
"group2": "Gut zu wissen"
"tagline": "Offene Daten, freie Software und Civic Tech für Flensburg und Schleswig-Holstein.",
"community": "Mitmachen",
"explore": "Entdecken",
"follow": "Folge uns",
"legal": "Rechtliches",
"meeting": "{day}, {start}–{end} Uhr",
"joinCta": "Komm dazu",
"githubCta": "Auf GitHub beitragen",
"email": "E-Mail",
"network": "Teil von {network}",
"carrier": "Träger: {association}"
},
"common": {
"all": "Alle",
Expand Down
15 changes: 11 additions & 4 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,17 @@
"3": "Wednesday"
},
"footer": {
"tagline": "Open data. Strong community. Flensburg.",
"group0": "Explore",
"group1": "The lab",
"group2": "Good to know"
"tagline": "Open data, free software and civic tech for Flensburg and Schleswig-Holstein.",
"community": "Get involved",
"explore": "Explore",
"follow": "Follow us",
"legal": "Legal information",
"meeting": "{day}, {start}–{end}",
"joinCta": "Join us",
"githubCta": "Contribute on GitHub",
"email": "Email",
"network": "Part of {network}",
"carrier": "Supported by {association}"
},
"common": {
"all": "All",
Expand Down
74 changes: 74 additions & 0 deletions tests/e2e/footer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'
import { locales, localizedPath, site } from '../../shared/config/site'
import de from '../../i18n/locales/de.json' with { type: 'json' }
import da from '../../i18n/locales/da.json' with { type: 'json' }
import en from '../../i18n/locales/en.json' with { type: 'json' }

const messages = { de, da, en }

for (const locale of locales) {
test(`${locale}: community footer has localized, accessible navigation and safe social links`, async ({ page }) => {
await page.goto(localizedPath('/', locale))
const footer = page.getByRole('contentinfo')
const copy = messages[locale]
await expect(footer.locator('.footer-tagline')).toHaveText(copy.footer.tagline)
await expect(footer.locator('.footer-meeting')).toContainText(copy.weekdays[site.meeting.weekday])
for (const value of [site.meeting.start, site.meeting.end, site.meeting.location]) {
await expect(footer.locator('.footer-meeting')).toContainText(value)
}
await expect(footer.getByRole('link', { name: site.name, exact: true })).toHaveAttribute('href', localizedPath('/', locale))
for (const [label, paths] of [
[copy.footer.community, ['mitmachen', 'veranstaltungen', 'team', 'daten-sind-daten']],
[copy.footer.explore, ['projekte', 'blog', 'ueber-uns', 'kontakt']],
[copy.footer.legal, ['impressum', 'datenschutz', 'code-of-conduct']],
] as const) {
const group = footer.getByRole('navigation', { name: label, exact: true })
for (const path of paths) {
const link = group.getByRole('link', { name: copy.nav[path], exact: true })
await expect(link).toHaveAttribute('href', localizedPath(`/${path}`, locale))
await expect(link).not.toHaveAttribute('target')
}
}
const follow = footer.getByRole('navigation', { name: copy.footer.follow, exact: true })
const mastodon = follow.getByRole('link', { name: 'Mastodon', exact: true })
await expect(mastodon).toHaveAttribute('href', site.social.mastodon)
await expect(mastodon).toHaveAttribute('rel', 'me noopener noreferrer')
await expect(follow.getByRole('link', { name: 'GitHub', exact: true })).toHaveAttribute('href', site.github)
for (const link of await footer.locator(`a[href="${site.github}"], a[href="${site.social.mastodon}"], a[href="${site.network.url}"]`).all()) {
await expect(link).toHaveAttribute('target', '_blank')
await expect(link).toHaveAttribute('rel', /\bnoopener noreferrer\b/)
}
const rss = follow.getByRole('link', { name: 'RSS', exact: true })
await expect(rss).toHaveAttribute('href', `/rss.xml?lang=${locale}`)
await expect(rss).not.toHaveAttribute('target')
const email = follow.getByRole('link', { name: copy.footer.email, exact: true })
await expect(email).toHaveAttribute('href', `mailto:${site.contact.email}`)
await expect(email).not.toHaveAttribute('target')
await expect(footer.locator('.footer-meta').getByRole('link', { name: site.network.name })).toHaveAttribute('href', site.network.url)
await expect(footer.locator('.footer-meta').getByRole('link', { name: site.association.name })).toHaveAttribute('href', localizedPath('/daten-sind-daten', locale))
const join = footer.getByRole('link', { name: copy.footer.joinCta, exact: true })
await join.focus()
await page.keyboard.press('Tab')
const githubCta = footer.getByRole('link', { name: copy.footer.githubCta, exact: true })
await expect(githubCta).toBeFocused()
await expect(githubCta).toHaveCSS('outline-style', 'solid')
const undersized = await footer.locator('a').evaluateAll((links) => links.filter((link) => link.getBoundingClientRect().height < 44).map((link) => link.textContent))
expect(undersized).toEqual([])
for (const link of await footer.locator('.footer-group a').all()) await expect(link).toHaveCSS('font-size', '16px')
expect((await new AxeBuilder({ page }).include('footer').withTags(['wcag2a', 'wcag2aa', 'wcag21aa']).analyze()).violations).toEqual([])
await join.click()
await expect(page).toHaveURL(localizedPath('/mitmachen', locale))
})
}

test('footer groups reflow from one to two to four columns', async ({ page, isMobile }) => {
test.skip(isMobile)
for (const [width, columns] of [[390, 1], [900, 2], [1440, 4]]) {
await page.setViewportSize({ width: width!, height: 1000 })
await page.goto('/')
const grid = page.locator('.footer-inner')
expect(await grid.evaluate((node) => getComputedStyle(node).gridTemplateColumns.split(' ').length)).toBe(columns)
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
}
})
Loading