diff --git a/CLAUDE.md b/CLAUDE.md index 8d7dd2b..8b563ff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,7 +54,7 @@ selbst gehosteter Stack ohne Abhängigkeiten von Drittanbietern. | Partner/Sponsoren | `data/partners.json` | — | | Instagram-Beiträge | `data/instagram.json` + `public/assets/img/instagram/` | Aufgenommen ausschließlich mit **`php bin/instagram-add.php `** (holt Bild, Text und Datum) — Einträge nie von Hand tippen, Bilder nie von Hand skalieren. **Kein Cron**, chat-gepflegt wie `events.json`. Eintrag und Bild müssen immer zusammen existieren, `bin/preflight.php` prüft beide Richtungen | | Matchcenter (Spiele/Ergebnisse/Tabellen) | `data/matchcenter.json` + `public/assets/img/crests/` | **maschinenverwaltet von `bin/matchcenter-sync.php` — nie von Hand editieren**. Team-/Wettbewerbs-IDs in `config.php` (`matchcenter.teams`) | -| Stadionzeitung (Ausgaben-Archiv) | `data/stadionzeitung.json` + `public/assets/img/stadionzeitung/` | **maschinenverwaltet von `bin/stadionzeitung-add.php` + `bin/stadionzeitung-publish.php` (Status/Snapshot) — nie von Hand editieren; einzige Chat-Pflege-Ausnahmen sind `vorwort`, `interview`, `aufruf`, `veranstaltung` (Slug-Verweis auf `veranstaltungen.json`) und `cover` (`teaser` einer Sonderausgabe).** Der Ausgaben-Ordner wird bei jedem Lauf komplett geleert und neu gefüllt | +| Stadionzeitung (Ausgaben-Archiv) | `data/stadionzeitung.json` + `public/assets/img/stadionzeitung/` | **maschinenverwaltet von `bin/stadionzeitung-add.php` + `bin/stadionzeitung-publish.php` (Status/Snapshot) — nie von Hand editieren; einzige Chat-Pflege-Ausnahmen sind `vorwort`, `interview`, `aufruf`, `veranstaltung` (Slug-Verweis auf `veranstaltungen.json`), `cover` (`teaser` einer Sonderausgabe), `opponent_short` (gedruckter Anzeigename des Gegners) und `gegner_teams` (weitere Mannschaften mit Gegnerseite).** Der Ausgaben-Ordner wird bei jedem Lauf komplett geleert und neu gefüllt | | Sponsoren-Anzeigen (Stadionzeitung) | `data/anzeigen.json` + `public/assets/img/anzeigen/` | Dauerhafter Bestand, **nicht** pro Ausgabe: Motive ändern sich selten, das Heft erscheint oft. `sponsor`, `alt` (optional), `aktiv`, `bis` (optional, Enddatum gegen das Ausgabedatum), `platzierung` (optional, fester Platz auf einer Live-Seite statt im Streu-Pool) und die **Reihenfolge** (= Reihenfolge im Heft) im Chat gepflegt; `base`/`widths` schreibt ausschließlich `bin/anzeige-add.php`. Anzeigen nie in den Ausgaben-Ordner legen | | Termine/Events (`/termine`) | `data/events.json` (Vereins-Termine **ohne** eigene Detailseite) — Spieltermine kommen weiterhin nur aus `data/matchcenter.json`, Feste mit eigener Seite nur aus `data/veranstaltungen.json`; die Termine-Seite mergt alle drei | Von Claude im Chat gepflegt wie `teams.json`/`partners.json` — **keine** Automatisierung, kein Bin-Skript. Spieltermine nie hier duplizieren. Ein Fest, das eine Detailseite hat, steht **nur** in `veranstaltungen.json` (`bin/preflight.php` prüft das) | | Veranstaltungen (`/veranstaltungen` + Detailseiten) | `data/veranstaltungen.json` + `public/assets/img/veranstaltungen/` | Termin, Texte, Programm, Turnierpläne (`turniere[]`, nur fürs Heft), Abschnitte von Claude im Chat gepflegt — **außer `plakate` und `galerie`: die schreibt ausschließlich `bin/veranstaltung-bilder.php`**. Adresse nie in die Texte schreiben (steht in `club.json`) | @@ -795,6 +795,49 @@ keinen farbigen Rahmen über das Spielerfoto** — der Kicker bleibt „Saison die Ausgabe nicht, in die die Felder gehören. `bin/preflight.php` prüft, dass der Slug und jeder Turnier-Key wirklich existieren — ein Tippfehler ergäbe sonst eine **leere** A5-Seite ohne Fehler. +**Gegnerseiten ohne Fest, und der Anzeigename des Gegners (17.09.2026).** Zwei Felder, beide +chat-gepflegt und von `bin/stadionzeitung-add.php` über erneute Läufe durchgetragen wie `vorwort`: + +- **`gegner_teams`** (Liste von Team-Keys) gibt weiteren Mannschaften eine Gegnerseite, ohne dass es + eine Veranstaltung braucht. Der Sportfest-Weg blieb unverändert daneben stehen (Heimspiele im + Festzeitraum); dieser hier nimmt das nächste Spiel im Fenster **zwei Tage vor bis drei Tage nach** + dem Ausgabedatum, **auch auswärts** — am normalen Spielwochenende fährt eine der drei Mannschaften + fast immer weg, und ihr Gegner ist trotzdem eine Seite wert. `stadionzeitung_gegner_fixtures()` + bleibt die eine entscheidende Stelle. Findet eine benannte Mannschaft kein Spiel im Fenster, + entsteht keine Seite und das Skript **warnt** — ein Tippfehler im Team-Key sähe sonst aus wie ein + spielfreies Wochenende. Auf der Gegnerseite steht seitdem **immer unsere Mannschaft links** + (Klasse `--own`), bei einem Auswärtsspiel läuft die Zeile „… bei …" statt „… gegen …"; vorher war + die linke Spalte fest die Heimmannschaft und die Hervorhebung lag beim Gegner. +- **`opponent_short`** ist der gedruckte Anzeigename des Gegners, gelesen über + **`stadionzeitung_opponent_display()`** (Cover, Archivzeile, Ausgaben-Kopf, Titel, Meta, Alt-Text + der Rückseite). **`opponent` selbst muss die BFV-Schreibweise behalten** — die Gegnerseite sucht + damit ihre Tabellenzeile und ihre Begegnung in `matchcenter.json` über einen exakten + Namensvergleich. Bei Spielgemeinschaften ist das eine Zeile wie „(SG1) TSV Wirsberg I/ FC + Kupferberg I", die auf einem Cover nichts zu suchen hat; `opponent_short` trägt dann + „Wirsberg/Kupferberg". Fehlt das Feld, bleibt es beim vollen Namen. + +Nebenbei löst das oft die Seitenzahl: drei Gegnerseiten statt einer sind **zwei Seiten mehr**, und +ein Heft, das bei 42 stand, steht damit auf 44. + +**Gegner- und Ergebnisseite neu gestaltet (19.09.2026).** Beide Seiten waren inhaltlich richtig und +gestalterisch leer, der Umbau lief über mehrere verworfene Entwürfe. Was davon bleibt: +- **Ergebnisse als Anzeigetafel** (Vorbild kicker/Sofascore): Wappen und Name je Seite, das Ergebnis + groß in der Mitte. Vorher waren es Listenzeilen, und eine Zeile stellt Heim und Gast untereinander + statt gegenüber. Verworfen und nicht wieder einzubauen: Tore am rechten Blattrand (halbe + Seitenbreite Leere zwischen Name und Ziffer), eine getönte Karte je Spiel („altbacken"), ein roter + Strich unter der Mannschaftszeile (lauter als die Ergebnisse) und graue Verlierer-Ziffern (auf + Papier schlechter lesbar). Vereinsnamen laufen **einzeilig** und werden sonst ausgepunktet. +- **Gegnerseite**: Duell symmetrisch und leise (beide Wappen gleich groß, der Name des Gegners steht + nur in der Überschrift laut da), darunter eine abgeleitete Aufmacher-Zeile („Sieben Siege aus acht + Spielen", aus den Tabellenwerten gebaut), der Vergleich als **Balkenpaar aus der Mitte** und zum + Schluss ein **Tabellenausschnitt** über die geteilte `league-table`-Komponente. Der Tabellenplatz + steht bewusst dort und nicht als Balken (klein ist besser, der Balken läge falsch herum) und auch + nicht als lose Ziffer im Kopf (war gebaut, wirkte unruhig). +- **Balkenlängen kommen aus Klassen in 5-Prozent-Stufen**, nie aus einem `style`-Attribut — die CSP + verwirft inline styles stillschweigend, die Balken wären erst nach dem Deploy unsichtbar. +- Die Gegnerseite druckt **`opponent_display`** (Kurzname der Ausgabe, wenn die Seite den Gegner der + Ausgabe zeigt); `opponent` bleibt die BFV-Schreibweise für den Namensvergleich. + **Cover (automatisch generiert, Phase 3):** einzige fotografische Stadionzeitungs-Seite — `app/components/stadionzeitung-cover.php`. Echtes Magazin-Cover statt Website-Baustein: **kein** Stadionfoto mehr als Hintergrund, stattdessen ein ruhiger Verlauf in Vereinsfarben, auf dem das diff --git a/app/components/stadionzeitung-ergebnisse.php b/app/components/stadionzeitung-ergebnisse.php index 174f325..5aacd12 100644 --- a/app/components/stadionzeitung-ergebnisse.php +++ b/app/components/stadionzeitung-ergebnisse.php @@ -40,19 +40,28 @@ $matchcenterLink = club_domain('matchcenter');

