Optionale Live-Seite hinter dem Ergebnis-Rückblick mit Fotos vom letzten
Spiel. bin/stadionzeitung-add.php liest sie aus <ordner>/galerie/, Kopf und
Alt-Texte sind Chat-Pflege und hängen an source. Preflight meldet fehlende
Galerie-Dateien.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCMNXcfkM9RcaVjqoJjRsz
Ergebnisse laufen als Anzeigetafel (Wappen und Name je Seite, Ergebnis gross
in der Mitte) statt als Listenzeilen. Verworfen und im CSS begruendet: Tore
am rechten Blattrand, getoente Karten je Spiel, roter Strich unter der
Mannschaftszeile, graue Verlierer-Ziffern. Vereinsnamen einzeilig, sonst
ausgepunktet.
Die Gegnerseite bekommt ein symmetrisches Duell, eine aus den Tabellenwerten
abgeleitete Aufmacher-Zeile, den Vergleich als Balkenpaar aus der Mitte und
einen Tabellenausschnitt ueber die geteilte league-table-Komponente. Die
Balkenlaengen kommen aus Klassen in 5-Prozent-Stufen, nie aus einem
style-Attribut (die CSP verwirft inline styles stillschweigend).
Das Mitglied-werden-Poster sagt jetzt "Werde Teil unseres Vereins".
Alle geaenderten Seiten auf A5-Hoehe nachgemessen, kein Ueberstand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv9SGDu3m3diiPrW9Fmj3v
Gegnerseiten lassen sich jetzt ueber `gegner_teams` auch ohne Veranstaltung
fuer weitere Mannschaften anlegen (Fenster zwei Tage vor bis drei Tage nach
dem Ausgabedatum, auch auswaerts), und `opponent_short` liefert den
gedruckten Anzeigenamen, waehrend `opponent` die BFV-Schreibweise fuer den
Namensvergleich behaelt.
Dazu die Ausgabe zum Heimspiel gegen Wirsberg/Kupferberg als Entwurf samt
Seitenbildern. Die gemeinsamen QR-Codes sind neu erzeugt (die vendorte
Bibliothek arbeitet nicht reproduzierbar, das Maskenmuster wechselt).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv9SGDu3m3diiPrW9Fmj3v
bin/instagram-sync.php lieferte seit dem 03.09.2026 bei jedem Lauf "Keine Posts
ermittelbar". Von einem privaten Anschluss gegengeprueft: feed/user und
web_profile_info antworten mit 401 require_login, das Profil-HTML traegt keine
Beitragsdaten mehr, auch /embed/ liefert nur noch eine App-Huelle. Es lag also
nicht an der Server-IP, und der Scraper ist geloescht, damit ihn niemand
wiederbelebt. Graph API (Meta-Konto noetig) und kommerzielle Dienste wurden
verworfen.
Neu: bin/instagram-add.php. --profil liest die Beitragsliste ueber headless
Chrome aus dem gerenderten Profil-DOM (im HTML stehen die Codes nicht) und holt
zu jedem neuen Beitrag Bild, Caption und Datum ueber Instagrams oEmbed-Endpunkt;
das Datum steckt in der media_id. Daneben einzelne URLs, --liste, --entfernen und
--bild als Notfall. Der Shortcode ist Primaerschluessel und damit die komplette
Dedupe-Logik. Die Struktur von data/instagram.json bleibt exakt gleich, deshalb
war an Startseite, News-Feed und Stadionzeitung nichts zu aendern.
Kein Cron mehr: der Job faellt aus public/cron.php, auf dem Webspace gibt es kein
Chrome. Die Chrome-Suche liegt jetzt gemeinsam in bin/chrome-lib.php, genutzt von
bin/logo-build.php und bin/instagram-add.php.
data/instagram.json ist ab jetzt versioniert. Solange sie ignoriert war, lief sie
gegen die versionierten Bilder auseinander: lokal lagen 10 Bilder zu 9 Eintraegen,
drei Beitraege fehlten im Feed, zwei Eintraege hatten kein Bild. Repariert, jetzt
16 Eintraege zu 16 Bildern. bin/preflight.php prueft das in beide Richtungen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J9mubC8gtpjtJntbhSJBRS
Damit die Abteilungen das Wappen selbst holen können, statt jedes Mal zu fragen:
drei Varianten (farbig, einfarbig schwarz, einfarbig weiß) in SVG, PDF, PNG und JPG,
dazu ein ZIP über alles und die Vereinsfarben. Verlinkt nur in der Fußzeile.
Bisher lag das Logo genau einmal im Projekt, als eine einzige SVG. Es gab kein PNG,
kein JPG, kein PDF und keine einfarbige Fassung.
bin/logo-build.php erzeugt alle Dateien aus einer Master-SVG unter storage/logo-quellen/.
Gerastert wird mit headless Chrome, weil GD kein SVG lesen kann und weder Inkscape noch
rsvg-convert noch ImageMagick verfügbar sind; das PDF ist dabei echter Vektor. Das Skript
läuft einmalig lokal, nichts davon im Request-Pfad. bin/preflight.php meldet als Fehler,
wenn eine Download-Datei fehlt: die Komponente ließe den Knopf sonst still weg.
Die beiden einfarbigen Fassungen sind exakte Negative voneinander, nur Linien, alle
Flächen frei. EPS gibt es bewusst nicht, dafür fehlt jedes Werkzeug; SVG und PDF decken
die Druckereien ab. Frühere Vereinslogos sind vorbereitet, aber im Projekt liegt bisher
kein einziges historisches Wappen vor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J9mubC8gtpjtJntbhSJBRS
Neue Live-Seiten, geschaltet vom chat-gepflegten Feld `veranstaltung` (Slug aus
data/veranstaltungen.json) — Programm und Turnierpläne kommen damit aus derselben
Quelle wie die Website, nicht als Kopie ins Heft:
- Programm-Seite (programm[], rendert zugleich auf /veranstaltungen/sportfest-2026)
- vier Turnierplan-Seiten (turniere[]) mit leerer Ergebnisspalte und leerer
Abschlusstabelle zum Eintragen am Platz
- Gegnerseite je Mannschaft mit Heimspiel am Fest statt nur der tragenden:
stadionzeitung_gegner_fixtures() ist die einzige Stelle, die das entscheidet,
Seitenplanung und Snapshot lesen beide dort. `gegner` ist deshalb eine Map je
Team-Key; die Vorschau überspringt jedes Spiel mit eigener Gegnerseite
- Danke-Seite mit Partner-Logo-Wand aus data/partners.json und QR auf
/partner-werden, dazu das Anzeigen-Feld `platzierung` für Motive mit festem
Platz auf einer Live-Seite (Emons über der Logo-Wand). Vorher klebte der Dank
als leere Fläche unten an einem Anzeigen-Bild
- Cover einer Sonderausgabe: das Fest als Titelgeschichte statt einer der drei
Begegnungen, dazu das Hauptplakat schräg gelegt an Stelle des Spielerfotos
Ausgabe 2026-09-04-ats-wartenfels als ENTWURF angelegt, 48 Seiten (12 Bögen),
Tabellen auf gerader Seite, längster Anzeigenblock zwei. Veröffentlicht wird auf
dem Server, dort liegt der frische Matchcenter-Stand.
Nebenbei behoben: bin/anzeige-add.php räumte beim Anlegen alle Motive weg, deren
Slug mit demselben Text beginnt (`emons` löschte `emons-partnerdank` und
`emons-stuebinger`). Muster jetzt auf die Breiten-Endung eingeschränkt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KubsajzuV6N6QsSd3xKNjC
Das hochkante Gruppenfoto (1000x1333) wurde auf dem Handy in eine
16:10-Querkachel gecovert und dadurch auf einen Mittelstreifen
zusammengeschnitten. Die Mobil-Kachel bekommt jetzt das echte 3:4-Format,
sodass object-fit: cover passgenau fuellt und niemand beschnitten wird.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WACMqwVB4JpPW5Wkz1Y1DU
Die vergrößerte Interview-Frage (4.6cqi) schob eine Ausgabe mit drei Fragen um
8px über die Blattkante (gemessen). Frage auf 4.2cqi gedeckelt (bleibt größer
als die Antwort) und Abstände von Frage/Absätzen/Trennlinie leicht gestrafft —
Text unverändert, passt jetzt mit Reserve.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFnFYLm2MoBskyk9mc84ZW
- 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
Neue opt-in-Prop groupsInDash in group-schedule zieht schlichte Gruppen
(ohne intro/details) als volle Bodenreihe MIT ins Dashboard-Bento, ohne
eigene Sektion und ohne "Unsere Gruppen"-Ueberschrift. Trampolin nutzt
das, damit Hinweis, Trainingsort, Kontakt und die drei Gruppen-Karten ein
zusammenhaengender Block sind statt einer losgeloesten Kartenreihe darunter.
Wiederverwendung von .group-list--grid/.group-card, Karten-Markup DRY in
renderSimpleCard. Wettkampfturnen/Kinderturnen bleiben unberuehrt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXDvgRDHQ9iGjs4KCXykEj
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 Datei lag geändert im Arbeitsverzeichnis (640×381 statt 640×425, 53 statt
128 KB) und war das einzige Stück, das noch fehlte: die zugehörige Änderung an
app/pages/turnen.php (widths auf [640], Kommentar zur WhatsApp-Quelle) und das
Entfernen der 1000er und 1600er Variante stecken bereits in 592acf0, wo sie
durch mein `git add app/pages/turnen*.php` mitgekommen sind. Ohne diesen Commit
zeigte das Repo das alte Bild bei neuer Deklaration.
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
Der Aufnahmeantrag der Turnabteilung ging als alte URL in den Einladungen an
die neuen Familien raus und lief seit dem Umzug in einen 404, ebenso der Link
auf die Turn-Seite. Beide leiten jetzt weiter, dazu alle uebrigen Seiten der
alten Navigation unter /08/de/.
Die PDF-Regel matcht am Umlaut vorbei: der Dateiname kommt je nach Mailprogramm
als NFC, als NFD (so stand er in der alten Datenbank) oder roh als UTF-8 an.
Die Sammelregel wirft den alten Query-String weg, sonst haengt mod_rewrite
?group_filter_id=14 ans neue Ziel.
Erledigt damit den offenen Punkt F3 aus docs/deploy.md.
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
Auf schmalen Bildschirmen stapeln sich Container- und Kartenabstand so, dass für
den Inhalt kaum Breite bleibt. Gemessen bei 309px: Container 11,5 + Karte 32 je
Seite, das Eingabefeld hatte 220px, also 71% der Bildschirmbreite.
Kein harter Breakpoint ("ab 310px vollflächig"), sondern fluide Abstände — ein
Umbruch bei 310px sähe bei 311px wieder falsch aus. Das Muster gab es im Projekt
schon: fee-table, membership-card, legal__card und matchcenter-empty nutzen
clamp()-Abstände. Fest waren genau die drei, über die es stolperte: .form (32px),
.group-card (32px) und .group-tabs__panel (40px).
Zwei neue Token in tokens.css als einzige Quelle, --pad-card (20…32px) und
--pad-card-lg (24…40px). Die OBERGRENZEN sind die bisherigen festen Werte, am
Desktop ändert sich also nichts — nachgemessen: 32px bei 1269px Viewport, wie
vorher. Es wird nur nach unten schmaler.
Ergebnis: Feldbreite bei 309px von 71% auf 76%, bei 379px von 74% auf 80%.
Kein Überlauf auf /turnen, den Disziplinseiten und /kontakt bei 320px und 1280px.
Die vier bereits fluiden Karten behalten ihre eigenen clamp()-Werte, damit ihr
Desktop-Bild unangetastet bleibt; sie in die Token einzusammeln ist eine
Aufräumung für später.
VORBESTEHEND und nicht von dieser Änderung verursacht (per git stash gegengeprüft,
mit und ohne Änderung identisch): /mitglied-werden läuft bei 320px um 12px über,
/turnen bei 300px um 13px. Die 320px sind eine echte Gerätebreite (iPhone SE),
das gehört als Nächstes angeschaut.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Bei sehr schmalen Breiten stand der Text über den Kartenrand hinaus. Gemessen bei
300px Viewport (clientWidth 289): Dokument 317px, also 28px Überlauf; die einzelne
Grid-Spur des Panels rechnete sich auf 267,7px aus, obwohl die Karte nur 193px
Inhaltsfläche hat.
Ursache: `grid-template-columns: 1.6fr 1fr` bzw. `1fr` — eine fr-Spur hat als
automatisches Minimum min-content und kann deshalb nicht schmaler werden als das
längste Wort darin. Bei deutschen Komposita ("sportartenübergreifende",
"Grundlagenausbildung") sprengt das die Karte. Jetzt minmax(0, …) in beiden
Spurdefinitionen von .group-tabs__panel, und dieselbe Korrektur bei
.group-list--stacked, wo das Muster latent drinsteckte.
Zwei Umwege, die ich mir hätte sparen können: min-width: 0 am Grid-KIND reicht
nicht, die SPUR muss das Schrumpfen erlauben. Und overflow-wrap: break-word
verkleinert die min-content-Breite nicht (nur `anywhere` täte das) — beides bleibt
trotzdem drin, zusammen mit hyphens: auto, weil die Seite lang="de" ist und der
Browser die Komposita dann sauber trennt statt hart umzubrechen.
Geprüft: alle sechs Turn-Seiten bei 300px, 320px und 390px ohne Überlauf. Desktop
unverändert zweispaltig (648,6 / 405,4 px, Verhältnis 1,6:1).
OFFEN und bewusst nicht mitgefixt: /turnen läuft bei 300px noch um 13px über, ab
320px nicht mehr. Es kommt aus dem Kontaktformular, nicht aus den Beitragskarten
oder Gruppenkarten — die min-content-Breite des Formulars in der Grid-Spur von
.container. .container global anzufassen ist mir für 13px auf einer Breite, die
kein Gerät hat, zu riskant (das schmalste verbreitete Display ist 320px).
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
--ls-caps (0.08em) war zu viel: bei 34px sind das 2,7px und die Überschriften
wirkten auseinandergezogen. Gewollt war ein Hauch Luft, etwa 0,4px (Felix).
Dafür gibt es keinen Token, und rohe Werte verbietet die Token-Disziplin — also
--ls-heading: 0.012em, was bei 34px genau 0,408px ergibt (nachgemessen). Der Fall
trifft jede Coolvetica-Überschrift in h3-Größe, nicht nur diese Karten: der
Schnitt ist condensed, bei 60px trägt er ohne Sperrung, bei 34px kleben die
Versalien. --ls-caps bleibt für kleine Labels, wofür es gedacht ist.
Klarstellung zum vorigen Commit: die Titel waren und bleiben Coolvetica. Der
Verweis auf Abel in meiner Begründung war ein Zitat der CLAUDE.md-Regel für
kleine Auszeichnungen, keine Änderung an der Schriftart.
CLAUDE.md entsprechend berichtigt, inklusive der Warnung, hier nicht --ls-caps
zu nehmen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Zwei Punkte aus dem Gegenlesen, beide gemessen statt geschätzt.
1) Die Kartentitel waren schwer lesbar. Gemessen war letter-spacing "normal",
also gar keins gesetzt — nicht zu eng eingestellt. Coolvetica ist ein
Condensed-Schnitt: bei 60px (Abschnitts-h2) trägt das, bei 34px in einer
schmalen Kartenspalte kleben die Versalien. Jetzt var(--ls-caps), der
dokumentierte Token für gesperrte Uppercase-Auszeichnungen.
2) Die Preise standen in Akzentrot. Das ist kein Geschmacksfehler, sondern ein
Kontrastfehler: #e20612 auf dem Kartengrund (rgba(0 0 0 / 0.22) über #222)
erreicht 3,50:1 und verfehlt die 4,5:1 für Fließtext. Weiß liegt bei 17,22:1.
CLAUDE.md verbietet Rot für Fließtext auf dunklem Grund ohnehin. Hervorgehoben
bleibt der Preis durch Coolvetica und 28px gegen 21px Fließtext.
Die Preisfarbe wirkt auch auf /mitglied-werden, wo dieselbe Komponente steht —
dort ist der Grund dasselbe dunkle Glas (geprüft), die Änderung verbessert also
beide Seiten.
Beide Erkenntnisse als Regel in CLAUDE.md, mit den gemessenen Zahlen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Die Turnabteilung verlinkt die Beiträge in Einladungen für neue Familien und
nimmt zum September auf — die Angaben waren aber auf der ganzen Website nicht
zu finden. Nachgeprüft: 0 Treffer auf /turnen und allen fünf Disziplin-Seiten,
/mitglied-werden führt nur die Fußball-Beiträge.
Es war keine verlorene Seite, sondern eine Lücke im Code: app/pages/turnen.php
rendert nur Hero, Karten und Formular. In data/turnen.json lagen ungenutzt
cards_intro, fees (drei Tabellen), austritt und faq (fünf Fragen). Der Docblock
behauptete, die Beiträge lägen auf den Unterseiten — sie lagen dort nicht, und
in der Git-Historie wurden sie nie gerendert.
Inhalte 1:1 wie auf der alten Seite (gegengeprüft an 2024.tsv08kulmbach.de,
Tab "Allgemeines"): Zusatzbeitrag 24/48 €, Neumitglieder 84/94/184 €,
Förderstufen 120/180/240 €, Austritt zum 30. September. Nichts umformuliert,
die Abteilung entscheidet über Änderungen.
Struktur jetzt Hero → Karten → Beiträge (#beitraege) → FAQ → Formular. Der
Anker ist die Adresse für die Einladungen. Nur bestehende Komponenten benutzt
(fee-table wie auf /mitglied-werden, faq, team-cards mit intro); neu ist allein
der Wrapper .fee-tables. Die FAQ füllt zusätzlich das FAQPage-Schema, weil
page_schema() sie als zweiten Parameter bekommt.
Feinschliff nach Sichtprüfung: Kartentitel von --fs-650 auf --fs-675, weil 28px
Coolvetica neben 21px Fließtext zu klein wirkten; Karten gleich hoch (der
Grid-Default stretch statt align-items: start, gemessen vorher 252/377/471px)
mit dem Hinweis am Kartenfuß; und minmax(min(22rem, 100%), 1fr) statt
minmax(22rem, 1fr), weil die Karten am Handy 352px breit in einem 347px-Raster
standen und überliefen.
NOCH offen: overview.cta ist weiter unbenutzt. Der Block verlinkt "Zur
Turn-Übersicht" und gehört auf die Disziplin-Unterseiten, nicht auf die
Übersicht — dort wäre es ein Selbstlink.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Am Handy waren die Seitenhöhen beim Durchwischen unterschiedlich: 350 bis 559px
je Inhalt, statt einheitlich 515. Nur Cover, Vorwort, Termine und Sportheim
trafen A5 — die vier haben eine eigene aspect-ratio.
Ursache war ein Fehler von mir aus dem vorigen Commit: der ergänzte Kommentar
landete HINTER dem schließenden */ des bestehenden. Damit stand roher Text vor
der Regel, und der CSS-Parser verwarf den kompletten Block
".stadionzeitung-viewer.is-enhanced .stadionzeitung-page, … { aspect-ratio }".
Der Browser meldete aspect-ratio: auto, obwohl die Regel im ausgelieferten CSS
stand und der Selektor per matches() passte — genau das Bild, das ein
verworfener Block erzeugt. Ein Testlauf mit aspect-ratio: 1/3 änderte nichts,
was den Verdacht bestätigte. php -l prüft CSS nicht, es gab also keinen
Hinweis.
Dass es vor der align-items-Änderung einheitlich aussah, war Zufall: der
Flex-Default stretch streckte alle Seiten auf dieselbe Zeilenhöhe (559) und
verdeckte damit, dass die A5-Regel nie griff.
Zusätzlich sind die Bild-Seiten (Anzeigen) jetzt in derselben A5-Regel. Sie
leiteten ihre Höhe aus dem eigenen Bildverhältnis ab; drei Motive sind
1440x2037 statt 1440x2043 und waren dadurch 513 statt 515px hoch, was beim
Wischen als Sprung auffiel. object-fit: cover fängt die 0,4% Abweichung ab,
wie die Druckfassung es ohnehin tut.
Gemessen: 40 von 40 Seiten 363x515 bei 390px Viewport. Zwei Seiten brauchen
dort internen Scroll (Ergebnisse +44px, Vorschau +11px), weil lange
Vereinsnamen umbrechen — Textumbruch skaliert nicht mit den cqi-Maßen. Im
Druck ist beides unauffällig, per PDF-Sichtprüfung an der Ergebnisse-Seite
bestätigt: Überschrift, drei Ergebnisblöcke und CTA-Kasten vollständig.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Titel lautet jetzt "Stadionzeitung zum Heimspiel gegen <Gegner>" statt nur
"Gegen <Gegner>". Damit ist die Zeile für sich verständlich, auch als Link-Text
für Screenreader. Das "zum" ist eingefügt, damit der Satz grammatisch trägt.
Größe von --fs-650 auf --fs-690, den Token für Karten-Überschriften (28px auf
32 bis 44px). Die alte Begründung für die kleinere Stufe war, dass lange
Gegner-Bezeichnungen in die 280px-Karte passen mussten — die Karte ist jetzt
eine Zeile über die volle Breite, der Grund ist weg. Nach unten bleibt
--fs-650 die harte Grenze für Coolvetica in Versalien.
Geprüft bei 1280px (Titel auf einer Zeile, füllt die Zeile) und bei echten
390px (bricht auf zwei Zeilen, liest sich sauber).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Cover links, Angaben rechts daneben — dasselbe Muster wie .veranstaltung-card,
und aus demselben Grund: der Bestand ist klein.
Vorher war es ein horizontales Regal (grid-auto-flow: column, 280px-Spalten,
Scroll-Snap). Der Kommentar dort nannte genau die richtige Gefahr, "bei 1-3
Karten wie eine leere Fläche mit einer verlorenen Karte", löste sie aber nicht:
mit einer veröffentlichten Ausgabe stand eine 280px-Karte allein links auf einer
1280px breiten Seite. Eine Zeile über die volle Breite füllt die Fläche bei jeder
Anzahl.
Zwei Dinge sind dabei anders als bei den Veranstaltungen, beide begründet:
- Die Medienspalte ist schmaler (clamp bis 230px statt 26%), weil das Medium
hier die live gerenderte Cover-Seite mit festem A5-Verhältnis ist. Ein <img>
liesse sich per object-fit beschneiden, das Cover nicht — es bestimmt die
Zeilenhöhe. 230px Cover ergeben 326px Zeile.
- Das margin-top: auto am CTA ist weg. Es stammte aus der hochformatigen Karte,
wo der Link an den Kartenfuss gehörte; in der breiten Zeile riss es die drei
kurzen Zeilen auf 150px auseinander.
Neu in der Zeile: die Coverzeile des Interviews als Teaser (interview.teaser,
dieselbe Quelle wie "Im Heft" auf dem Cover). Sie füllt die Breite mit echtem
Inhalt statt Leerfläche und sagt, warum man die Ausgabe öffnen sollte; ohne
Interview entfällt sie, wie .veranstaltung-card__text.
Geprüft per Screenshot bei 1280px und bei echten 390px (iframe-Wrapper, weil
headless Chrome sonst eine Mindestbreite erzwingt): mobil steht das Cover über
den Angaben.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Zwei Befunde aus dem Gegenlesen des Hefts, beide gemessen.
1) Die Seiten waren unterschiedlich breit: Umschlagbogen 448,5px,
Innenblätter 441px. Ursache war ein Falz-Steg von 2%, den nur die
Innenbögen hatten (:nth-child(n+3)). Der Gedanke war richtig — randabfallende
Anzeigen stoßen sonst mit 0mm an die Blattmitte — die Umsetzung aber
uneinheitlich, und das fiel als "das Programm ist schmaler als das Vorwort
drüber" auf. Entfernt zugunsten von "alle Bögen gleich"; die 9,9mm
Innenabstand der Seiten halten den Inhalt aus dem Knick. Soll er zurück,
dann für alle Bögen.
2) Unter dem Foto der Sportheim-Seite blieb ein weißer Streifen: die Seite war
625,7px hoch in einer 636,4px hohen Fläche. Sie hat wie Vorwort und Termine
eine eigene aspect-ratio plus negativen Margin, aber die min-height-
Absicherung hatte nur das Vorwort bekommen. Die drei stehen jetzt in EINER
Regel, damit eine vierte nicht wieder abdriftet.
Nachgemessen: Sportheim 448,5 x 636,4 statt 441 x 625,7, Foto bündig 0/0/0,
alle Slots ohne Padding, im gedruckten PDF Bogen 11 unten 0,0mm. Sichtprüfung
an Bogen 11: Foto läuft bis an linke und untere Kante.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Im Druck war oben fast kein Rand und unten viel — im Chrome-Export etwa 3 zu
8 mm. Der Rand war nicht die Ursache: der Blatt-Inhalt war über eine
aspect-ratio bemaßt und nur 199,1 mm hoch statt 210 mm. Die fehlenden 10,9 mm
sammelten sich immer unten, weil der Inhalt oben ausgerichtet ist. Gemessen an
Blatt 4 des erzeugten PDF:
margin 5mm -> oben 4,8 / unten 5,8 mm (Rest 0,6 fiel kaum auf)
margin 0 -> oben 0,0 / unten 10,9 mm (der ganze Rest unten)
Ein Rand hätte den Fehler also nur kaschiert. Randabfallend ist die
geometrisch saubere Lösung, weil zwei A5 exakt ein A4 quer sind
(2 x 148,5 = 297 mm, Überschuss null) — es bleibt nichts zu verteilen.
Umgesetzt durch Auflösen einer Ausnahme statt neuer Sonderregeln: die zwei
Umschlagbögen liefen längst randabfallend (@page umschlag, Fläche 210mm), das
gilt jetzt für alle Bögen. Damit fällt auch der Bruch weg, dass der Umschlag
anders gedruckt wurde als die Innenblätter.
Nachgemessen: oben und unten je 0,0 mm auf Blatt 1, 4 und 6. Verbleibendes Weiß
steckt in den Anzeigenmotiven selbst (viele Sponsoren gestalten auf weißem
Grund, 100% weiße Randpixel) und ist nicht unser Layout. Sichtprüfung an
Blatt 4: Sportfest-Plakat läuft bis an alle vier Kanten, nichts beschnitten.
Bewusst in Kauf genommen (Felix): der Bürodrucker beschneidet auf sein
Hardware-Minimum von 4 bis 5 mm und liefert den Rand selbst. Unser Fließtext
verliert dabei nichts, die Live-Seiten haben 9,9 mm Innenabstand. Ein
Falz-Abstand entfällt, die Seiten stoßen mittig aneinander.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Unter dem einblendenden Rasenbild blieb ein weißer Streifen, responsiv wurde er
größer. Gemessen bei 390px Viewport: Seite 559px hoch, Vorwort 515px — 44px
Lücke.
Ursache lag nicht am Vorwort, sondern an der Seite: .stadionzeitung-viewer__page
ist display:flex, und der Flex-Default align-items:stretch streckt die Seite auf
die Höhe der Flex-Zeile und übersteuert ihre aspect-ratio. Die Seite war damit
gar kein A5 mehr (559/363 statt 210/148) — das betraf ALLE Seiten, sichtbar wurde
es nur beim Vorwort, weil dort ein Bild die Blattkante erreichen muss.
align-items: center behebt es an der Wurzel: alle Seiten sind wieder exakt A5,
Lücke 0px. Zusätzlich bekommt das Vorwort min-height: calc(100% + 2x Padding) als
Absicherung — die aspect-ratio trägt weiter, wenn die Seite keine definite Höhe
hat (No-JS-Stapel, Druck), die min-height füllt sie, wenn sie eine hat.
Gemessen vorher/nachher, Desktop-Viewer per Screenshot gegengeprüft.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Die Druckfassung ist Werkzeug für die Heft-Produktion, kein Besucher-Angebot.
Als sichtbarer Knopf neben Blättern und Vollbild wurde er versehentlich geklickt
und riss Leute aus dem Lesen (Felix, 31.07.2026). Die Route bleibt unverändert
erreichbar, nur über die URL: ?druck=1 an die Ausgabe hängen.
Mit dem Knopf fallen drei CSS-Stellen weg, die es ohne ihn nicht mehr braucht:
die Basisregel, die Mobil-Ausblendung (dort wäre er eine Sackgasse gewesen) und
der Eintrag in der Ausblend-Liste der Druckansicht. In CLAUDE.md vermerkt,
inklusive der Bitte, ihn nicht als Link wieder einzubauen.
Geprüft: Viewer enthält kein druck=1 und kein viewer__print mehr, Vollbild und
Blättern intakt, ?druck=1 antwortet weiter mit 200 und montiert die Blätter.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Das Cover fehlte am iPhone, Chrome am Desktop zeigte es. Es hatte im Viewer eine
eigene Regel mit "max-height: 72vh; width: auto" — und width:auto ist bei
inline-size-Containment genau die Falle, die für .stadionzeitung-page schon
behoben war: der Inhalt zählt für die eigene Breite per Definition nicht mit.
Sichtbar wird der Unterschied, weil .stadionzeitung-viewer__page display:flex ist:
auf einem Flex-Kind heißt width:auto "richte dich nach dem Inhalt". Chrome
rettet sich über aspect-ratio plus gestreckte Höhe, Safari nicht.
Cover und Seite stehen jetzt in EINER Regel mit derselben definiten Breite, damit
sie nicht wieder auseinanderlaufen; nur overflow-y bleibt den Seiten vorbehalten.
Gemessen bei 390px Viewport: Cover 363x559, identisch zu den Folgeseiten.
EHRLICHE GRENZE: Chrome zeigt vor und nach der Änderung dieselben Maße, der
Fehler ist im Desktop-Browser nicht reproduzierbar. Die Ursache ist damit
begründet, aber nicht bewiesen — Gegenprobe nur am echten Gerät. In CLAUDE.md
vermerkt, damit das nächste Mal niemand im Desktop-Browser "verifiziert".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
Korrektur meiner eigenen Annahme von heute früh. Der Tarif hat SSH und
Cronjobs, aber das KAS-Formular kennt nur ein Feld "Protokoll / Pfad" mit
https:// — ein `php bin/…` ist dort nicht eintragbar. Der Endpoint, den ich
vorhin stillgelegt hatte, ist damit wieder der einzige Takt-Geber, und zwar in
einer besseren Form als geplant: Aufrufer ist der Hoster selbst, der Schlüssel
verlässt den Webspace nie, kein Dritter ist beteiligt.
Dritter Job 'logrotate' in der Whitelist, denn ohne ihn liefe die Logpflege nie
automatisch und die 2-MB-Grenze samt 90-Tage-Frist wäre toter Code. Er passt in
denselben Rahmen wie die Syncs: kein Parameter aus der URL, keine Verbindung nach
außen, schreibt nur in storage/logs. bin/log-rotate.php trägt dafür denselben
Riegel wie die Syncs (CRON_HTTP) und require_once. Eine vierte Ausnahme braucht
wieder denselben Aufwand an Begründung.
Geprüft: falscher Schlüssel 403, unbekannter Job 403, gültiger Aufruf 204 mit
leerem Body, sofortige Wiederholung 204 plus WARN, Logdateien unversehrt.
Zeitraster angepasst, weil das KAS Wochentage nicht mit Halbstunden kombiniert:
Matchcenter stündlich statt 2x/Tag plus Wochenende, Instagram als zwei
Tageseinträge, Logpflege täglich nachts. Die Abwägung steht in
config.example.php und docs/deploy.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7