Commit Graph

63 Commits

Author SHA1 Message Date
1213fa4ca1 Sync-Stand vom Server: Wappen und Instagram-Bilder aktualisiert 2026-07-31 16:36:23 +02:00
fs
e6f72a0e4b Wappen und Instagram-Bilder versionieren
Eine veröffentlichte Stadionzeitung friert Verweise auf genau diese Dateien
dauerhaft ein: 37 der 86 Bilder im Hutschdorf-Heft lagen in gitignorierten
Ordnern (33 Wappen, 4 Instagram-Fotos). Auf einem frischen Klon wären sie
kaputt, und der Instagram-Sync räumt alte Posts irgendwann weg, womit eine
archivierte Ausgabe nachträglich zerfiele.

Die JSON-Caches bleiben draußen, die baut der Sync neu. Maschinenverwaltet und
flüchtig ist nicht dasselbe wie maschinenverwaltet und für immer referenziert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 16:02:56 +02:00
fs
2f8619e78d Gedruckte Adressen aus club.website statt base_url
QR-Codes und Adresszeilen trugen `http://localhost:8000` ins Druckmaterial, weil
sie über abs_url() liefen. Nach dem Produktivgang hätten alle zehn Codes neu
erzeugt werden müssen — und ein gedruckter Code mit localhost darin ist Altpapier.

Die öffentliche Domain ist Vereinsidentität, keine Umgebungseinstellung: neues
Feld `club.website` in data/club.json plus club_url()/club_domain(). base_url
bleibt umgebungsabhängig, weil Canonical, OG, Sitemap und JSON-LD zur
ausliefernden Adresse passen müssen.

Umgestellt: Cover-QR und die acht geteilten CTA-QRs, die Internet-Zeile im
Heft-Impressum, die Cover-Zeile neben dem QR, die acht Linkzeilen unter den
CTA-QRs der Live-Seiten (die hatte ich beim ersten Durchgang übersehen), das
Web-Feld im Website-Impressum und bin/qr.php.