-
-

- · -

- $match['home'] ?? '', 'crest' => $match['home_crest'] ?? null, 'goals' => $match['goals_home'] ?? null], - ['club' => $match['away'] ?? '', 'crest' => $match['away_crest'] ?? null, 'goals' => $match['goals_away'] ?? null]] as $side): ?> -
+ $match['home'] ?? '', 'crest' => $match['home_crest'] ?? null, 'goals' => $match['goals_home'] ?? null], + ['club' => $match['away'] ?? '', 'crest' => $match['away_crest'] ?? null, 'goals' => $match['goals_away'] ?? null], + ]; +?> +
+ $side): ?> + +
+

+ : +

+

·

+
+ +
$side['crest'], 'name' => (string) $side['club']]); ?> -
-
+
diff --git a/app/components/stadionzeitung-gegner.php b/app/components/stadionzeitung-gegner.php index a211bb5..5ceb5a6 100644 --- a/app/components/stadionzeitung-gegner.php +++ b/app/components/stadionzeitung-gegner.php @@ -18,15 +18,29 @@ $teamName = trim((string) ($info['team_name'] ?? '')); $fixture = is_array($info['fixture'] ?? null) ? $info['fixture'] : []; $own = is_array($info['own'] ?? null) ? $info['own'] : null; $opponent = is_array($info['opponent_stats'] ?? null) ? $info['opponent_stats'] : null; -$opponentName = (string) ($info['opponent'] ?? 'Unser Gegner'); +// Gedruckt wird der Anzeigename (Kurzname der Ausgabe, wenn gepflegt) — die +// BFV-Schreibweise „(SG1) TSV Wirsberg I/ FC Kupferberg I" ist ein Suchschlüssel, +// keine Überschrift. +$ausschnitt = is_array($info['table_excerpt'] ?? null) ? $info['table_excerpt'] : []; +$opponentName = (string) ($info['opponent_display'] ?? $info['opponent'] ?? 'Unser Gegner'); + +// Links steht IMMER unsere Mannschaft (Klasse --own hebt sie hervor), rechts der +// Gegner. Bei einem Heimspiel ist das ohnehin die Reihenfolge Heim/Gast; fährt die +// Mannschaft aber auswärts, trüge die Hervorhebung sonst der gegnerische Verein. +$istHeim = !empty($fixture['is_home']); +$ownName = (string) (($istHeim ? ($fixture['home'] ?? null) : ($fixture['away'] ?? null)) ?? 'TSV 08 Kulmbach'); +$ownCrest = ($istHeim ? ($fixture['home_crest'] ?? null) : ($fixture['away_crest'] ?? null)) ?? $own['crest'] ?? null; +$gegnerLabel = $opponentName; +$gegnerCrest = ($istHeim ? ($fixture['away_crest'] ?? null) : ($fixture['home_crest'] ?? null)) ?? $opponent['crest'] ?? null; $kickoff = (string) ($fixture['kickoff'] ?? ''); $kickoffDate = $kickoff !== '' ? substr($kickoff, 0, 10) : ''; $kickoffTime = $kickoff !== '' ? substr($kickoff, 11, 5) : ''; +// Der Tabellenplatz steht NICHT in der Balkenliste: klein ist dort besser, +// ein Balken läge genau falsch herum. Er gehört nach oben neben die Wappen. +// „Spiele“ fällt ebenfalls raus (für beide gleich, steht in der Kopfzeile). $rows = [ - ['label' => 'Tabellenplatz', 'key' => 'rank'], - ['label' => 'Spiele', 'key' => 'played'], ['label' => 'Siege', 'key' => 'won'], ['label' => 'Unentschieden', 'key' => 'drawn'], ['label' => 'Niederlagen', 'key' => 'lost'], @@ -41,6 +55,57 @@ $format = static function (?array $row, string $key, bool $signed = false): stri $value = (int) $row[$key]; return $signed && $value > 0 ? '+' . $value : (string) $value; }; + +// Balkenlänge als KLASSE, nie als style-Attribut: die CSP verwirft inline +// styles stillschweigend (siehe CLAUDE.md), der Balken wäre in Produktion +// unsichtbar. Deshalb 5-Prozent-Stufen, die components.css ausdefiniert. +$barStep = static function (?array $row, ?array $other, string $key): int { + if ($row === null) { + return 0; + } + $value = abs((int) ($row[$key] ?? 0)); + $scale = max($value, $other === null ? 0 : abs((int) ($other[$key] ?? 0))); + if ($scale <= 0) { + return 0; + } + return (int) round($value / $scale * 20); +}; + +// Ein Satz statt einer Zahlenkolonne: derselbe Bestand, nur in Sprache. Die +// erste zutreffende Regel gewinnt, damit immer genau eine Aussage dasteht. +$zahlwort = static function (int $n): string { + $woerter = [0 => 'Kein', 1 => 'Ein', 2 => 'Zwei', 3 => 'Drei', 4 => 'Vier', 5 => 'Fünf', + 6 => 'Sechs', 7 => 'Sieben', 8 => 'Acht', 9 => 'Neun', 10 => 'Zehn', 11 => 'Elf', + 12 => 'Zwölf', 13 => 'Dreizehn', 14 => 'Vierzehn', 15 => 'Fünfzehn']; + return $woerter[$n] ?? (string) $n; +}; +$aufmacher = ''; +if ($opponent !== null) { + $played = (int) ($opponent['played'] ?? 0); + $won = (int) ($opponent['won'] ?? 0); + $lost = (int) ($opponent['lost'] ?? 0); + $rank = (int) ($opponent['rank'] ?? 0); + $points = (int) ($opponent['points'] ?? 0); + $diff = (int) ($opponent['goal_diff'] ?? 0); + if ($played > 0 && $lost === 0) { + $aufmacher = 'Noch ohne Niederlage'; + } elseif ($played > 0 && $won === 0) { + $aufmacher = 'Noch ohne Sieg'; + } elseif ($played > 0 && $won / $played >= 0.7) { + $aufmacher = $zahlwort($won) . ' Siege aus ' . mb_strtolower($zahlwort($played)) . ' Spielen'; + } elseif ($rank === 1) { + $aufmacher = 'Tabellenführer mit ' . $points . ' Punkten'; + } elseif ($played > 0 && $diff <= -8) { + $aufmacher = $zahlwort(abs($diff)) . ' Tore mehr kassiert als geschossen'; + } elseif ($played > 0 && $diff >= 8) { + $aufmacher = $zahlwort($diff) . ' Tore mehr geschossen als kassiert'; + } elseif ($played > 0) { + $aufmacher = $points . ' Punkte aus ' . $played . ' Spielen'; + } +} +$spieltagInfo = $opponent !== null && (int) ($opponent['played'] ?? 0) > 0 + ? 'Stand nach ' . (int) $opponent['played'] . ' Spieltagen' + : ''; ?>
@@ -53,25 +118,26 @@ $format = static function (?array $row, string $key, bool $signed = false): stri
-
- $fixture['home_crest'] ?? $own['crest'] ?? null, 'name' => $fixture['home'] ?? 'TSV 08 Kulmbach']); ?> - +
+ $ownCrest, 'name' => $ownName]); ?> +
-
- gegen +
+

