- Neue generische Live-Seite `aufruf` (erste Nutzung: Sportfest-Helferaufruf):
Komponente, Dispatcher, Skript-Einbindung (Planung im Aufschlag, QR aus
aufruf.link, Feld-Carryover), CSS, optionales kleines Poster rechts.
- Ausgabe 2026-08-15-tsv-koednitz als ENTWURF: Vorwort (Benedikt, Revanche
dezent), Interview (Ali, 3 Fragen), Sportfest-Helferseite mit Kontakten aus
ansprechpartner.json; Rückseite/Cover wie Hutschdorf wiederverwendet.
- Interview-Frage größer/prominenter gesetzt.
- Sportfest-Plakat-Anzeige deaktiviert (redundant zur Helferseite) -> 40 Seiten.
Sync-Dateien (instagram/matchcenter) bleiben gitignored, Snapshot wird beim
Veröffentlichen auf dem Server eingefroren.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFnFYLm2MoBskyk9mc84ZW
Drei Karten nach Zielgruppe (Kinder+Erwachsene Mo, Kinder ab 5 Do,
Kinder ab 10 und Fortgeschrittene Do) mit Tag/Zeit und Trainerteam je
Session, Schnupper-Hinweis auf "nach Absprache" umgestellt, Fußnote zu
den Schulferien. group-schedule bekommt zwei optionale Props
(contactEmail/contactPhone) fuer Tinas persoenlichen Kontakt in der
Kontaktkachel (tel:-Muster wie person-card, keine neue CSS-Regel).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXDvgRDHQ9iGjs4KCXykEj
Eva Wünsch aus der Turnabteilung am 31.07.2026: „Der Sternchentext müsste
eigentlich sein, dass die Person mit dem Sternchen Übungsleiter ist. Von daher
müsste vor den Namen 'Trainerteam' stehen. Einige sind auch nicht ÜL."
Ich hatte davon nur die Fußnote erledigt. Das Label blieb „Übungsleitung" und
damit stand die falsche Aussage weiter auf der Seite: neun namentlich genannte
Personen liefen unter „Übungsleitung", vier davon sind es. Das ist keine
Formulierungsfrage, die Seite behauptete etwas über echte Menschen.
Label jetzt „Trainerteam:" an beiden Stellen (Trainingszeit und Gruppe), der
Stern bleibt die Auszeichnung und die Fußnote erklärt ihn. So stand es auch auf
der alten Seite: „TRAINERTEAM (* = ÜBUNGSLEITER)". Ein Kommentar an der Stelle
sagt, warum es nicht zurückbenannt werden darf.
Dabei fiel ein zweiter Datenfehler auf: die alte Seite führt „Annelie Frisque*,
Eva Wünsch*", bei uns stand Eva Wünsch ohne Stern. Sie ist Übungsleiterin.
Geprüft: „Übungsleitung" kommt auf keiner der sechs Turn-Seiten mehr vor, die
Grashüpfer-Zeile lautet jetzt Zeichen für Zeichen wie in Evas Screenshot. Die
Fußnote erscheint nur in Sektionen mit mehreren Gruppen — nachgezählt, dass
beide Disziplinen mit Sternchen-Namen mehrere Gruppen haben, die
Einzelgruppen-Disziplinen tragen keine Sterne.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Bei zwei Gruppen verstecken Tabs mehr Inhalt als sie ordnen, und die offene
Kartenreihe hält den Bento-Eindruck der Seite (Entscheidung Felix). Das
Kinderturnen bleibt bei Tabs, dort sind es drei textreiche Gruppen — die
Flamingos-Karte allein ist am Handy 2142px hoch.
.group-list--stacked lag vollständig im CSS, wurde aber von KEINEM PHP
ausgegeben: totes CSS aus der Zeit vor den Tabs, das heute morgen sogar noch
meine minmax(0, …)-Korrektur mitbekommen hat, ohne dass es jemand sehen konnte.
Jetzt ist es lebendig. Dritter toter Rest heute: "layout": "blocks" in
data/turnen.json las ebenfalls niemand, ist raus.
Der Karteninhalt steht dafür jetzt einmal statt zweimal. Er wird in zwei
Fassungen gebraucht (Tab-Panel mit role/aria und gestapelt ohne) und ist
deshalb eine Closure geworden — eine zweite Kopie wäre die Stelle, an der beide
in einem halben Jahr auseinanderlaufen.
Die Überschrift ist visually-hidden, nicht entfernt: sonst hätte die Sektion
keinen zugänglichen Namen und die Überschriften-Gliederung eine Lücke. Dasselbe
Muster wie $title_hidden in team-cards. Damit braucht die Seite auch kein
group-tabs.js mehr, das ich zwei Commits vorher erst dazugeschrieben hatte.
Zwei Nachbesserungen, beide gemessen:
- Die versteckte Überschrift hinterließ ein Loch. Zwischen Dashboard und erster
Karte standen 160px (80 unten von der einen Sektion, 80 oben von der anderen),
in denen nur nackte Hintergrund-Textur zu sehen war — vorher füllte die
Überschrift diesen Raum. .group-list-section--tight nimmt das obere Padding
weg, jetzt 80px.
- Die gestapelten Karten hatten feste 40px Innenabstand; auf --pad-card-lg
umgestellt, dieselbe Korrektur wie heute morgen beim Tab-Panel. Am Desktop
unverändert 40px, am Handy 28px.
Nicht behoben, weil kein Fehler: die Hintergrund-Textur wirkt in leeren Flächen
weich. Sie ist 1920x2545 groß und wird bei 1478px Fensterbreite auf Faktor 0,77
VERKLEINERT, ist also nicht unscharf gerechnet. Sichtbar wurde sie durch den
Leerraum plus background-attachment: fixed, das sie beim Scrollen stehen lässt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Die Flamingos sind organisatorisch vom Kinderturnen zum Wettkampfturnen
gewechselt. Felix hat dazu die Fassung geschickt, die die Turnabteilung selbst
auf der alten Seite gepflegt hatte — dabei kamen sieben Abweichungen heraus,
alle korrigiert:
- Die zweite Gruppe heißt "Leistungsturnen", nicht "Wettkampfturnen" (so hieß
bei uns die Gruppe wie die Disziplin).
- Reihenfolge: Flamingos zuerst, Leistungsturnen danach.
- Den Flamingos fehlte eine Trainingszeit: Montag gibt es ZWEI Blöcke
(18:00–19:00 und 19:00–20:00), wir hatten nur den ersten.
- Ansprechpersonen sind pro Gruppe verschieden (Flamingos: Theresa Diener,
Leistungsturnen: Franziska Trotzke). Dafür ist `contact` neu an einer Gruppe.
Die Kontakt-Kachel oben kann nur EINE Person nennen und wäre für die andere
Gruppe falsch — deshalb ist `lead` bei dieser Disziplin jetzt leer, die Kachel
trägt nur noch die Abteilungs-Mail. So war es auf der alten Seite auch: dort
gab es keine Ansprechperson für die Disziplin, nur je Gruppe.
- Theresa Diener steht im Trainerteam vorn und ohne Stern (bei uns hinten).
- Intro, Trainingsinhalte, Wettkämpfe und der Hinweis waren gekürzt; jetzt im
Wortlaut der Abteilung. Den Gedankenstrich in "(Kür modifiziert –
Leistungsklasse 3)" ersetzt ein Komma (Sprache & Ton).
- Die Fußnote sagte "* So im Trainingsplan der Abteilung gekennzeichnet" — eine
Verlegenheitsformulierung, weil wir die Bedeutung nicht kannten. Die alte
Seite schreibt "* = ÜBUNGSLEITER", also jetzt "* Übungsleiterinnen und
Übungsleiter" (Paarform, Haus-Stil).
`level` bleibt "Turnen · fortgeschritten" ohne "Alter nach Absprache" — das
hatte Felix am 31.07.2026 ausdrücklich aus dem Chip genommen.
Zwei Folgen, die sonst still kaputtgegangen wären: die Seite hatte eine einzige
Gruppe ohne Beschreibung und lief damit im Einzelgruppen-Layout. Mit zwei
Gruppen, davon eine mit Beschreibung, rendert group-schedule eine Tab-Leiste —
die braucht group-tabs.js, das die Seite nicht lud (tote Tab-Knöpfe). Und drei
Flamingos-Trainerinnen tragen ein Sternchen, die erklärende Fußnote wurde auf
dieser Seite nicht übergeben (drei unerklärte Sterne).
Geprüft: Tabs "Flamingos" und "Leistungsturnen", Skript geladen, Fußnote da,
Flamingos auf /turnen/eltern-kind-turnen nicht mehr erwähnt, kein Überlauf bei
390px, alle sechs Turn-Seiten 200.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Im Menü stand „Eltern-Kind-Turnen", die Seite heißt „Eltern-Kind- &
Kinderturnen" — das Kinderturnen fehlte also im Menü, obwohl Eltern genau
danach suchen. Beim Prüfen fielen zwei weitere Abweichungen auf, alle drei
sind jetzt gleich:
Eltern-Kind-Turnen → Eltern-Kind- & Kinderturnen
Fitness und Pilates → Fitness & Pilates
Trampolin → Trampolinturnen
Punkte („…") braucht es dafür nicht: das Dropdown hat min-width und
white-space: nowrap, das Panel wächst also mit dem längsten Punkt statt zu
beschneiden. Ein abgeschnittener Menüpunkt wäre auch die schlechtere Lösung,
weil man dann nicht mehr liest, wohin der Link führt.
Es war aber knapper als es aussah. Gemessen mit dem längsten Punkt (187,6px):
ruhend 190,0px Platz, im Hover UND auf der aktiven Seite nur 186,0px, weil
padding-left dort von --space-s (16px) auf --space-m (20px) wächst. Das Panel
wäre beim Rübergehen um zwei Pixel gewachsen und auf dieser Seite dauerhaft
breiter gewesen — sichtbar als Flackern, weil es mittig sitzt und sich nach
beiden Seiten schiebt.
Behoben mit einem Wert: min-width 15rem → 15,5rem. Alle drei Panels bleiben
jetzt in jedem Zustand bei 248px, mit 8,4px Luft statt 1,6px Fehlbetrag. Auch
bei 1250px Viewport (der schmalsten Breite mit Desktop-Menü) liegt kein Panel
am Rand. Die Rechnung steht als Kommentar über der Regel, damit der nächste
längere Menüpunkt nachgemessen wird.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Der Anmelde-Vorgang der Abteilung (erst Platz anfragen, dann Aufnahmeantrag)
stand nirgends auf der Seite. Auf der alten Seite gab es dafür einen Block
„ANMELDUNG" mit zwei Knöpfen.
Erst als eigene Sektion unter den Beiträgen gebaut (cta-band), auf Wunsch von
Felix wieder entfernt und stattdessen als eigene FAQ-Frage umgesetzt. Zwei
Sektionen mit denselben Knöpfen auf einer Seite schwächen beide Stellen, und
man weiß nicht mehr, welcher der richtige Weg ist. Deshalb ist auch
overview.cta ganz aus data/turnen.json raus statt unbenutzt liegen zu bleiben —
genau so ein toter Block hat heute schon einmal in die Irre geführt (die Notiz
„NOCH unbenutzt" stand monatelang im Docblock von turnen.php).
faq.php bekommt dafür ein optionales ctas-Feld pro Eintrag, gerendert über die
bestehende cta-Komponente, also keine neue Knopf-Variante. .faq__actions
bricht um, weil zwei Knöpfe auf einem schmalen Handy nicht nebeneinander passen.
Die Antwort nennt beide Wege bewusst AUCH in Worten: faq_schema() liest nur q
und a, Knöpfe landen nie im FAQPage-Schema. Stünde in der Antwort nur „so
geht's" plus zwei Knöpfe, hätte Google eine Antwort ohne Ziel. Nachgeprüft:
sechs Fragen im Schema, der neue Antworttext ist HTML- und Markup-frei.
Gemessen bei 390px: kein Überlauf, Knöpfe 130x44 und 193x44 (Trefferfläche
erfüllt). Die fünf Turn-Unterseiten hatten als einzige von 105 PHP-Dateien
keinen Zeilenumbruch am Dateiende, das ist mitkorrigiert.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Rückmeldung der Turnabteilung. Der Austritt-Block auf /turnen nannte bisher nur
die Schriftform und die Frist zum 30. September, nicht aber, wohin die Kündigung
geht.
Die Adresse steht wie überall ausschließlich in club.json (email_turnen), der
Satz in turnen.json markiert ihre Stelle nur mit {mail} — dasselbe Muster wie
der Material-Aufruf auf /historie. Ersetzt wird nach inline_links_html(), damit
der Fließtext escaped bleibt und nur der Platzhalter zu einem mailto-Link wird.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWoqDYuANFbJDGFQAgaRdT
Info von Eva aus der Trampolin-Gruppe: neue Kinder können ab September
(Schulbeginn) gerne schnuppern kommen. Die Info stand nirgends.
Rot wäre dafür falsch gewesen. Die Farbe bekommt deshalb eine Bedeutung:
--stop (gefüllt rot) heißt „geht nicht", --info (Glas-Fläche, Rot nur im Icon)
heißt „geht". Eine Einladung in Alarmrot liest sich wie eine Absage, und wenn
alles rot ist, stumpft Rot ab und der echte Wartelisten-Stopp bei „Tiger" und
„Hühner" verliert seine Wirkung. --info ist ab jetzt der Default.
Damit steht der Hinweis an drei Stellen, also greift die Bedingung aus dem
letzten Commit („kommt ein dritter Ort dazu, gehören die gemeinsamen
Eigenschaften in eine geteilte Klasse"): neu app/components/notice.php als
einzige Render-Stelle statt dreimal dasselbe Markup. Die beiden bestehenden
Klassen schrumpfen auf das, was sie wirklich beitragen —
.turnen-dash__notice nur noch grid-area, .contact__notice nur noch die
Formularbreite. Die tote .turnen-dash__notice-icon-Regel ist raus, der
CSS-Abschnitt neu sortiert (notice als eigener Block hinter turnen-dash statt
mitten hinein).
Text, Tonlage und Icon stehen wie gehabt in data/turnen.json bei der
Disziplin, nicht im Template. Kinderturnen trägt jetzt explizit
notice_tone: stop, Trampolin nichts und ist damit neutral.
Das Grid-Template für --notice + --single gab es schon, an beiden
Breakpoints — die Trampolin-Seite hat eine Gruppe und wäre sonst
auseinandergefallen.
Gemessen bei 390px: kein Überlauf auf allen drei Seiten, Trampolin-Fläche
rgba(0,0,0,0.22) (Glas), die beiden anderen #e20612. Zwölf Seiten geprüft,
alle 200. preflight ohne neue Meldungen.
OFFEN, mit Eva oder Tina zu klären: der zweite Satz („Schreib uns davor
einfach kurz eine Nachricht") ist von mir, Eva hat ihn nicht gesagt. Wenn die
Kinder einfach zum Training kommen dürfen, gehört er weg. Und die Einladung
steht bisher nur auf /turnen/trampolin, nicht auf der Übersicht /turnen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Die Turnabteilung (Maxi) beantwortet laufend Anfragen nach einem Wartelistenplatz
für „Tiger" und „Hühner", den es nicht gibt. Auf der alten Seite stand der Hinweis
direkt über dem Anfrageformular, hier fehlte er.
Schlimmer: an der Stelle stand das GEGENTEIL. Der bisherige Hinweis lautete
"Aktuell sind alle Kinderturngruppen belegt. Bei Interesse setzen wir sie gerne
auf die Warteliste." — für alle Kindergruppen, also auch für die beiden, die
keine Liste führen. Der Satz hat einen Teil der Anfragen vermutlich erst erzeugt.
Er ist nicht gelöscht, sondern auf die übrigen Gruppen eingegrenzt und aufs Du
umgestellt (Haus-Stil; vorher stand dort klein "sie").
Der Text steht genau EINMAL, in data/turnen.json unter
disciplines.eltern-kind-turnen.notice + notice_title. Zwei Stellen lesen ihn:
die rote Kachel auf /turnen/eltern-kind-turnen (gab es schon) und neu ein Block
direkt über dem Formular auf /turnen. Ändert die Abteilung die Lage, ist das eine
Zeile in einer Datei.
Umsetzung:
- notice_title ist neu, weil eine rote Fläche ohne Überschrift wie eine
Fehlermeldung liest. Abel statt Coolvetica, der Schnitt ist bei ~19px in
Versalien unleserlich (CLAUDE.md, Design).
- Der Block steht AUSSERHALB des <form> und damit außerhalb der
role="status"-Region: ein dauerhafter Hinweis, keine Rückmeldung zum Absenden.
In der Live-Region läse ihn ein Screenreader bei jeder Formularmeldung erneut
vor. Kein role="alert", er steht schon beim Laden da.
- Überschrift als h3 unter der h2 des Intro-Blocks, damit die Folge nicht springt.
- .contact__notice übernimmt bewusst die Optik von .turnen-dash__notice, damit
beide Stellen als derselbe Hinweis erkennbar sind. Weiß auf --clr-accent
erreicht 4,92:1 und trägt damit Fließtext (WCAG 1.4.3). Kommt ein dritter Ort
dazu, gehören die gemeinsamen Eigenschaften in eine geteilte .notice-Klasse;
für zwei wäre das verfrüht.
Gemessen: Hinweisbreite gleich Formularbreite (720px bei 1280, 347px bei 390),
kein Überlauf. preflight ohne neue Meldungen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Rückmeldung der Turnabteilung mit Screenshot: auf dem iPhone war im
Flamingos-Panel der Fließtext rechts abgeschnitten.
Gemessen bei 390px mit aktivem Flamingos-Tab (die anderen Panels sind
ausgeblendet, ihre Chips haben Breite 0 — deshalb fiel es beim Prüfen der Seite
nicht auf): Dokument 442px bei 379px Bildschirm, also 63px Überlauf; der Chip
393px breit in einer 345px-Karte.
Ursache war nicht die Textlänge allein, sondern .group-card__level mit
`white-space: nowrap` UND `flex-shrink: 0`: der Chip konnte seinen Text nicht
brechen und auch nicht schrumpfen, seine Mindestbreite spannte die ganze Karte
auf. Von vier level-Chips war nur dieser betroffen (47 Zeichen gegen 17 bis 25).
Zwei Änderungen:
- Inhalt wie gewünscht gekürzt: "Turnen · fortgeschritten · Alter nach Absprache"
→ "Turnen · fortgeschritten".
- Sicherheitsnetz im CSS: nowrap raus, max-width: 100% rein. Ein Chip trägt
Information, deshalb umbrechen statt mit … kürzen. flex-shrink: 0 bleibt, es
hält den Chip neben einem kurzen Gruppennamen in Form.
Gegenprobe, damit der Fix nicht nur am kürzeren Text hängt: mit dem LANGEN Text
und dem CSS-Fix bleibt das Dokument bei 379px, der Chip bricht auf zwei Zeilen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Drei Dinge, zwei aus deinem Feedback und eines von mir.
1) Der Einleitungstext klebte ohne Abstand an der Überschrift und lief über die
volle Breite, über 120 Zeichen je Zeile ("schaut weird aus"). Ursache: ich
hatte <p class="lead"> geschrieben — eine Klasse, die es im Projekt gar nicht
gibt, der Absatz war also völlig unformatiert. Jetzt das etablierte Muster
(.membership-signup__head, instagram, partners): zentrierter Kopf, 60ch breit,
Luft nach unten. Anmerkung im Code: dasselbe Kopf-Muster steht damit zum
vierten Mal fast gleich in components.css — ein gemeinsames .section__head
wäre die Aufräumung, aber ein eigener Umbau.
2) Preise von 28px auf 34px. Sie tragen die Hervorhebung allein, seit die Farbe
weg ist, und sind die Information, auf die man in einer Beitragstabelle sieht.
3) Selbst aufgefallen: die Überschrift "Austritt" lag in .prose und rendert dort
mit 21px Abel — exakt Fließtextgröße, also gar keine Auszeichnung. In
Impressum und Artikeln ist das richtig, deshalb .prose nicht angetastet,
sondern eigene Klassen für den Hinweis: 28px Coolvetica (die Untergrenze für
Versalien) mit derselben dezenten Sperrung wie die Kartentitel.
Außerdem: data/turnen.json ist wieder in seiner ursprünglichen Formatierung.
Mein JSON-Schreiber hatte im vorigen Commit die kompakten Arrays aufgeklappt
("widths": [640, 1000, 1600] wurde vierzeilig) — inhaltlich identisch, aber 371
Diff-Zeilen für eine Umbenennung. Jetzt textuell ersetzt statt neu geschrieben:
17 Diff-Zeilen, Inhalt per Vergleich identisch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Rückmeldung der Turnabteilung: Step Aerobic findet nicht mehr statt.
Wichtig war der Befund vor der Änderung: die Seite bündelt DREI Angebote —
Pilates, Fitness & Stretching und Step-Aerobic. Ein pauschales "die Card raus"
hätte Pilates und Fitness mitgenommen. Entfernt ist deshalb nur die eine Gruppe
(Donnerstag 19:30, Heike Wahler); Pilates und Fitness & Stretching bei Karola
Lorz laufen unverändert weiter. Heike Wahler stand nur in dieser Gruppe und
erscheint damit nicht mehr auf der Website — das ist die einzige Nebenwirkung,
die jemandem auffallen könnte.
Umbenannt in "Fitness & Pilates" an allen zehn Fundstellen: navigation.json,
turnen.json (Disziplin-Schlüssel, Hero-Titel, drei Übersichtstexte),
ansprechpartner.json, news.json, turnen.php (Karte, Alt-Text, Meta) und die
Seiten-Datei selbst.
URL mitgezogen wie besprochen: turnen/step-aerobic-fitness-pilates →
turnen/fitness-pilates, samt Seiten-Datei und den drei Bilddateien (git mv,
Historie bleibt). Der alte Slug war in der Sitemap und ist indexiert, deshalb
eine 301 in public/.htaccess — VOR dem Front Controller, sonst würde index.php
den Pfad als unbekannten Slug abfangen und 404 rendern.
Geprüft: /turnen/fitness-pilates liefert 200 mit nur noch zwei Gruppen,
Sitemap führt ausschließlich den neuen Slug, Navigation, Übersichtskarte und der
News-Artikel verweisen darauf, keine Referenz auf die alten Bildnamen mehr.
Der alte Slug gibt lokal 404, weil php -S keine .htaccess liest; die 301 greift
auf Apache.
Nicht geändert: das Wort "Step-Aerobic" in der CLAUDE.md-Sprachregel. Dort ist
es ein Beispiel für orthografisch nötige Bindestriche und bleibt als solches
richtig, auch ohne den Kurs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Das Cover blieb am iPhone leer, während Chrome am Desktop es zeigte. Ursache
war kein fehlendes Bild: das freigestellte Spielerfoto hatte 2584x5093 Pixel und
12,3 MB. iOS Safari verwirft Bilder, deren dekodierte Größe sein Budget
übersteigt — hier gut 53 MB, und auf einer Ausgabe liegen 86 Bilder.
Dargestellt wird das Foto mit 98cqi Höhe, bei 480px Seitenbreite also etwa
238x470 CSS-Pixel. Es gibt jetzt 320/640/1000 als PNG mit erhaltener
Transparenz (0,2 / 0,8 / 1,9 MB); sizes nennt die echte Breite, sonst zöge ein
Handy mit DPR 3 die größte Variante. Das Original ist gelöscht.
Dafür kann resize_image_variants() nun Alpha erhalten ($alpha = true schreibt
PNG statt JPG) und die img-Komponente eine Endung ($image['ext']). Der Default
bleibt JPG, weil bestehende Aufrufer PNG-Quellen absichtlich zu JPG verkleinern.
bin/stadionzeitung-add.php erzeugt die Varianten künftig selbst. Dort stand
"unverändert kopieren, Felix liefert bereits ein web-taugliches Foto" — genau
diese Annahme ist gerissen.
Außerdem: smtp.password stand in der Produktiv-Config noch auf dem Platzhalter
POSTFACH_PASSWORT (von preflight auf dem Server gefunden, einziger harter
Fehler). Übernommen wurde das Passwort aus der Dev-Config, mit dem der Versand
am 27.07.2026 verifiziert wurde; Host, Postfach und Empfänger sind identisch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Beim ersten Deploy blieben die Slides des Banner-Sliders schwarz. Ursache war
kein fehlendes Bild, sondern die CSP: public/.htaccess setzt
default-src 'self' ohne style-src, der Browser verwirft damit jedes
style-Attribut — und die Komponente lieferte Bild und Akzentfarbe genau so
(style="--banner-bg: url(…); --banner-accent: #6cbbbc"). Lokal war das
unsichtbar, weil php -S keine .htaccess liest.
Der Hintergrund ist jetzt ein <img class="ad-banner__bg"> über die bestehende
img-Komponente (CSP erlaubt img-src) und bringt srcset mit: das Handy lädt die
640er statt der 1600er, die erste Slide lädt eager mit fetchpriority. Der
dunkle Leseverlauf liegt auf ::before statt im background-image.
Die Akzentfarbe kann als beliebiger Hex-Wert nicht ohne Inline-Style in die
Seite. Sie steht deshalb als Token in tokens.css (wo Farben laut CLAUDE.md
hingehören), components.css bildet den Namen darauf ab, und banners.json nennt
nur noch "accent": "breadcrumb".
bin/preflight.php bricht ab, wenn unter app/ wieder ein style-Attribut
auftaucht — der Fehler wäre sonst erneut erst in Produktion sichtbar. Beim
Bauen dieser Wache zweimal selbst hineingetappt: ein Muster mit [^>]* bricht an
jedem schließenden PHP-Tag ab, und die Zeichenfolge dieses Tags beendet den
PHP-Modus auch in einem //-Kommentar (preflight gab ab dort seinen eigenen
Quelltext aus, php -l meldete nichts).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
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 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
Umbenennung durchgängig: „Damenmannschaft" → „Frauenmannschaft" und
„Damenfußball" → „Frauenfußball". Der Abteilungsname ist ein Zuordnungs-
schlüssel und musste auf allen Seiten gleichzeitig wandern — teams.json
(department + contact_department), ansprechpartner.json, die Abteilungslisten
auf Start- und Fußballseite, die Auswahl im Kontaktformular UND die Whitelist
in contact-submit.php. Geprüft: Ansprechpartner erscheinen weiter auf allen
vier Seiten, Formular-Optionen und serverseitige Whitelist sind deckungsgleich.
URL /fussball/damen und der Datenschlüssel „damen" bleiben absichtlich — eine
Slug-Änderung würde bestehende Links und die Suchmaschinen-Historie brechen,
sichtbar ist der Slug nirgends.
Die zweite Mannschaft ist in matchcenter.teams auskommentiert (nicht gelöscht):
Die BFV-Mannschaft hinter der ID lief 25/26 als „(SG1) TSV Melkendorf I/
TSV 08 Kulmbach II" und liefert in 26/27 nur noch „TSV Melkendorf" — also
Spiele ohne den TSV 08, samt Tabelle, die Melkendorf als eigenen Verein
markiert. Begründung steht bei der Zeile; nach Klärung im Verein reicht ein
Entfernen des Kommentars, ggf. mit neuer team_id. Die Seite
/fussball/zweite-mannschaft und ihr Menüpunkt bleiben unberührt, sie hängen
nicht am Matchcenter. Die Matchcenter-Description nannte die Zweite noch und
ist entsprechend neu gefasst.
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>
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
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
Ü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
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
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
Alle Unterseiten-Heros auf ein einheitliches Muster gebracht — nur title +
text, dazu compact und align bottom-left. kicker und tagline entfernt
(team-erste/zwei/damen, jugendfussball, matchcenter, ansprechpartner,
mitglied-werden), historie von zentriert auf bottom-left. Der große
Startseiten-Hero (display) bleibt unverändert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGzc6GhWhmLJt1jC2q1SRZ
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
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
- 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
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
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
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
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
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
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