Nebenbei: club_domain() ohne Pfad hängte einen Wurzel-Slash an
(„tsv08kulmbach.de/"), und der Kopf-Kommentar von stadionzeitung-add.php
beschrieb noch die alten vier Anzeigen-Gruppen.

Nicht verifizierbar und deshalb dokumentiert: die vendorte QR-Bibliothek
arbeitet nicht reproduzierbar (gleiche URL, andere Pixel — anderes Maskenmuster,
beides gültig). Ein PNG-Vergleich beweist nichts, nachweisbar ist nur der
Code-Pfad. Jeden Druck-QR einmal scannen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 15:40:59 +02:00
fs
a101b0c3ec Stadionzeitung, Veranstaltungen, News-Artikel, Lightbox und Anzeigen-Verteilung
Stadionzeitung: Live-Seiten (Tabellen, Ergebnisse, Vorschau, Termine, News,
Kontakt, Historie, Sportheim, Partner, Momente, Impressum), automatisches Cover,
Swipe-Viewer mit Vollbildmodus und die Druckfassung als Falt-Heft. Anzeigen
kommen jetzt aus dem Bestand und werden gleichmäßig verteilt, nie mehr als zwei
hintereinander (vorher vier Blöcke à fünf). Die Doppelseiten-Regel rechnet das
Skript selbst, statt sie von Hand zu prüfen.

Veranstaltungen: Bereich /veranstaltungen mit Detailseite pro Fest, Lebenszyklus
über das Datum (Ankündigung vor dem Fest, Rückblick danach), Galerie mit
Lightbox. veranstaltungen.json ist dritte Termin-Quelle, damit ein Fest nie
doppelt gepflegt wird.

News-Artikel als dritte dynamische Prefix-Route, gemeinsamer detail-head.

Behobene Fehler:
- Wappen-Kontexte setzten Zeilen-Layout auf .crest statt auf die Zeile; das
  Wappen wurde zum Grid, Vereinsname und Tore rutschten darunter zusammen
  (Ergebnis-Rückblick, Vorschau, Cover).
- --header-h (60px) unterschätzt die Kopfleiste um 32px (Logo 72px + 20px
  Versatz): neues abgeleitetes --header-space, sonst klebten Zurück-Links
  unter dem Logo.
- base_url in der Produktions-Config ohne www, während .htaccess auf www
  umleitet; preflight prüft das jetzt.
- .lightbox brauchte display:none mit (0,2,0), sonst lag das Overlay offen.

Neue Werkzeuge: bin/anzeige-add.php (Anzeigen-Bestand), bin/veranstaltung-bilder.php
(Plakate und Galerien, Alt-Texte folgen der Quelldatei), bin/qr.php (Druck-QR mit
Warnung, wenn base_url nicht auf die echte Domain zeigt).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 14:55:27 +02:00
fs
3fb480dd78 Inhalte und Bilder: Stadionzeitung, Veranstaltungen, News, Anzeigen-Bestand
Neue Datenquellen:
- data/veranstaltungen.json  Feste mit eigener Detailseite (dritte Termin-Quelle)
- data/anzeigen.json         Sponsoren-Anzeigen als dauerhafter Bestand statt
                             pro Ausgabe abgeliefert
- data/stadionzeitung.json   Ausgaben-Archiv (maschinenverwaltet)
- data/news.json             redaktionelle Meldungen mit Artikelseiten

Bilder einmal statt pro Ausgabe: public/assets/img/anzeigen/ hält die 19
Sponsorenmotive plus drei Eigenanzeigen; vorher lag jede Anzeige als Kopie
unter jeder Stadionzeitungs-Ausgabe.

Zuerst die Inhalte, dann der Code: so ist der Baum in jedem Schritt gültig.
Daten ohne die zugehörigen Seiten sind harmlos, Seiten ohne Daten nicht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 14:54:37 +02:00
fs
82cf21085d Design-System: Fokus vereinheitlichen, Formularfeld-Ring WCAG-fest machen
Letzter Teil. base.css hatte längst eine globale :focus-visible-Regel, trotzdem
existierten 15 Fokus-Regeln — die meisten davon Ballast.

Fünf Overrides wiederholten die Basisregel praktisch wortgleich (.back-to-top,
.ad-banner__dot, .faq__q, .table-scroll, .btn) und sind ersatzlos entfallen; sie
unterschieden sich höchstens um 1px Offset. Die Basisregel selbst läuft jetzt
über --focus-ring/--focus-offset, .position-card__head wechselt von Akzentrot
auf Weiß. Übrig bleiben zehn Regeln, davon deklarieren nur noch vier überhaupt
eine outline — jede mit Grund (inset-Offset bei .tab und .position-card__head,
geschichteter Ring beim Formularfeld). Die anderen sechs sind reine Zusatz-
effekte (transform, Farbe, Fläche) und lassen den Basis-Ring durch.

Der eigentliche Fund steckte im Formularfeld: es setzte outline: none und
ersetzte den Ring durch einen roten Rahmen mit 30%-Glow. Akzentrot erreicht auf
der Feldfläche aber nur 2,8:1 und verfehlt damit WCAG 1.4.11 (>=3:1) für
Fokus-Indikatoren — Tastaturnutzer hatten dort also den schwächsten Fokus der
ganzen Seite. Roter Rahmen und Glow bleiben als Marken-Signal, der weiße
Standard-Ring kommt zurück; outline-offset entspricht der Glow-Breite, beide
Ringe liegen bündig nebeneinander statt übereinander.

CLAUDE.md dokumentiert die neuen Regeln: Token-Disziplin mit Tabelle (Spacing,
Typo, Icons, Sperrung, Fokus, Trefferfläche) samt der bewussten em-Ausnahme,
das Button- und Tab-System inklusive der (0,2,0)-Falle durch die
CSS-Ladereihenfolge, und die Fokus-Regel. Dazu ein Punkt in der Checkliste.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSsVdooFLgPA8gPFp7HwZU
2026-07-27 10:33:53 +02:00
fs
eacf9449e0 Design-System: Button-Varianten konsolidieren, 44px-Trefferflächen
Dritter Teil. .btn hatte nur primary + outline + sm/lg, daneben lebten mehrere
Controls als Eigenbau — teils zeichengleich dupliziert.

Tabs: .group-tabs__tab und .filter-bar__tab waren bis auf flex-shrink und 4px
Innenabstand identisch. Beide laufen jetzt über eine gemeinsame .tab-Basis, die
aria-pressed (Filter) und aria-selected (Tabs) gleich behandelt; von den
BEM-Klassen bleibt nur das jeweilige Delta. Innenabstand vereinheitlicht auf
--space-m, die Gruppen-Tabs verlieren dadurch 4px pro Seite.

Icon-Buttons: .ad-banner__nav-btn und .ad-banner__toggle waren vollständige
Duplikate voneinander. Sie tragen jetzt .btn .btn--icon und behalten nur die
Glas-Optik als Delta. Der Selektor schreibt .btn mit — utilities.css lädt nach
components.css, bei gleicher Spezifität gewänne sonst die rote .btn-Fläche.

Trefferflächen auf 44px (WCAG 2.5.5): .btn bekommt min-height und wird
inline-flex, damit Icon und Label ohne Zusatz-Markup zentriert liegen.
Bezeichnend auch hier — .department-choice__btn hatte inline-flex, align-items
und exakt denselben gap bereits selbst deklariert und gibt sie jetzt ab.
Controls, die optisch klein bleiben sollen, strecken die Fläche stattdessen
unsichtbar per Pseudo-Element (.tap-target, automatisch auch für .btn--sm) —
gewertet wird die Trefferfläche, nicht die gezeichnete.

Der Banner-Indikator brauchte einen anderen Weg: er trug overflow: hidden, um
den Fortschrittsbalken zu beschneiden, was jede Erweiterung wieder abgeschnitten
hätte. Der sichtbare 6px-Balken liegt jetzt im ::before, der Fortschritt ist
selbst rund — overflow entfällt, der Button ist 48x44px.

Haupt-Nav-Links, Dropdown-Trigger und Footer-Social-Links bekommen min-height
statt einer Pseudo-Erweiterung: sie stehen in Reihen, wo überlappende
Trefferflächen den Nachbarn verdecken würden. In der 60px hohen Kopfleiste ist
die Höhe ohnehin nicht sichtbar.

Bewusst unverändert: die Formular-Checkbox bleibt bei 24px (WCAG 2.5.8).
<input> ist ein Replaced Element, dort greift ::after nicht zuverlässig — und
das zugehörige <label> ist klickbar und liefert die große Fläche.

.btn--ghost ist angelegt, hat aber aktuell keinen Abnehmer: die vermuteten
nackten Links sind bei näherem Hinsehen Bildkarten (.instagram__link),
Wrapper (.split__cta) oder eigenständige Karten-Affordanzen (.team-card__cta),
keine tertiären Buttons.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSsVdooFLgPA8gPFp7HwZU
2026-07-27 10:28:02 +02:00
fs
3fa521eaf1 Design-System: Typo-Skala schließen, h4–h6 auf Abel, Icon-Größen tokenisieren
Zweiter Teil der Konsolidierung. Vorher standen 31 font-size-Werte außerhalb
der Skala — beim Durchgehen stellte sich heraus, dass 20 davon gar keine
Textgrößen sind: der icon()-Helper rendert 1em, also steuerte font-size dort
die Icon-Größe. Dafür gibt es jetzt eine eigene, kleine Skala (--icon-s/m/l/xl
in rem, --icon-inline-sm/--icon-inline/--icon-inline-lg in em für Icons, die im
Textfluss mitwachsen sollen). Elf Ad-hoc-Werte fallen damit auf sieben Stufen.

h4–h6 laufen nicht mehr über Coolvetica. Sie lagen mit 26px und 21px unter der
--fs-650-Grenze aus CLAUDE.md, ab der der Condensed-Schnitt in Uppercase
unleserlich wird — die Regel war also nur dokumentiert, nicht durchsetzbar.
Jetzt tragen h1–h3 Coolvetica (alle über der Grenze) und h4–h6 Abel mit
Sperrung. Bezeichnend: das einzige <h4> im Markup hatte sich genau dieses
Muster bereits von Hand gebaut. Die Basisregel liefert es jetzt, die Komponente
gibt ihre drei Duplikat-Deklarationen ab — sichtbar ändert sich dadurch nichts.

Neue Stufen schließen die Lücken: --fs-550 (fluider Lead), --fs-690 (Karten-
Überschrift) und --fs-1000 (Display-Hero). Vier fast deckungsgleiche clamp()-
Rampen für Karten-Überschriften (bento__heading, bento__stat-value,
group-card__name, team-card__name) laufen jetzt über --fs-690; team-card__name
wächst dabei am Desktop um ~5px, die anderen drei bleiben im 1px-Bereich.

Die Wappen-Initialen im Matchcenter hingen an zwei Mini-Schriftgrößen (10,4 und
11,2px) unterhalb der Skala. Sie sind in Wahrheit ~0,4 × Kreisdurchmesser,
folgen jetzt also --crest-size statt einer eigenen font-size. Nebenbei fallen
drei redundante Doppel-Selektoren weg: .crest--initials trägt immer auch .crest.

Ebenfalls tokenisiert: 27 letter-spacing-Werte mit elf verschiedenen Zahlen auf
vier Stufen (--ls-tight/caps/wide/wider), maximale Abweichung 0,02em.

Danach steht kein font-size- und kein letter-spacing-Wert mehr außerhalb von
tokens.css.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSsVdooFLgPA8gPFp7HwZU
2026-07-27 10:17:13 +02:00
fs
87055bd14b Design-System: Spacing auf Tokens migrieren
Alle padding/margin/gap-Deklarationen in components.css, layout.css und
base.css nutzen jetzt die Spacing-Skala — 414 Token-Verwendungen, kein roher
rem-Wert mehr in einer Abstands-Deklaration.

Die Substitution lief mechanisch (nächste Rasterstufe, Gleichstand aufgerundet),
damit sie deterministisch und nachvollziehbar ist statt 450-mal von Hand
geraten. Rund 325 Werte lagen bereits exakt auf einer Stufe und sind
verlustfrei übernommen. 125 Werte verschieben sich:

  ±0,4–2,0px   106 Werte  (0.1/0.15/0.3/0.35/0.4/0.6/0.85/0.9/1.1/1.6rem …)
  ±4px          12 Werte  (2.25 -> 2.5, 2.75 -> 3rem)
  ±8px           8 Werte  (3.5 -> 4, 4.5 -> 5, 7.5 -> 7rem)

Die 8px-Sprünge treffen ausschließlich Sektionsabstände (padding-block großer
Bänder, margin-top von Blöcken), wo sie optisch nicht ins Gewicht fallen. Die
±4px-Gruppe ist 2.25rem, das exakt zwischen zwei Stufen lag.

em-Werte bleiben bewusst roh: sie skalieren mit der font-size (Button-Padding,
Badge-Innenabstände) und gehören damit nicht auf ein festes px-Raster. Nullen,
auto und Prozentwerte sind unangetastet, calc() wurde korrekt mitgezogen
(calc(var(--header-h) + var(--space-5xl))).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSsVdooFLgPA8gPFp7HwZU
2026-07-27 10:09:33 +02:00
fs
8304095dc3 Design-System: Token-Ebene für Spacing, Fokus und Tap-Targets
Vorbereitung für die Konsolidierung. tokens.css war für Farben und Typo schon
die Single Source of Truth, für Abstände aber nur mit --space-section und
--container-pad besetzt — dem standen ~450 Spacing-Deklarationen mit 39
verschiedenen rem-Werten gegenüber.

Neu: 14-stufige Spacing-Skala auf 4px-Raster, aus dem Ist-Bestand geclustert
statt frei erfunden. Die neun häufigsten Werte der Codebase (1, 1.5, 1.25, 2.5,
0.75, 0.5, 1.75, 2, 3rem — zusammen ~60% aller Deklarationen) liegen bereits
exakt auf Stufen, die Migration ist dort verlustfrei. Nach oben wird das Raster
gröber (3 → 4 → 5 → 7rem), weil große Abstände das vertragen. --space-4xs (2px)
ist ausdrücklich nur für optische Korrekturen an Icon-/Baseline-Nudges.

Dazu --tap-min (44px, WCAG 2.5.5) und --focus-ring/--focus-offset. Der Fokus
wird auf Weiß festgeschrieben: 15,9:1 auf --clr-bg gegenüber 3,2:1 für Rot,
das WCAG 1.4.11 nur knapp schafft und als einziger Indikator zu schwach ist.

--container-pad und --space-section verweisen jetzt auf die Skala statt eigene
Werte zu führen. Entfernt: --clr-bg-light und --clr-surface-raised, beide
projektweit 0× verwendet.

Consumer folgen in den nächsten Commits — dieser Schritt ändert die Darstellung
noch nicht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSsVdooFLgPA8gPFp7HwZU
2026-07-27 10:07:31 +02:00
fs
96a698ebdc Mobiles Menü als Drawer: oben verankert, Accordion statt Sprung
Das Overlay war vertikal zentriert. Beim Aufklappen eines Dropdowns wuchs die
Liste und rezentrierte sich — der gerade angetippte Punkt sprang unter dem
Finger weg, bei langen Menüs lief der Inhalt aus dem Screen (nicht scrollbar).

Jetzt ist die Liste oben verankert (justify-content: flex-start, padding unter
Header + Logo-Überhang) und scrollbar mit overscroll-behavior: contain. Punkte
sind volle Tap-Breite mit Hairline-Trennern, Chevron rechtsbündig, aktive Seite
mit rotem Balken links. Untermenüs animieren über die 0fr/1fr-Zeile des
li-Grids statt display: none — weiches Auf-/Zuklappen ohne Layout-Sprung.

Unterpunkte in Abel statt Coolvetica (unter 28px unleserlich, siehe CLAUDE.md),
linksbündig, Desktop-Panel-Optik im Overlay komplett zurückgesetzt (inkl.
backdrop-filter/radius/translateX, die sonst durchschlugen).

nav.js setzt .site-nav--js auf die Nav: nur ohne JS öffnet :focus-within die
Untermenüs. Mit JS wäre das ein Bug — der Button behält nach dem Tipp den
Fokus, das Menü ließe sich nie wieder zuklappen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSsVdooFLgPA8gPFp7HwZU
2026-07-27 09:46:40 +02:00
fs
68c3b51c41 Matchcenter: Tabelle vor dem 1. Spieltag + Testspiele kennzeichnen
Die Tabelle der Frauenmannschaft sah kaputt aus (zehn Mannschaften auf Platz 1,
alle grün als Aufstiegszone, überall 0), obwohl sie korrekt war: die Liga
startet erst am 06.09., und das einzige Ergebnis war ein Freundschaftsspiel.
Beides kommt so vom BFV — vor dem ersten Spieltag liefert die API jeder
Mannschaft Rang 1 und dieselbe Zone.

league-table erkennt das jetzt an der Summe der gespielten Spiele: Rangspalte
zeigt „–", Zonenfarben und Legende entfallen, darüber steht der Hinweis, dass
ab dem 1. Spieltag gewertet wird. Tabellen mit laufender Saison (Kreisklasse)
bleiben unverändert.

Spielzeilen tragen zusätzlich „Freundschaftsspiel" (match_competition_label),
damit sichtbar ist, warum ein Testspiel-Ergebnis nicht in der Tabelle steht.
Ligaspiele bleiben unbeschriftet — die Sektion nennt die Liga schon.

Enthält außerdem den Rahmen um den Tabellen-Scroller (table-scroll-frame),
der im Arbeitsstand lag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 00:40:44 +02:00
fs
66c402d9f7 Matchcenter: „Nächste Spiele" als Band statt Autoplay-Slider
Der Bereich wirkte deplatziert: der Track war auf 620px begrenzt und zeigte
eine Folie, wodurch die Karte auf breiten Fenstern in ~840px Totraum schwebte;
dazu derselbe Hintergrund wie der Hero direkt darüber, kein Akzent, und ein
einsames „:" zwischen den Wappen, das wie ein fehlendes Ergebnis aussah.

Neu als abgesetztes Band (.section--sunken, nutzt endlich das dafür
vorgesehene, bisher unbenutzte Token --clr-bg-deep) mit roter Oberkante:
links der Aufmacher (match-feature), rechts die folgenden Termine als
verlinkte match-row-Zeilen ohne Kartenfläche.

Der Aufmacher ist die große Variante der Terminzeile — Heim über Gast,
linksbündig, gleiche Struktur wie die Zeilen daneben. Bewusst nur drei
typografische Rollen (Meta-Zeile · Vereinsnamen in Coolvetica · Anstoßzeile
mit Countdown) statt der acht der ersten Fassung, ohne Badges, Icons oder
Button. Die eigene Mannschaft wird über Helligkeit markiert, nicht über
font-weight — Coolvetica hat nur einen Schnitt, bold wäre synthetisch.

match-row bekommt optionale $href/$meta-Props; Raster und Innenabstand liegen
jetzt im __link-Wrapper, damit als Link die ganze Zeilenfläche klickbar ist.
Neue Helper match_when() (ISO + deutscher Countdown, explizit Europe/Berlin,
weil PHP global auf UTC läuft) und match_competition_label().

match-slider.php und match-card.php entfallen — Letztere wurde
ausschließlich vom Slider genutzt. carousel.js treibt damit nur noch den
Banner-Slider der Startseite und wird im Matchcenter nicht mehr geladen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 00:39:57 +02:00
fs
3a591fe8d0 Matchcenter: Saison-Tabs, Vorsaison-Archiv & gehärteter Cache-Write
- Sync schreibt Cache jetzt fehlersicher: json_encode mit JSON_THROW_ON_ERROR
  + Rückgabeprüfung von file_put_contents/rename → ein defektes UTF-8-Zeichen
  aus der BFV-API kann den guten Cache nicht mehr leerschreiben (alter Cache
  bleibt via $fail unangetastet)
- Vorsaison wird bei erkanntem compoundId-Wechsel einmalig archiviert (previous),
  pro Team compound_id + abgeleitetes season-Label + stale-Flag persistiert
- Aktuelle Saison ist immer sichtbar (auch leer), Wechsel zur Vorsaison über
  wiederverwendete Stripe-Tabbar (group-tabs); neue Komponente matchcenter-season
- Leere aktuelle Saison zeigt Hinweis statt leerer Fläche

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 00:51:14 +02:00
fs
8437f00e06 Feinschliff: Formulare neu, Glas global dunkler, Hero-Fade sauber
Formulare (form-field.php, 3 Form-Wrapper, components.css, form.js):
- Floating Labels (Label im Feld, wandert bei Fokus/Inhalt hoch; select/date
  dauerhaft oben) via :has() + placeholder=" "-Trick. Echtes <label> bleibt
  im DOM (kein Placeholder-als-Label).
- Globale "Pflichtfelder mit *"-Hinweiszeile entfernt; Stern bleibt am Feld,
  pro Feld als visually-hidden "(Pflichtfeld)" für Screenreader erklärt.
- Markenroter Fokus-Ring statt Weiß.
- Felder als sanfter Füll-Look: weichere Ränder (eigene --clr-field-*-Tokens),
  Erkennbarkeit über Fläche + Rand (WCAG 1.4.11 erfüllt).
- form.js: Submit-Button-Text wird je Formular korrekt wiederhergestellt.

Glas-Look global (tokens.css, components.css):
- Frosted Glass zentral: --glass-blur + --glass-shadow (Glow + Top-Sheen),
  eine Regel statt 28× wiederholt; Leaf-Karten mit Blur, Container nur Glow
  (kein Doppel-Frost). @supports-Fallback.
- Dunkleres Glas (--clr-glass-bg auf Schwarz-Tint), dezenter Card-Rand.

Hero-Fade (components.css):
- Unterer Auslauf nicht mehr mit grauem --clr-bg zugedeckt, sondern .hero__media
  per mask-image ausgeblendet → echter dunkler, texturierter Hintergrund scheint
  durch (schwärzer, durchsichtig, kein graues Band). Abdunklung auf reines
  Schwarz. Gilt für alle drei Hero-Varianten.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGzc6GhWhmLJt1jC2q1SRZ
2026-06-20 22:18:34 +02:00
fs
08f5a1ae9b Kontaktseite: eigene /kontakt mit Hero + Formular
Neue /kontakt-Seite (Hero + wiederverwendete contact-form-Komponente, ohne
Intro-Block). Behebt die bisher ins 404 laufenden "Kontakt aufnehmen"-CTAs.

- contact-form: optionales $intro + Rücksprung-Slug $return (Hidden-Feld)
- contact-submit: PRG-Redirect zur absendenden Seite, whitelist-validiert
  gegen routes.php (kein Open-Redirect)
- Verlinkt im Footer-Rechtsbereich und im Menü "Der Verein"

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGzc6GhWhmLJt1jC2q1SRZ
2026-06-20 21:14:28 +02:00
fs
23778324c7 Nav-Dropdown: Panel-Padding wirkt jetzt + gleichmäßiger Innenabstand
Das Submenu-Padding wurde stillschweigend vom globalen reset.css-Selektor
`ul[class] { padding: 0 }` (Spezifität 0,1,1) überschrieben, da
`.site-nav__submenu` nur (0,1,0) hat — die Hover-/Active-Pillen klebten
dadurch links/rechts am Panel-Rand. Selektoren auf
`.site-nav__list .site-nav__submenu` (0,2,0) gehoben (Haupt-Regel,
@supports-Glas, Mobile-Override), sodass das Padding ohne !important greift.
Innenabstand auf gleichmäßige 0.55rem rundum gesetzt — Pillen stehen
oben/unten/seitlich gleich dezent frei.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 21:08:59 +02:00
fs
adf142b18e Feinschliff: Navigation, Hero-Center, Personen-Karten & Tokens
Querschnitt-Verbesserungen ohne eigenes Feature:
- Navigation: Untermenü-Trenner (separator) + Mitmachen-Link, rote Akzent-Leiste
- Startseiten-Hero zentriert (align center, ohne Hero-CTAs); .hero--center-Styles
- person-card/person-grid: Kontakt-Layout, 3-Spalten-Raster (Startseite)
- jsonld: geo aus club.json; cta unterstützt #hash-Anker
- verein: hartcodierten Maps-Link/Inline-CTA entfernt, .facility zentriert
- tokens: Sepia-Farben für Timeline; base: globale p-max-width entfernt
- historie: Timeline um 2024/2025 ergänzt
- Icons link-45deg (Footer) und robot (meta) nachgereicht

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGzc6GhWhmLJt1jC2q1SRZ
2026-06-20 20:49:46 +02:00
fs
a3d64e7d91 Banner-Slider: Vor-/Zurück-Steuerung
Prev/Next-Navigationsbuttons für den Banner-Slider (carousel.js, generisch über
[data-carousel-prev/next]), zugehörige ad-banner-Steuerungs-Styles und Chevron-
Icons. Ohne JS bleiben die Controls ausgeblendet (progressive enhancement).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGzc6GhWhmLJt1jC2q1SRZ
2026-06-20 20:49:12 +02:00
fs
b40f36aee6 Turnen: Tab-Navigation für Gruppen mit vielen Trainingszeiten
group-schedule um eine Tab-Leiste (group-tabs) erweitert: bei Disziplinen mit
vielen Gruppen (z. B. Eltern-Kind-Turnen) werden die Gruppen als Tabs gruppiert
statt endlos gestapelt. JS (group-tabs.js) als progressive enhancement — ohne JS
bleiben alle Panels sichtbar. Begleitend Feinschliff an group-card/-list/schedule-row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGzc6GhWhmLJt1jC2q1SRZ
2026-06-20 20:48:39 +02:00
fs
1552f974aa Partner werden: Sponsoring-Argumente & Werbekanäle
Überarbeitete Seite /partner-werden mit Argument-Karten (sponsor-arguments)
und einer Übersicht der Werbekanäle (sponsor-channels) aus
data/partner-werden.json, inkl. zugehöriger Icons.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGzc6GhWhmLJt1jC2q1SRZ
2026-06-20 20:47:30 +02:00
fs
2c0594811c Mitmachen: Ehrenamtsseite mit Stellen-Filter
Neue Seite /mitmachen, die offene Ehrenamtsstellen aus data/mitmachen.json als
position-card-Komponente listet, mit clientseitigem Bereichsfilter
(position-filter.js, progressive enhancement) und JobPosting-Schema
(job_posting_schema(), employmentType VOLUNTEER). Route ergänzt.
Enthält zudem kleine person-card__contact-Stilanpassungen (gleiche CSS-Sektion).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGzc6GhWhmLJt1jC2q1SRZ
2026-06-20 20:47:13 +02:00
fs
6a612e52c3 Sportheim mieten: Buchungsseite mit Anfrageformular
Neue Seite /sportheimbuchung mit Bento-Layout (Fotos, Lage, Ausstattung) und
einem Buchungsanfrage-Formular nach Projektkonvention (form-field-Komponente,
Status-Region, Honeypot + ft-Token, Whitelist-Validierung, Rate-Limiting, PRG).
Versand via Brevo SMTP über app/actions/sportheimbuchung-senden.php, POST-Route
in index.php. form-field um min/max-Attribute erweitert (Personenzahl-Feld).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGzc6GhWhmLJt1jC2q1SRZ
2026-06-20 20:46:44 +02:00
fs
e7801ca6ee Spam-Schutz: Rate-Limiting, Link-Heuristik & Spam-Logging
Mehrschichtiger Formular-Schutz ohne externe Dienste, ergänzend zu Honeypot
und HMAC-Time-Trap:
- rate_limit_ok(): pro IP+Route (5/10 min), Tages-Cap (100/Tag), Token-Replay
- client_ip() / log_spam() (abgewiesene Versuche -> storage/logs/spam.log)
- Honeypot-Feld website -> company_url umbenannt (contact-/membership-form)
- Ratelimit-Hinweis in form.js
- storage/ratelimit/ (gitignored bis auf .gitkeep)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGzc6GhWhmLJt1jC2q1SRZ
2026-06-20 20:41:50 +02:00
fs
4b78285698 Neue Seiten: Mitglied/Partner werden, Sportheimbuchung & Ansprechpartner
Mitglied werden: Hub-Seite mit Online-Anmeldung (membership-form +
membership-submit-Action, Honeypot + Time-Trap), Abteilungswahl
(department-choice), Beitragstabelle (fee-table) und Turn-Aufnahmeantrag
als PDF-Download. Daten in data/mitglied-werden.json.

Partner werden & Sportheimbuchung: neue Seiten samt Routing und Navigation.

Ansprechpartner: echter Hero (Vereinsgelände), Bereiche aus dem group-Feld
der Single Source (Fußball / Turnen) zu zwei Sektionen gebündelt, Disziplin
als Karten-Überschrift. Fehlende Turn-Disziplin-Leads (Frisque, Lorz,
Kornatz, Trotzke) aus den Turnen-Unterseiten extrahiert und in
data/ansprechpartner.json ergänzt (allgemeine Turnabteilungs-Mail als
Kontakt). WhatsApp für alle Personen mit Telefonnummer. person-grid:
kompaktere Karten, Hairline unter der Bereichsüberschrift, 5 Spalten am
Desktop. Startseite auf die Haupt-Ansprechpartner je Bereich begrenzt.

Dazu: neue Icons (check-circle, download, file-earmark-text, whatsapp),
Anpassungen an Header/Footer/CTA/age-groups sowie tokens/layout/utilities.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RZsKTL5kRSG4KkK8w2LKkE
2026-06-19 11:16:10 +02:00
fs
185365dc79 Matchcenter: Spielplan, Ergebnisse & Tabellen (eigene Seite statt BFV-Widget)
Neue Unterseite /matchcenter ersetzt die alten BFV-Widget-Embeds (verstoßen
gegen CSP) durch eine self-hostete Lösung: Cron-Scraper holt die Daten aus der
öffentlichen BFV-Widget-JSON-API, schreibt data/matchcenter.json und lädt die
Vereinswappen lokal — Besucher kontaktieren nur unsere Domain.

- bin/matchcenter-sync.php: CLI/Cron-Scraper (Muster wie instagram-sync), JSON-API
  team/{id}/matches + competition/{compoundId}/table, Wappen via getLogo lokal,
  atomarer Write, Fail-safe. Auto-Saison: Tabellen-compoundId wird aus der
  Matches-API abgeleitet (team_id ist saison-stabil). Per-Team-Cache-Fallback bei
  Fetch-Fehlern, damit Sektionen nie leeren.
- Seite app/pages/matchcenter.php + Route + Nav-Eintrag (unter Fußball).
- Komponenten: match-slider (nächste Spiele), match-card, match-row, league-table
  (mit Auf-/Abstiegszonen), matchcenter-section (kicker-Layout: Tabelle 2/3 +
  Spiele 1/3), matchcenter-empty, crest (weißes Chip + Initialen-Fallback).
- carousel.js: banner.js zum generischen [data-carousel]-Antrieb verallgemeinert
  (Banner- und Match-Slider teilen ihn); crest.js: Logo-Fallback im Browser.
- helpers.php: sportsevent_nodes() für SportsEvent-JSON-LD.
- Icons calendar-event; config.example + .gitignore + CLAUDE.md ergänzt.
- Responsive geprüft (kein Seiten-Overflow 360–1280, Tabelle scrollt in der Karte).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192vTfGmGpoWeyUse4TuQFj
2026-06-19 10:57:14 +02:00
fs
ea622244db Turnabteilung: Übersicht + 5 Disziplin-Unterseiten im Bento-Layout
- Neue Übersichtsseite (Hub: Hero + Disziplin-Kacheln) sowie Unterseiten für
  Erwachsenenturnen, Eltern-Kind-/Kinderturnen, Step Aerobic/Fitness/Pilates,
  Trampolin und Wettkampfturnen. Inhalte 1:1 von der alten Seite, gepflegt in
  der neuen Single-Source data/turnen.json.
- Neue Komponente group-schedule im einheitlichen Bento-Look wie die
  Mannschaftsseiten: Dashboard mit Trainingszeiten, Kontakt-Kachel (mailto an die
  Turnabteilung) und Trainingsort; gestapelte/gerasterte Gruppen-Karten für
  Mehr-Gruppen-Disziplinen, rote Hinweis-Kachel (z. B. Warteliste).
- Routing (app/routes.php) und Navigations-Dropdown (data/navigation.json) für
  Turnen; zugehörige Styles in components.css.
- Themenbezogene, self-hostete Unsplash-Platzhalterbilder für die Kacheln.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
2026-06-19 10:33:03 +02:00
fs
d0ff8922f3 Banner-Slider: ungenutzte Platzhalter-SVGs entfernt
placeholder-1/2/3.svg + bg-sample.svg wurden seit den echten Slides
(Eigenwerbung + Breadcrumb) von keiner Banner-Konfiguration mehr referenziert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
2026-06-19 08:53:10 +02:00
fs
d82acc7f9c Banner-Slider: einspaltiges Layout, Logo oben links, Foto frei
Logo führt die Slide oben links an (statt großer Logo-Platte rechts); Inhalt
einspaltig, „Anzeige"/Status-Label als Pille unten rechts in der Fußzeile.
Damit gibt die rechte Hälfte das Hintergrundfoto frei — Motivwahl wird viel
einfacher. Logo nur rendern, wenn vorhanden; Wappen (Eigenwerbung) etwas größer.

Breadcrumb-Slide auf einen CTA reduziert: „Neue Website entdecken" → Startseite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
2026-06-19 08:52:21 +02:00
fs
d0d78d7a5c Banner-Slider: 1px-Durchschimmern an der Foto-Kante beheben
background-clip: padding-box auf den Foto-Varianten (--bg und house+bg),
damit das Hintergrundbild nicht unter den 1px-Rahmen (border-color:
transparent) gemalt wird und an der Kante durchschimmert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
2026-06-19 08:45:14 +02:00
fs
ede0258cd8 Banner-Slider: Crossfade-Übergang + per-Slide-Akzentfarbe
Folienwechsel als sauberes Crossfade (best practice): Slides im selben
Grid-Feld gestapelt → konstante Höhe ohne Sprung, ein-/ausgehende Folie
blendet über (Opacity + leichtes translateY). JS steuert die Sichtbarkeit
über .is-active statt hidden; ohne JS bleibt der hidden-Fallback (erste Folie).
Reduced-Motion über den globalen Kill-Switch instant.

Neue Slide-Prop "accent" (CSS-Farbe) überschreibt das Vereins-Rot der Buttons
pro Slide; Breadcrumb-Slide nutzt #6cbbbc. Button-Text wird aus der Akzentfarbe
dunkel abgeleitet (color-mix), da helle Akzente mit Weiß zu wenig Kontrast haben.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
2026-06-19 08:40:35 +02:00
fs
f294f1a9de Banner-Slider: Beispiel-Anzeige Breadcrumb Solutions
Zweite Slide als echte Sponsoren-Anzeige (Hero-Foto + Wortmarken-Logo,
self-hosted). Komponente erweitert um cta_secondary (Outline-Button) und
logo_light (weiße Logo-Platte für dunkle Firmenlogos). CTAs verlinken extern
(target=_blank, rel=sponsored) auf Lösungen + Über uns. Mit zwei Slides
aktiviert der Slider Auto-Rotation und Indikatoren.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
2026-06-19 08:34:39 +02:00
fs
88fceb67b1 Banner-Slider: Eigenwerbung-Slide "Hier könnte Ihre Werbung stehen"
House-Ad mit TSV-08-Wappen und echtem Foto (fussball-hero) statt der
Platzhalter-Slides: wirbt selbst für die Werbefläche, CTA führt intern aufs
Kontaktformular (#kontakt). Komponente um generische Props erweitert: variant
(CSS-Modifier), tag (Badge-Text) und cta.internal (interner Link ohne
target=_blank/rel=sponsored). Neue Styles: rote Glas-/Foto-Variante mit
gestricheltem "Werbeplatz frei"-Frame, rot-dunkler Verlauf für Lesbarkeit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
2026-06-19 08:27:57 +02:00
fs
c5f7b258ff Navigation: Dropdown-Untermenüs für Fußball und Verein
navigation.json gruppiert Fußball (Übersicht, Mannschaften, Jugend) und
Verein (Übersicht, Historie) als children. nav.php rendert Disclosure-Buttons
mit aria-expanded/-controls; ohne JS per CSS :hover/:focus-within bedienbar.
nav.js ergänzt Toggle, Escape (Untermenü zuerst, dann Overlay) und
Klick-außerhalb-schließt. Kontakt-CTA aus der Nav entfernt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
2026-06-19 08:13:10 +02:00
fs
3b6f630a72 Unterseiten: Fußball-Übersicht, Mannschaften, Jugend, Verein, Rechtliches
Neue Seiten + Routen: fussball, fussball/{erste-mannschaft,zweite-mannschaft,
damen}, jugendfussball, verein, impressum, datenschutz. Dazu die Komponenten
team, team-cards/-roster/-photo/-bento, faq/faq-contact, key-facts, age-groups,
cta-band, training-location und der contact-slider.

Daten in teams/fussball/jugend/verein.json; club.json um Vorstand, Bankdaten,
Datenschutz-Kontakt erweitert. person-grid bekommt $names-Filter, partner-grid
einen Intro-Stil. Seiten setzen $meta['schema'] via team_/page_schema.
Styles für alle neuen Komponenten in components.css.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
2026-06-19 08:13:04 +02:00
fs
a933b76c4f Banner-Slider auf der Startseite
Komponente banner-slider liest data/banners.json; banner.js übernimmt
Autoplay/Pause/Steuerung (progressive enhancement). Slider sitzt zwischen
Hero und Instagram-Feed. Bilder vorerst als Platzhalter-SVGs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
2026-06-19 08:12:43 +02:00
fs
5170dcb606 Icon-System: icon()-Helper, lokale Bootstrap-Icons, icons-add.php
Inline-SVG-Ausgabe via icon('name', 'klasse', 'label?') aus
public/assets/icons/. Dekorativ (aria-hidden) oder semantisch (role=img).
.icon-Basisklasse in utilities.css (1em, currentColor). bin/icons-add.php
holt neue Icons einzeln von icons.getbootstrap.com.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
2026-06-19 08:11:58 +02:00
c0616da257 Bild/Text-Sektionen im tinted-Band: padding-block auf clamp(4.5–7.5rem) erhöht
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:24:16 +02:00
5be2a1e6b9 Ansprechpartner: 3er-Reihen mit zentrierten Rest-Reihen (3+2-Layout via Flex)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:19:37 +02:00
3a518f4a65 Hero-Tagline: Outline-Stil entfernt, gefüllt mit 88% Weiß
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:13:40 +02:00
1cb7a21759 Hero Startseite: Editorial-Display-Variante — linksbündig, Kicker mit Akzentbalken, Titel bis 120px, Outline-Tagline, Seitenverlauf, Scroll-Cue
Enthält auch User-Tweak: hero--compact auf 60vh/560px

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:11:11 +02:00
259e209692 Back-to-top-Button: erscheint nach 1,5 Viewporthöhen, Fokus-Management, reduced-motion, 48px-Ziel
Enthält auch User-Upgrade: JSON-LD als @graph mit seitenspezifischen Extra-Knoten

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:00:03 +02:00
21e2789507 Timeline: Jahreszahlen weiß, Jahres-Nav auf fs-650 (Coolvetica-Lesbarkeitsregel)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 22:54:49 +02:00
efa47d4f60 Vereinshistorie-Seite: kompakter Hero, interaktive Museums-Timeline (Scroll-Reveal, Jahres-Sprungnav), Originalbilder unverändert
routes.php enthält auch die fussball-Route (User-WIP, Seite existiert)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 22:50:15 +02:00
1ee08b932d tinted-Band generalisiert (.section--tinted) und Ansprechpartner-Sektion hervorgehoben
Enthält auch User-Erweiterung: person-grid mit Abteilungs-Filter/Title/Intro-Props

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 22:41:57 +02:00
5c4b45a5fe Stats-Band: abgedunkeltes Vereinsgelände-Foto mit weichen Verlaufskanten statt schwarzem Streifen, Zahlen auf fs-900
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 22:39:09 +02:00
297e4a1546 tinted-Variante: durchgehendes Vollbreite-Band statt Karte; aufeinanderfolgende Bänder verschmelzen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 22:35:24 +02:00
f55bd40a1e Split-Sektionen: tinted-Variante (sanft aufgehelltes Panel) für Historie + Sportheim
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 22:31:58 +02:00
51149e8c5f Platzhalter-Avatar: AdobeStock-Frauenavatar (von alter Seite, lokal optimiert 11 KB) statt Initialen-Fallback
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 22:28:51 +02:00
d757fe1043 Person-Card: geschlechtsneutraler Initialen-Avatar als Fallback (statt männlichem Foto-Platzhalter), platzhalter.jpg entfernt
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 22:24:45 +02:00