- - - - Uhr +


Uhr

- $fixture['away_crest'] ?? $opponent['crest'] ?? null, 'name' => $fixture['away'] ?? $opponentName]); ?> - + $gegnerCrest, 'name' => $gegnerLabel]); ?> +
+ +

+ +
@@ -79,18 +145,33 @@ $format = static function (?array $row, string $key, bool $signed = false): stri Markup — das Kürzel des Marktschorgast-Gegners wäre in jeder Ausgabe mitgedruckt worden. Die Überschrift nennt den Gegner ohnehin. */ ?> Wir - Vergleich + Gegner
- - - + + + + +

Die aktuellen Vergleichsdaten waren beim letzten Abruf nicht verfügbar.

+ + + +
+

Die Tabelle dazwischen

+ + $block, 'caption' => 'Tabellenausschnitt ' . (string) ($info['league'] ?? '')]); ?> + +
+
diff --git a/app/components/stadionzeitung-mitglied.php b/app/components/stadionzeitung-mitglied.php index ba17f92..3c6a73f 100644 --- a/app/components/stadionzeitung-mitglied.php +++ b/app/components/stadionzeitung-mitglied.php @@ -4,7 +4,7 @@ declare(strict_types=1); /** * Mitglied-werden-Seite der Stadionzeitung (Live-Seite) — Poster statt Liste: - * riesige zentrierte Coolvetica-Headline („Werde Teil des Nullachters", das + * riesige zentrierte Coolvetica-Headline („Werde Teil unseres Vereins", das * letzte Wort in Vereinsrot), darunter Claim-Satz und Abteilungszeile, * unten der geteilte CTA-Fuß mit QR auf /mitglied-werden. Fester Baustein * jeder Ausgabe (bin/stadionzeitung-add.php plant sie nach den @@ -25,8 +25,8 @@ $mitgliedLink = club_domain('mitglied-werden');

Werde Teil - des - Nullachters + unseres + Vereins

Seit 1908 in Kulmbach zuhause. Egal ob auf dem Platz, in der Halle oder am Spielfeldrand: bei uns ist Platz für dich.

Fußball · Jugend · Turnen

diff --git a/public/assets/css/components.css b/public/assets/css/components.css index 7675447..5defef8 100644 --- a/public/assets/css/components.css +++ b/public/assets/css/components.css @@ -7261,74 +7261,136 @@ a.news-feature__link:hover .news-feature__more { } /* === stadionzeitung-ergebnisse (Live-Seite) === */ -/* Ergebnis-Rückblick: zwei Zeilen pro Spiel (Heim über Gast, wie das - „Nächste Spiele"-Band) — lange Vereinsnamen passen auf der schmalen - A5-Seite nie in eine Zeile. Eigene Mannschaft über Gewicht der Body-Schrift - markiert (Abel hat echte Schnitte — anders als Coolvetica erlaubt, siehe - CLAUDE.md „nie über font-weight" bei Coolvetica). */ +/* Ergebnis-Rückblick als Anzeigetafel: Wappen und Vereinsname je Seite, das + Ergebnis groß in der Mitte (Vorbild kicker/Sofascore). Vierter Entwurf, + 19.09.2026 — die drei Vorgänger scheiterten alle an derselben Ursache: + sie waren Listenzeilen, und eine Zeile stellt Heim und Gast untereinander + statt gegenüber, obwohl eine Begegnung etwas Symmetrisches ist. Mit den + Toren rechts am Blattrand lag halbe Seitenbreite Leere zwischen Name und + Ziffer, eine getönte Karte dagegen sah „altbacken" aus, und mit den Toren + links fehlte der Seite die Hierarchie. + + Die Mittelachse löst zusätzlich das Namensproblem: lange Namen wie + „(SG1) TSV Melkendorf III/ TSV 08 Kulmbach II" umbrechen in ihrer Spalte + über mehrere Zeilen, ohne dass irgendetwas verrutscht — deshalb steht der + Name UNTER dem Wappen und mittig, nicht daneben. + + Flächen gibt es keine, nur Linien: Haarlinie zwischen den Begegnungen, + roter Strich unter der Mannschaftszeile, sonst weißes Blatt. */ +/* Trennung der Mannschaften: Haarlinie plus reichlich Abstand auf beiden + Seiten. Die Linie liegt am Abschnitt, nicht an der letzten Begegnung darin — + sonst wanderte sie bei mehreren Spielen pro Mannschaft mitten in den Block. */ .stadionzeitung-ergebnisse__team { - margin-bottom: 4.2cqi; -} - -.stadionzeitung-ergebnisse__team-name { - margin-bottom: 1.7cqi; - font-family: var(--ff-body); - font-weight: var(--fw-bold); - font-size: 2.7cqi; - text-transform: uppercase; - letter-spacing: var(--ls-caps); - color: var(--clr-text-muted); -} - -.stadionzeitung-ergebnisse__match { - padding-bottom: 1.7cqi; - margin-bottom: 1.7cqi; + padding-bottom: 4.6cqi; + margin-bottom: 4.6cqi; border-bottom: 1px solid var(--clr-glass-border); } -.stadionzeitung-ergebnisse__match:last-child { +.stadionzeitung-ergebnisse__team:last-of-type { + padding-bottom: 0; margin-bottom: 0; border-bottom: 0; } -.stadionzeitung-ergebnisse__meta { - font-size: 2.5cqi; +/* Mannschaftszeile bewusst leise: die Gliederung macht der Abstand zwischen + den Mannschaften, keine Linie (ein roter Strich darunter war gebaut und am + 19.09.2026 wieder entfernt — er zog mehr Aufmerksamkeit als die Ergebnisse + darunter). */ +.stadionzeitung-ergebnisse__team-name { + margin-bottom: 1.6cqi; + font-family: var(--ff-body); + font-size: 2.4cqi; + text-align: center; + text-transform: uppercase; + letter-spacing: var(--ls-caps); color: var(--clr-text-faint); } -/* Das Zeilen-Layout gehört auf die ZEILE, nicht auf das Wappen darin (stand - hier mal als `.stadionzeitung-ergebnisse__row .crest` — dann wurde das Wappen - selbst zum Grid, lag als Block-Element auf eigener Zeile, und Vereinsname und - Tore rutschten als Inline-Spans darunter zusammen). `--crest-size` bleibt - trotzdem hier: Custom Properties erben, das Wappen bleibt eine dumme - Komponente und der Kontext gibt ihm seine Größe — so machen es alle anderen - Wappen-Kontexte im Projekt auch. */ -.stadionzeitung-ergebnisse__row { - --crest-size: 5.8cqi; +.stadionzeitung-ergebnisse__match { display: grid; - grid-template-columns: auto 1fr auto; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; - gap: 1.7cqi; - padding-block: 0.6cqi; + gap: 2.1cqi; + padding-block: 1.4cqi; + border-bottom: 1px solid var(--clr-glass-border); } +.stadionzeitung-ergebnisse__match:last-child { + padding-bottom: 0; + border-bottom: 0; +} + +/* Beide Seiten sind gleich gebaut und gleich breit (1fr), damit die Mittelachse + wirklich in der Mitte liegt — auch wenn eine Spielgemeinschaft drei Zeilen + Namen mitbringt und der Gegner eine. `--crest-size` setzt der Kontext, die + crest-Komponente bleibt dumm (gleiches Muster wie in allen anderen + Wappen-Kontexten). */ +.stadionzeitung-ergebnisse__side { + --crest-size: 8.5cqi; + display: grid; + justify-items: center; + gap: 1.2cqi; + text-align: center; +} + +/* Einzeilig, notfalls ausgepunktet: ein umbrechender Name schiebt die + Anzeigetafel auseinander und die Zeilenpaare stehen dann ungleich hoch + nebeneinander. Die volle Schreibweise steht ohnehin im Matchcenter. */ .stadionzeitung-ergebnisse__club { - min-width: 0; - font-size: 3.75cqi; - line-height: 1.2; + max-width: 100%; + overflow: hidden; + font-size: 3.2cqi; + line-height: 1.15; + white-space: nowrap; + text-overflow: ellipsis; } -.stadionzeitung-ergebnisse__row--own .stadionzeitung-ergebnisse__club { +.stadionzeitung-ergebnisse__side--own .stadionzeitung-ergebnisse__club { font-weight: var(--fw-bold); } -.stadionzeitung-ergebnisse__goals { +.stadionzeitung-ergebnisse__score { + display: grid; + justify-items: center; + gap: 0.8cqi; +} + +.stadionzeitung-ergebnisse__result { + display: flex; + align-items: baseline; + gap: 0.8cqi; font-family: var(--ff-heading); - font-size: 5.8cqi; + font-size: 7.4cqi; line-height: 1; } +/* Das Ergebnis läuft durchgehend in voller Textfarbe: eine zurückgenommene + Verlierer-Ziffer war gebaut und am 19.09.2026 verworfen (Felix) — auf dem + gedruckten Blatt ist eine graue Ziffer schlechter lesbar, und wer gewonnen + hat, sagt die Zahl selbst. */ +.stadionzeitung-ergebnisse__meta { + font-size: 2.5cqi; + text-transform: uppercase; + letter-spacing: var(--ls-caps); + color: var(--clr-text-faint); + white-space: nowrap; +} + /* === stadionzeitung-gegner (Live-Seite) === */ +/* Gegnerseite als Duell-Aufmacher plus Balkenvergleich (19.09.2026). Vorher + war das eine rosa getönte Karte mit der Begegnung und darunter eine + Zahlenkolonne aus sieben Zeilen: inhaltlich richtig, gestalterisch ohne + Blickfang, und die untere Blatthälfte blieb leer. + + Drei Entscheidungen tragen den Aufbau: + - Das GEGNERWAPPEN ist der Bildanker (deutlich größer als unseres). Die + Seite handelt von ihm, nicht von der Begegnung. + - Der Tabellenplatz steht oben bei den Wappen, NICHT als Balken: dort ist + klein besser, ein Balken läge genau falsch herum. + - Der Vergleich läuft als Balkenpaar aus der Mitte (Vorbild Sofascore). + Die Länge kommt aus einer KLASSE in 5-Prozent-Stufen, nie aus einem + style-Attribut: die CSP verwirft inline styles stillschweigend, der + Balken wäre in Produktion unsichtbar (siehe CLAUDE.md). */ .stadionzeitung-gegner { min-height: 100%; } @@ -7336,90 +7398,204 @@ a.news-feature__link:hover .news-feature__more { .stadionzeitung-gegner__duell { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); - align-items: center; + align-items: start; gap: 2.5cqi; - margin-bottom: 5cqi; - padding: 3.3cqi; - border: 1px solid var(--clr-glass-border); - border-radius: var(--radius-btn); - background: color-mix(in srgb, var(--clr-accent) 5%, #fff); + margin-top: 2.5cqi; } +/* Beide Seiten sind gleich gebaut und gleich groß. Der Gegner war hier kurz + mit doppelt so großem Wappen und seinem Namen in Coolvetica gesetzt — damit + stand er zweimal laut auf derselben Seite (Überschrift und Duell) und das + Blatt wirkte unruhig (Felix, 19.09.2026). Die Überschrift nennt ihn, das + Duell zeigt nur noch, wer gegen wen und auf welchem Platz. */ .stadionzeitung-gegner__club { - --crest-size: 12cqi; - display: flex; - flex-direction: column; - align-items: center; - gap: 1.7cqi; + --crest-size: 11cqi; + display: grid; + grid-template-rows: auto minmax(7.4cqi, auto); + justify-items: center; + gap: 1.2cqi; min-width: 0; text-align: center; } -.stadionzeitung-gegner__club strong { +.stadionzeitung-gegner__club-name { font-size: 3.2cqi; line-height: 1.15; } -.stadionzeitung-gegner__club--own strong { - color: var(--clr-accent); -} - -.stadionzeitung-gegner__match { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.5cqi; - font-size: 2.5cqi; - color: var(--clr-text-muted); +/* Mittelspalte: „gegen" und der Anstoß stehen zusammen, damit im Duell nur + noch drei Dinge auf einer Höhe liegen (wir, wann, sie). Zwei lose + Platz-Ziffern und eine freistehende Datumszeile standen hier kurz und + machten den Kopf unruhig (Felix, 19.09.2026) — der Tabellenplatz steht + jetzt im Ausschnitt unter den Balken. */ +.stadionzeitung-gegner__vs { + display: grid; + justify-items: center; + gap: 1.2cqi; + padding-top: 3.3cqi; + text-align: center; white-space: nowrap; } -.stadionzeitung-gegner__match strong { - font-size: 2.9cqi; - color: var(--clr-text); +.stadionzeitung-gegner__vs-wort { + font-size: 2.7cqi; + text-transform: uppercase; + letter-spacing: var(--ls-caps); + color: var(--clr-text-faint); +} + +.stadionzeitung-gegner__anstoss { + font-size: 2.7cqi; + line-height: 1.4; + color: var(--clr-text-muted); +} + +/* Im Ausschnitt entfällt die Zonen-Legende: sie erklärt Auf- und Abstieg, + und das ist die Aufgabe der vollständigen Tabellenseite weiter hinten im + Heft. Hier geht es nur um die Nachbarschaft der beiden Mannschaften. */ +.stadionzeitung-gegner__ausschnitt { + margin-top: 3.3cqi; +} + +.stadionzeitung-gegner__ausschnitt .league-table__legend { + display: none; +} + +/* Der Ausschnitt läuft eine Spur enger als die vollständige Tabellenseite: + er teilt sich das Blatt mit Duell, Aufmacher und Balken. Gemessen: mit den + Maßen der Tabellenseite stand die Seite 16px über der A5-Kante. */ +.stadionzeitung-gegner__ausschnitt .league-table th, +.stadionzeitung-gegner__ausschnitt .league-table td { + padding: 0.7cqi 0.9cqi; +} + +.stadionzeitung-gegner__ausschnitt .league-table tbody .crest { + --crest-size: 4cqi; +} + +.stadionzeitung-gegner__ausschnitt-titel { + margin-bottom: 1.4cqi; + font-size: 2.4cqi; + text-transform: uppercase; + letter-spacing: var(--ls-caps); + color: var(--clr-text-faint); +} + +/* Zwei Blöcke (wenn die Mannschaften weit auseinanderliegen) bekommen Luft + dazwischen, sonst sähe es aus wie eine durchgehende Tabelle mit Sprung. */ +.stadionzeitung-gegner__ausschnitt .table-scroll-frame + .table-scroll-frame { + margin-top: 2.1cqi; +} + +/* Die Tabelle in einem Satz: derselbe Bestand, nur in Sprache. Coolvetica + trägt hier, weil die Zeile kurz ist und nie umbricht. */ +.stadionzeitung-gegner__aufmacher { + margin-top: 3.3cqi; + padding-top: 2.5cqi; + border-top: 0.5cqi solid var(--clr-accent); + font-family: var(--ff-heading); + font-size: 5.8cqi; + line-height: 1; + letter-spacing: var(--ls-heading); + text-transform: uppercase; } .stadionzeitung-gegner__vergleich { - border-block: 1px solid var(--clr-glass-border); + margin-top: 2.5cqi; } .stadionzeitung-gegner__vergleich-head, .stadionzeitung-gegner__vergleich-row { display: grid; - grid-template-columns: 1fr 2fr 1fr; + grid-template-columns: 6cqi minmax(0, 1fr) 24cqi minmax(0, 1fr) 6cqi; align-items: center; + gap: 1.7cqi; text-align: center; } .stadionzeitung-gegner__vergleich-head { - padding-block: 1.7cqi; - font-size: 2.5cqi; - font-weight: var(--fw-bold); + padding-bottom: 1.4cqi; + margin-bottom: 1.4cqi; + border-bottom: 1px solid var(--clr-glass-border); + font-size: 2.4cqi; text-transform: uppercase; letter-spacing: var(--ls-caps); - color: var(--clr-text-muted); + color: var(--clr-text-faint); +} + +/* Die beiden äußeren Spaltenköpfe sitzen über den Zahlen, der mittlere über + dem Label — deshalb spannt er über die drei inneren Spalten. */ +.stadionzeitung-gegner__vergleich-head span:nth-child(2) { + grid-column: 2 / 5; } .stadionzeitung-gegner__vergleich-row { - min-height: 6.5cqi; - border-top: 1px solid var(--clr-glass-border); + min-height: 5.8cqi; } -.stadionzeitung-gegner__vergleich-row span { +.stadionzeitung-gegner__wert { + font-family: var(--ff-heading); + font-size: 4.4cqi; + line-height: 1; +} + +.stadionzeitung-gegner__label { font-size: 2.8cqi; color: var(--clr-text-muted); } -.stadionzeitung-gegner__vergleich-row strong { - font-family: var(--ff-heading); - font-size: 4.8cqi; - line-height: 1; +/* Balkenpaar: der linke wächst aus der Mitte nach links, der rechte nach + rechts. Die Spur bleibt sichtbar (helle Fläche), damit auch ein Wert von + null als Zeile lesbar ist. */ +.stadionzeitung-gegner__balken { + display: flex; + justify-content: flex-start; + block-size: 2.2cqi; + border-radius: 1.1cqi; + background: var(--clr-glass-border); + overflow: hidden; } -.stadionzeitung-gegner__vergleich-row strong:first-child { - color: var(--clr-accent); +.stadionzeitung-gegner__balken--own { + justify-content: flex-end; } +.stadionzeitung-gegner__balken-fuellung { + display: block; + block-size: 100%; + border-radius: 1.1cqi; + background: var(--clr-text); +} + +/* Unsere Seite rot, die des Gegners schwarz: die einzige Farbe der Seite + steht damit bei uns, ohne dass Rot Fließtext tragen müsste. */ +.stadionzeitung-gegner__balken--own .stadionzeitung-gegner__balken-fuellung { + background: var(--clr-accent); +} + +/* 5-Prozent-Stufen, siehe Kommentar oben (kein style-Attribut). */ +.stadionzeitung-gegner__balken-fuellung--s0 { inline-size: 0%; } +.stadionzeitung-gegner__balken-fuellung--s1 { inline-size: 5%; } +.stadionzeitung-gegner__balken-fuellung--s2 { inline-size: 10%; } +.stadionzeitung-gegner__balken-fuellung--s3 { inline-size: 15%; } +.stadionzeitung-gegner__balken-fuellung--s4 { inline-size: 20%; } +.stadionzeitung-gegner__balken-fuellung--s5 { inline-size: 25%; } +.stadionzeitung-gegner__balken-fuellung--s6 { inline-size: 30%; } +.stadionzeitung-gegner__balken-fuellung--s7 { inline-size: 35%; } +.stadionzeitung-gegner__balken-fuellung--s8 { inline-size: 40%; } +.stadionzeitung-gegner__balken-fuellung--s9 { inline-size: 45%; } +.stadionzeitung-gegner__balken-fuellung--s10 { inline-size: 50%; } +.stadionzeitung-gegner__balken-fuellung--s11 { inline-size: 55%; } +.stadionzeitung-gegner__balken-fuellung--s12 { inline-size: 60%; } +.stadionzeitung-gegner__balken-fuellung--s13 { inline-size: 65%; } +.stadionzeitung-gegner__balken-fuellung--s14 { inline-size: 70%; } +.stadionzeitung-gegner__balken-fuellung--s15 { inline-size: 75%; } +.stadionzeitung-gegner__balken-fuellung--s16 { inline-size: 80%; } +.stadionzeitung-gegner__balken-fuellung--s17 { inline-size: 85%; } +.stadionzeitung-gegner__balken-fuellung--s18 { inline-size: 90%; } +.stadionzeitung-gegner__balken-fuellung--s19 { inline-size: 95%; } +.stadionzeitung-gegner__balken-fuellung--s20 { inline-size: 100%; } + .stadionzeitung-gegner__empty { padding: 4.2cqi; border: 1px solid var(--clr-glass-border);