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
Neue Single-Source-Zeilen für data/anzeigen.json und data/veranstaltungen.json,
Vorlage der Stadionzeitung als Blöcke mit Lücken statt vier Anzeigen-Gruppen,
Doppelseiten-Regel jetzt maschinell. Dazu die Fallen, in die wir gelaufen sind:
Kontext-Regeln setzen Custom Properties und kein Layout (… .crest), --header-space
statt --header-h, [hidden] gegen .lightbox, QR-Codes tragen die base_url fest
eingebacken.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
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
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
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>
Audit aller 23 indexierbaren Routen (lokaler Crawl) und Fixes:
- Descriptions auf ~130-160 Zeichen; Turnen-Disziplinseiten hatten den
kurzen Hero-Teaser als Description, jetzt eigene SERP-Texte
- Titles: team-erste gekürzt, sportheimbuchung ohne doppelten Brand
- h1->h3-Sprünge behoben: visually-hidden h2 aus JSON-Section-Titeln
(sportheimbuchung, partner-werden), sichtbarer choice.title auf
mitglied-werden; historie bekommt Breadcrumb-Schema
- Neuer Helper img_intrinsic_attrs() liefert width/height aus der
Bilddatei (PNG via getimagesize, SVG via viewBox) für Partner-Grid-
und Banner-Slider-Logos — kein Layout-Shift mehr
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Versand läuft jetzt über das KAS-Postfach noreply@tsv08kulmbach.de
(w01ca75d.kasserver.com:587, STARTTLS) — ein Drittanbieter weniger,
SPF der Domain deckt den Server bereits ab. bin/smtp-test.php prüft
Verbindung, TLS und Login ohne eine Mail zu versenden (Debug bewusst
aus, damit keine Credentials im Terminal landen).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
SSoT-Tabelle ergänzt um teams/fussball/jugend/verein/banners.json, damit
Mannschafts- und Seiteninhalte aus der Quelle statt aus Templates kommen.
Per-Page-Meta-Abschnitt dokumentiert das $meta['schema']-Muster und die
*_schema()-Helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
- Ordnerstruktur mit public/-Docroot, Deny-.htaccess für app/bin/config/data/storage
- CLAUDE.md mit Leitplanken (self-hosted only, Single Source of Truth, Component-first)
- Design-Tokens aus alter Seite extrahiert (Akzent #e20612, Coolvetica/Abel als woff2)
- Front Controller mit Routen-Register, Sitemap, Canonical, JSON-LD (SportsClub)
- Startseite: Hero, Instagram-Feed (lokaler Cache), Historie/Sportheim, Stats, Partner, Kontakt
- Kontaktformular via PHPMailer/Brevo mit Honeypot + HMAC-Time-Trap (kein reCAPTCHA)
- bin/instagram-sync.php: Scraper mit Strategie-Kette, flock, atomarem Cache, lokalen Bildern
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>