diff --git a/app/components/banner-slider.php b/app/components/banner-slider.php index 3274e29..cf347c3 100644 --- a/app/components/banner-slider.php +++ b/app/components/banner-slider.php @@ -98,7 +98,7 @@ $total = count($banners); 1): ?> diff --git a/app/components/group-schedule.php b/app/components/group-schedule.php index ab64d49..f041062 100644 --- a/app/components/group-schedule.php +++ b/app/components/group-schedule.php @@ -139,7 +139,7 @@ $soleNote = $single ? ($groups[0]['note'] ?? '') : '';
$g): ?> - -
diff --git a/app/pages/mitmachen.php b/app/pages/mitmachen.php index a8f9b56..1d65740 100644 --- a/app/pages/mitmachen.php +++ b/app/pages/mitmachen.php @@ -52,7 +52,7 @@ component('hero', ['hero' => $page['hero']]);
$f): ?> - +
diff --git a/public/assets/css/components.css b/public/assets/css/components.css index 5f8f4b5..23edf01 100644 --- a/public/assets/css/components.css +++ b/public/assets/css/components.css @@ -987,7 +987,14 @@ margin-bottom: var(--space-2xl); } -.filter-bar__tab { +/* Gemeinsame Tab-Basis für Filter-Leiste und Gruppen-Tabs. Beide waren bis auf + flex-shrink und 4px Innenabstand zeichengleich implementiert. Aktiv-Zustand + kommt je nach Muster über aria-pressed (Filter) oder aria-selected (Tabs). + Fokus liegt inset, damit der Ring nicht in den Nachbar-Tab ragt. */ +.tab { + display: inline-flex; + align-items: center; + min-height: var(--tap-min); padding: var(--space-xs) var(--space-m); background: none; border: none; @@ -1000,24 +1007,26 @@ transition: color var(--transition), box-shadow var(--transition); } -.filter-bar__tab:first-child { - padding-inline-start: 0; -} - -.filter-bar__tab:hover { +.tab:hover { color: var(--clr-text); } -.filter-bar__tab[aria-pressed="true"] { +.tab[aria-pressed="true"], +.tab[aria-selected="true"] { color: var(--clr-text); box-shadow: inset 0 -2px 0 var(--clr-accent); } -.filter-bar__tab:focus-visible { - outline: 2px solid var(--clr-accent); +.tab:focus-visible { + outline: var(--focus-ring); outline-offset: -2px; } +/* Erster Filter bündig zur Textkante links. */ +.filter-bar__tab:first-child { + padding-inline-start: 0; +} + .positions__list { display: grid; gap: var(--space-l); @@ -1730,55 +1739,27 @@ display: none; } -.ad-banner__toggle { - display: inline-grid; - place-items: center; - width: 2.25rem; - height: 2.25rem; - padding: 0; - color: var(--clr-text); - background: var(--clr-glass-bg); - border: 1px solid var(--clr-glass-border); - border-radius: 999px; - cursor: pointer; - transition: background var(--transition); -} - .ad-banner__toggle[hidden] { display: none; } -.ad-banner__nav-btn { - display: inline-grid; - place-items: center; - width: 2.25rem; - height: 2.25rem; - padding: 0; +/* Glas-Optik als Delta zu .btn--icon — Größe, Rundung, Zentrierung und die + 44px-Trefferfläche kommen von dort. Beide Controls waren vorher zeichengleich + dupliziert. .btn muss im Selektor mitstehen: utilities.css lädt nach + components.css, bei gleicher Spezifität gewänne sonst die rote .btn-Fläche. */ +.btn.ad-banner__toggle, +.btn.ad-banner__nav-btn { font-size: var(--icon-s); color: var(--clr-text); background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); - border-radius: 999px; - cursor: pointer; transition: background var(--transition); } -.ad-banner__nav-btn:hover { +.btn.ad-banner__toggle:hover, +.btn.ad-banner__nav-btn:hover { background: rgb(255 255 255 / 0.2); -} - -.ad-banner__nav-btn:focus-visible { - outline: 2px solid var(--clr-text); - outline-offset: 3px; -} - -.ad-banner__toggle:hover { - background: rgb(255 255 255 / 0.2); -} - -.ad-banner__toggle:focus-visible { - outline: 2px solid var(--clr-text); - outline-offset: 3px; + border-color: var(--clr-glass-border); } .ad-banner__toggle-icon { @@ -1806,20 +1787,34 @@ gap: var(--space-2xs); } +/* Trefferfläche 48x44px, sichtbar bleibt der 6px-Balken. Der Balken liegt im + ::before statt auf dem Button selbst — so kann overflow:hidden entfallen, das + sonst jede Vergrößerung der Trefferfläche wieder abschneiden würde. */ .ad-banner__dot { position: relative; width: 48px; - height: 6px; + height: var(--tap-min); padding: 0; + background: none; border: 0; - border-radius: 999px; - background: var(--clr-glass-border); cursor: pointer; - overflow: hidden; +} + +.ad-banner__dot::before, +.ad-banner__progress { + position: absolute; + inset: calc(50% - 3px) 0 auto; + height: 6px; + border-radius: 999px; +} + +.ad-banner__dot::before { + content: ""; + background: var(--clr-glass-border); transition: background var(--transition); } -.ad-banner__dot:hover { +.ad-banner__dot:hover::before { background: rgb(255 255 255 / 0.35); } @@ -1829,8 +1824,6 @@ } .ad-banner__progress { - position: absolute; - inset: 0; background: var(--clr-accent); transform: scaleX(0); transform-origin: left; @@ -2217,32 +2210,9 @@ .group-tabs__bar::-webkit-scrollbar { display: none; } +/* Tabs im Panel dürfen nicht schrumpfen — sie scrollen stattdessen horizontal. */ .group-tabs__tab { flex-shrink: 0; - padding: var(--space-xs) var(--space-l); - background: none; - border: none; - box-shadow: inset 0 -2px 0 var(--clr-glass-border); - font-family: var(--ff-body); - font-size: var(--fs-500); - color: var(--clr-text-muted); - cursor: pointer; - white-space: nowrap; - transition: color var(--transition), box-shadow var(--transition); -} - -.group-tabs__tab:hover { - color: var(--clr-text); -} - -.group-tabs__tab[aria-selected="true"] { - color: var(--clr-text); - box-shadow: inset 0 -2px 0 var(--clr-accent); -} - -.group-tabs__tab:focus-visible { - outline: 2px solid var(--clr-accent); - outline-offset: -2px; } /* Panel-Karte übernimmt das bestehende group-card--stacked-Layout */ @@ -3407,11 +3377,9 @@ } .department-choice__btn { + /* display/align-items/gap kommen jetzt aus .btn */ align-self: start; margin-top: auto; - display: inline-flex; - align-items: center; - gap: var(--space-2xs); } .department-choice__arrow { diff --git a/public/assets/css/layout.css b/public/assets/css/layout.css index 362a13c..92a2c01 100644 --- a/public/assets/css/layout.css +++ b/public/assets/css/layout.css @@ -89,6 +89,11 @@ } .site-nav__list a:not(.btn) { + display: inline-flex; + align-items: center; + /* 44px Trefferfläche (WCAG 2.5.5). Die Leiste ist --header-h (60px) hoch und + zentriert ihre Punkte, die Höhe ist deshalb nicht sichtbar. */ + min-height: var(--tap-min); color: var(--clr-text); text-decoration: none; padding-block: 0.25em; @@ -123,6 +128,7 @@ .site-nav__sublink { display: inline-flex; align-items: center; + min-height: var(--tap-min); gap: 0.3em; margin: 0; padding: 0.25em 0; @@ -599,6 +605,7 @@ .site-footer__social a { display: inline-flex; align-items: center; + min-height: var(--tap-min); gap: var(--space-2xs); padding-block: var(--space-3xs); color: var(--clr-text-muted); diff --git a/public/assets/css/utilities.css b/public/assets/css/utilities.css index 12d767a..b60dfae 100644 --- a/public/assets/css/utilities.css +++ b/public/assets/css/utilities.css @@ -1,8 +1,39 @@ /* ============================================================ UTILITIES — Buttons, visually-hidden, kleine Helfer ============================================================ */ +/* --- Trefferflächen --------------------------------------------------------- + WCAG 2.5.5 wertet die Trefferfläche, nicht die gezeichnete Fläche. Controls, + die optisch klein bleiben sollen (dezente Buttons, Slider-Punkte), strecken + sie deshalb über ein unsichtbares Pseudo-Element auf --tap-min, statt zu + wachsen. Voraussetzung: das Element darf kein overflow: hidden tragen und + ::after muss frei sein. */ +.tap-target, +.btn--sm { + position: relative; +} + +.tap-target::after, +.btn--sm::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + translate: -50% -50%; + width: 100%; + height: 100%; + min-width: var(--tap-min); + min-height: var(--tap-min); +} + +/* --- Buttons --------------------------------------------------------------- + inline-flex statt inline-block, damit min-height die Trefferfläche sichern + kann und Icon+Label ohne Zusatz-Markup zentriert nebeneinander liegen. */ .btn { - display: inline-block; + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2xs); + min-height: var(--tap-min); padding: var(--btn-pad-y) var(--btn-pad-x); white-space: nowrap; background: var(--clr-accent); @@ -26,7 +57,7 @@ } .btn:focus-visible { - outline: 2px solid var(--clr-text); + outline: var(--focus-ring); outline-offset: 2px; } @@ -53,8 +84,37 @@ color: var(--clr-text); } -/* Größenmodifier — Padding ist em-basiert, skaliert mit der font-size */ -.btn--sm { font-size: var(--btn-font-sm); } +/* tertiary — ohne Fläche und Rahmen, für Aktionen neben einem primären Button. + Unterstrichen, damit der Button nicht allein über die Farbe erkennbar ist. */ +.btn--ghost { + background: transparent; + border-color: transparent; + text-decoration: underline; +} + +.btn--ghost:hover { + background: var(--clr-glass-bg); + border-color: transparent; + color: var(--clr-text); +} + +/* Icon-Button — quadratisch, nur ein Icon, Label über aria-label. + Das Icon selbst skaliert über font-size (siehe --icon-*). */ +.btn--icon { + gap: 0; + min-width: var(--tap-min); + padding: 0; + border-radius: 999px; +} + +/* Größenmodifier — Padding ist em-basiert, skaliert mit der font-size. + --sm bleibt bewusst optisch klein und holt die 44px über die Trefferfläche; + dafür muss min-height hier zurückgenommen werden. */ +.btn--sm { + min-height: 0; + font-size: var(--btn-font-sm); +} + .btn--lg { font-size: var(--btn-font-lg); } .visually-hidden { diff --git a/public/dateien/.htaccess b/public/dateien/.htaccess new file mode 100644 index 0000000..a027ed2 --- /dev/null +++ b/public/dateien/.htaccess @@ -0,0 +1,28 @@ +# Dateiablage /dateien (Files Gallery). Die Regeln aus public/.htaccess gelten hier vererbt +# weiter — nur diese drei Punkte müssen abweichen. + + + # Files Gallery rendert Inline-