Compare commits
2 Commits
fef80bf669
...
0ee75aa93b
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ee75aa93b | |||
| 8f864368f0 |
45
CLAUDE.md
@@ -54,7 +54,7 @@ selbst gehosteter Stack ohne Abhängigkeiten von Drittanbietern.
|
|||||||
| Partner/Sponsoren | `data/partners.json` | — |
|
| Partner/Sponsoren | `data/partners.json` | — |
|
||||||
| Instagram-Beiträge | `data/instagram.json` + `public/assets/img/instagram/` | Aufgenommen ausschließlich mit **`php bin/instagram-add.php <post-url>`** (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 |
|
| Instagram-Beiträge | `data/instagram.json` + `public/assets/img/instagram/` | Aufgenommen ausschließlich mit **`php bin/instagram-add.php <post-url>`** (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`) |
|
| 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 |
|
| 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) |
|
| 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`) |
|
| 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
|
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.
|
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 —
|
**Cover (automatisch generiert, Phase 3):** einzige fotografische Stadionzeitungs-Seite —
|
||||||
`app/components/stadionzeitung-cover.php`. Echtes Magazin-Cover statt Website-Baustein: **kein**
|
`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
|
Stadionfoto mehr als Hintergrund, stattdessen ein ruhiger Verlauf in Vereinsfarben, auf dem das
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ $issues = $issues ?? [];
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="stadionzeitung-card__body">
|
<div class="stadionzeitung-card__body">
|
||||||
<h3 class="stadionzeitung-card__name">Stadionzeitung zum Heimspiel gegen <?= e($issue['opponent']) ?></h3>
|
<h3 class="stadionzeitung-card__name">Stadionzeitung zum Heimspiel gegen <?= e(stadionzeitung_opponent_display($issue)) ?></h3>
|
||||||
<p class="stadionzeitung-card__meta">
|
<p class="stadionzeitung-card__meta">
|
||||||
<time datetime="<?= e($issue['date']) ?>"><?= e(german_date($issue['date'])) ?></time>
|
<time datetime="<?= e($issue['date']) ?>"><?= e(german_date($issue['date'])) ?></time>
|
||||||
· <?= (int) $issue['pages'] ?> Seiten
|
· <?= (int) $issue['pages'] ?> Seiten
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ declare(strict_types=1);
|
|||||||
* der Gegner läuft über den Initialen-Fallback der crest-Komponente.
|
* der Gegner läuft über den Initialen-Fallback der crest-Komponente.
|
||||||
*/
|
*/
|
||||||
$issue = $issue ?? [];
|
$issue = $issue ?? [];
|
||||||
$opponent = (string) ($issue['opponent'] ?? '');
|
$opponent = stadionzeitung_opponent_display($issue);
|
||||||
$date = (string) ($issue['date'] ?? '');
|
$date = (string) ($issue['date'] ?? '');
|
||||||
$teamKey = (string) ($issue['team_key'] ?? '');
|
$teamKey = (string) ($issue['team_key'] ?? '');
|
||||||
$playerImage = $issue['player_image'] ?? null;
|
$playerImage = $issue['player_image'] ?? null;
|
||||||
|
|||||||
@@ -40,19 +40,28 @@ $matchcenterLink = club_domain('matchcenter');
|
|||||||
<section class="stadionzeitung-ergebnisse__team">
|
<section class="stadionzeitung-ergebnisse__team">
|
||||||
<h3 class="stadionzeitung-ergebnisse__team-name"><?= e(trim(implode(' · ', array_filter([(string) ($group['team_name'] ?? ''), (string) ($group['league'] ?? '')])))) ?></h3>
|
<h3 class="stadionzeitung-ergebnisse__team-name"><?= e(trim(implode(' · ', array_filter([(string) ($group['team_name'] ?? ''), (string) ($group['league'] ?? '')])))) ?></h3>
|
||||||
<?php foreach ($group['matches'] ?? [] as $match): ?>
|
<?php foreach ($group['matches'] ?? [] as $match): ?>
|
||||||
<div class="stadionzeitung-ergebnisse__match">
|
<?php
|
||||||
<p class="stadionzeitung-ergebnisse__meta">
|
$sides = [
|
||||||
<?= e(trim((string) ($match['date_display'] ?? ''))) ?><?php if (($match['competition'] ?? 'Liga') !== 'Liga'): ?> · <?= e((string) $match['competition']) ?><?php endif; ?>
|
['club' => $match['home'] ?? '', 'crest' => $match['home_crest'] ?? null, 'goals' => $match['goals_home'] ?? null],
|
||||||
</p>
|
['club' => $match['away'] ?? '', 'crest' => $match['away_crest'] ?? null, 'goals' => $match['goals_away'] ?? null],
|
||||||
<?php foreach ([['club' => $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): ?>
|
?>
|
||||||
<div class="stadionzeitung-ergebnisse__row<?= str_contains((string) $side['club'], 'TSV 08 Kulmbach') ? ' stadionzeitung-ergebnisse__row--own' : '' ?>">
|
<article class="stadionzeitung-ergebnisse__match">
|
||||||
|
<?php foreach ($sides as $i => $side): ?>
|
||||||
|
<?php if ($i === 1): ?>
|
||||||
|
<div class="stadionzeitung-ergebnisse__score">
|
||||||
|
<p class="stadionzeitung-ergebnisse__result">
|
||||||
|
<span class="stadionzeitung-ergebnisse__goals"><?= e((string) $sides[0]['goals']) ?></span><span class="stadionzeitung-ergebnisse__colon">:</span><span class="stadionzeitung-ergebnisse__goals"><?= e((string) $sides[1]['goals']) ?></span>
|
||||||
|
</p>
|
||||||
|
<p class="stadionzeitung-ergebnisse__meta"><?= e(trim((string) ($match['date_display'] ?? ''))) ?><?php if (($match['competition'] ?? 'Liga') !== 'Liga'): ?> · <?= e((string) $match['competition']) ?><?php endif; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="stadionzeitung-ergebnisse__side<?= str_contains((string) $side['club'], 'TSV 08 Kulmbach') ? ' stadionzeitung-ergebnisse__side--own' : '' ?>">
|
||||||
<?php component('crest', ['src' => $side['crest'], 'name' => (string) $side['club']]); ?>
|
<?php component('crest', ['src' => $side['crest'], 'name' => (string) $side['club']]); ?>
|
||||||
<span class="stadionzeitung-ergebnisse__club"><?= e((string) $side['club']) ?></span>
|
<span class="stadionzeitung-ergebnisse__club"><?= e((string) $side['club']) ?></span>
|
||||||
<span class="stadionzeitung-ergebnisse__goals"><?= e((string) $side['goals']) ?></span>
|
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</article>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</section>
|
</section>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|||||||
@@ -18,15 +18,29 @@ $teamName = trim((string) ($info['team_name'] ?? ''));
|
|||||||
$fixture = is_array($info['fixture'] ?? null) ? $info['fixture'] : [];
|
$fixture = is_array($info['fixture'] ?? null) ? $info['fixture'] : [];
|
||||||
$own = is_array($info['own'] ?? null) ? $info['own'] : null;
|
$own = is_array($info['own'] ?? null) ? $info['own'] : null;
|
||||||
$opponent = is_array($info['opponent_stats'] ?? null) ? $info['opponent_stats'] : 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'] ?? '');
|
$kickoff = (string) ($fixture['kickoff'] ?? '');
|
||||||
$kickoffDate = $kickoff !== '' ? substr($kickoff, 0, 10) : '';
|
$kickoffDate = $kickoff !== '' ? substr($kickoff, 0, 10) : '';
|
||||||
$kickoffTime = $kickoff !== '' ? substr($kickoff, 11, 5) : '';
|
$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 = [
|
$rows = [
|
||||||
['label' => 'Tabellenplatz', 'key' => 'rank'],
|
|
||||||
['label' => 'Spiele', 'key' => 'played'],
|
|
||||||
['label' => 'Siege', 'key' => 'won'],
|
['label' => 'Siege', 'key' => 'won'],
|
||||||
['label' => 'Unentschieden', 'key' => 'drawn'],
|
['label' => 'Unentschieden', 'key' => 'drawn'],
|
||||||
['label' => 'Niederlagen', 'key' => 'lost'],
|
['label' => 'Niederlagen', 'key' => 'lost'],
|
||||||
@@ -41,6 +55,57 @@ $format = static function (?array $row, string $key, bool $signed = false): stri
|
|||||||
$value = (int) $row[$key];
|
$value = (int) $row[$key];
|
||||||
return $signed && $value > 0 ? '+' . $value : (string) $value;
|
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'
|
||||||
|
: '';
|
||||||
?>
|
?>
|
||||||
<div class="stadionzeitung-gegner">
|
<div class="stadionzeitung-gegner">
|
||||||
<header class="stadionzeitung-page__head">
|
<header class="stadionzeitung-page__head">
|
||||||
@@ -53,25 +118,26 @@ $format = static function (?array $row, string $key, bool $signed = false): stri
|
|||||||
<span class="stadionzeitung-page__rule" aria-hidden="true"></span>
|
<span class="stadionzeitung-page__rule" aria-hidden="true"></span>
|
||||||
|
|
||||||
<div class="stadionzeitung-gegner__duell">
|
<div class="stadionzeitung-gegner__duell">
|
||||||
<div class="stadionzeitung-gegner__club stadionzeitung-gegner__club--own">
|
<div class="stadionzeitung-gegner__club">
|
||||||
<?php component('crest', ['src' => $fixture['home_crest'] ?? $own['crest'] ?? null, 'name' => $fixture['home'] ?? 'TSV 08 Kulmbach']); ?>
|
<?php component('crest', ['src' => $ownCrest, 'name' => $ownName]); ?>
|
||||||
<strong><?= e((string) ($fixture['home'] ?? 'TSV 08 Kulmbach')) ?></strong>
|
<span class="stadionzeitung-gegner__club-name"><?= e($ownName) ?></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stadionzeitung-gegner__match">
|
<div class="stadionzeitung-gegner__vs">
|
||||||
<span>gegen</span>
|
<p class="stadionzeitung-gegner__vs-wort"><?= $istHeim ? 'gegen' : 'bei' ?></p>
|
||||||
<?php if ($kickoffDate !== ''): ?>
|
<?php if ($kickoffDate !== ''): ?>
|
||||||
<strong><?= e(german_date($kickoffDate)) ?></strong>
|
<p class="stadionzeitung-gegner__anstoss"><?= e(german_date($kickoffDate)) ?><?php if ($kickoffTime !== ''): ?><br><?= e($kickoffTime) ?> Uhr<?php endif; ?></p>
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($kickoffTime !== ''): ?>
|
|
||||||
<span><?= e($kickoffTime) ?> Uhr</span>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="stadionzeitung-gegner__club">
|
<div class="stadionzeitung-gegner__club">
|
||||||
<?php component('crest', ['src' => $fixture['away_crest'] ?? $opponent['crest'] ?? null, 'name' => $fixture['away'] ?? $opponentName]); ?>
|
<?php component('crest', ['src' => $gegnerCrest, 'name' => $gegnerLabel]); ?>
|
||||||
<strong><?= e((string) ($fixture['away'] ?? $opponentName)) ?></strong>
|
<span class="stadionzeitung-gegner__club-name"><?= e($gegnerLabel) ?></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php if ($aufmacher !== ''): ?>
|
||||||
|
<p class="stadionzeitung-gegner__aufmacher"><?= e($aufmacher) ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($own !== null && $opponent !== null): ?>
|
<?php if ($own !== null && $opponent !== null): ?>
|
||||||
<div class="stadionzeitung-gegner__vergleich" role="table" aria-label="Tabellenvergleich">
|
<div class="stadionzeitung-gegner__vergleich" role="table" aria-label="Tabellenvergleich">
|
||||||
<div class="stadionzeitung-gegner__vergleich-head" role="row">
|
<div class="stadionzeitung-gegner__vergleich-head" role="row">
|
||||||
@@ -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
|
Markup — das Kürzel des Marktschorgast-Gegners wäre in jeder Ausgabe
|
||||||
mitgedruckt worden. Die Überschrift nennt den Gegner ohnehin. */ ?>
|
mitgedruckt worden. Die Überschrift nennt den Gegner ohnehin. */ ?>
|
||||||
<span role="columnheader">Wir</span>
|
<span role="columnheader">Wir</span>
|
||||||
<span role="columnheader">Vergleich</span>
|
<span role="columnheader"><?= e($spieltagInfo !== '' ? $spieltagInfo : 'Vergleich') ?></span>
|
||||||
<span role="columnheader">Gegner</span>
|
<span role="columnheader">Gegner</span>
|
||||||
</div>
|
</div>
|
||||||
<?php foreach ($rows as $row): ?>
|
<?php foreach ($rows as $row): ?>
|
||||||
<div class="stadionzeitung-gegner__vergleich-row" role="row">
|
<div class="stadionzeitung-gegner__vergleich-row" role="row">
|
||||||
<strong role="cell"><?= e($format($own, (string) $row['key'], (bool) ($row['signed'] ?? false))) ?></strong>
|
<strong class="stadionzeitung-gegner__wert" role="cell"><?= e($format($own, (string) $row['key'], (bool) ($row['signed'] ?? false))) ?></strong>
|
||||||
<span role="rowheader"><?= e((string) $row['label']) ?></span>
|
<span class="stadionzeitung-gegner__balken stadionzeitung-gegner__balken--own" aria-hidden="true"><span class="stadionzeitung-gegner__balken-fuellung stadionzeitung-gegner__balken-fuellung--s<?= $barStep($own, $opponent, (string) $row['key']) ?>"></span></span>
|
||||||
<strong role="cell"><?= e($format($opponent, (string) $row['key'], (bool) ($row['signed'] ?? false))) ?></strong>
|
<span class="stadionzeitung-gegner__label" role="rowheader"><?= e((string) $row['label']) ?></span>
|
||||||
|
<span class="stadionzeitung-gegner__balken" aria-hidden="true"><span class="stadionzeitung-gegner__balken-fuellung stadionzeitung-gegner__balken-fuellung--s<?= $barStep($opponent, $own, (string) $row['key']) ?>"></span></span>
|
||||||
|
<strong class="stadionzeitung-gegner__wert" role="cell"><?= e($format($opponent, (string) $row['key'], (bool) ($row['signed'] ?? false))) ?></strong>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p class="stadionzeitung-gegner__empty">Die aktuellen Vergleichsdaten waren beim letzten Abruf nicht verfügbar.</p>
|
<p class="stadionzeitung-gegner__empty">Die aktuellen Vergleichsdaten waren beim letzten Abruf nicht verfügbar.</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php /* Der Tabellenplatz stand kurz als zwei lose Ziffern oben im Duell und
|
||||||
|
wirkte dort unruhig (Felix, 19.09.2026). Hier zeigt ihn der Ausschnitt
|
||||||
|
im Zusammenhang: wer steht zwischen uns und ihnen. Gerendert mit der
|
||||||
|
geteilten league-table-Komponente, nie mit einer zweiten Tabelle. */ ?>
|
||||||
|
<?php if ($ausschnitt !== []): ?>
|
||||||
|
<div class="stadionzeitung-gegner__ausschnitt">
|
||||||
|
<p class="stadionzeitung-gegner__ausschnitt-titel">Die Tabelle dazwischen</p>
|
||||||
|
<?php foreach ($ausschnitt as $block): ?>
|
||||||
|
<?php component('league-table', ['table' => $block, 'caption' => 'Tabellenausschnitt ' . (string) ($info['league'] ?? '')]); ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Mitglied-werden-Seite der Stadionzeitung (Live-Seite) — Poster statt Liste:
|
* 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,
|
* letzte Wort in Vereinsrot), darunter Claim-Satz und Abteilungszeile,
|
||||||
* unten der geteilte CTA-Fuß mit QR auf /mitglied-werden. Fester Baustein
|
* unten der geteilte CTA-Fuß mit QR auf /mitglied-werden. Fester Baustein
|
||||||
* jeder Ausgabe (bin/stadionzeitung-add.php plant sie nach den
|
* jeder Ausgabe (bin/stadionzeitung-add.php plant sie nach den
|
||||||
@@ -25,8 +25,8 @@ $mitgliedLink = club_domain('mitglied-werden');
|
|||||||
<img class="stadionzeitung-mitglied__logo" src="<?= e(asset('img/logo.svg')) ?>" alt="" width="75" height="97">
|
<img class="stadionzeitung-mitglied__logo" src="<?= e(asset('img/logo.svg')) ?>" alt="" width="75" height="97">
|
||||||
<h2 class="stadionzeitung-mitglied__headline">
|
<h2 class="stadionzeitung-mitglied__headline">
|
||||||
<span class="stadionzeitung-mitglied__line">Werde Teil</span>
|
<span class="stadionzeitung-mitglied__line">Werde Teil</span>
|
||||||
<span class="stadionzeitung-mitglied__line">des</span>
|
<span class="stadionzeitung-mitglied__line">unseres</span>
|
||||||
<span class="stadionzeitung-mitglied__line stadionzeitung-mitglied__line--accent">Nullachters</span>
|
<span class="stadionzeitung-mitglied__line stadionzeitung-mitglied__line--accent">Vereins</span>
|
||||||
</h2>
|
</h2>
|
||||||
<p class="stadionzeitung-mitglied__claim">Seit 1908 in Kulmbach zuhause. Egal ob auf dem Platz, in der Halle oder am Spielfeldrand: bei uns ist Platz für dich.</p>
|
<p class="stadionzeitung-mitglied__claim">Seit 1908 in Kulmbach zuhause. Egal ob auf dem Platz, in der Halle oder am Spielfeldrand: bei uns ist Platz für dich.</p>
|
||||||
<p class="stadionzeitung-mitglied__departments">Fußball · Jugend · Turnen</p>
|
<p class="stadionzeitung-mitglied__departments">Fußball · Jugend · Turnen</p>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ $total = count($images);
|
|||||||
if ($images === []) {
|
if ($images === []) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$opponent = (string) ($issue['opponent'] ?? '');
|
$opponent = stadionzeitung_opponent_display($issue);
|
||||||
|
|
||||||
// Live-Daten nur laden, wenn die Ausgabe tatsächlich eine Live-Seite enthält.
|
// Live-Daten nur laden, wenn die Ausgabe tatsächlich eine Live-Seite enthält.
|
||||||
$hasLive = false;
|
$hasLive = false;
|
||||||
|
|||||||
103
app/helpers.php
@@ -992,6 +992,22 @@ function stadionzeitung_teaser(array $issue): string
|
|||||||
return trim((string) ($issue['interview']['teaser'] ?? ''));
|
return trim((string) ($issue['interview']['teaser'] ?? ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Name des Gegners, wie er GEDRUCKT wird — Cover, Archivzeile, Kopf, Meta.
|
||||||
|
*
|
||||||
|
* `opponent` muss die BFV-Schreibweise tragen, weil die Gegnerseite damit ihre
|
||||||
|
* Tabellenzeile und ihre Begegnung in matchcenter.json findet (exakter
|
||||||
|
* 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` ist deshalb der im Chat gepflegte Anzeigename
|
||||||
|
* („Wirsberg/Kupferberg"); fehlt er, bleibt es beim vollen Namen.
|
||||||
|
*/
|
||||||
|
function stadionzeitung_opponent_display(array $issue): string
|
||||||
|
{
|
||||||
|
$short = trim((string) ($issue['opponent_short'] ?? ''));
|
||||||
|
return $short !== '' ? $short : trim((string) ($issue['opponent'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Welche Mannschaften bekommen in einer Ausgabe eine Gegnerseite — die EINE
|
* Welche Mannschaften bekommen in einer Ausgabe eine Gegnerseite — die EINE
|
||||||
* Logik dafür, benutzt von bin/stadionzeitung-add.php (Seitenplanung) UND
|
* Logik dafür, benutzt von bin/stadionzeitung-add.php (Seitenplanung) UND
|
||||||
@@ -999,10 +1015,19 @@ function stadionzeitung_teaser(array $issue): string
|
|||||||
* oder Daten ohne Seite entstehen.
|
* oder Daten ohne Seite entstehen.
|
||||||
*
|
*
|
||||||
* Immer: die tragende Mannschaft (Gegner = `opponent`, Tag = `date`).
|
* Immer: die tragende Mannschaft (Gegner = `opponent`, Tag = `date`).
|
||||||
* Zusätzlich, wenn die Ausgabe eine `veranstaltung` (Slug) mit Zeitraum trägt
|
* Zusätzlich auf zwei Wegen, die sich nicht in die Quere kommen:
|
||||||
* (Sportfest-Ausgabe): jede weitere Mannschaft aus matchcenter.json mit einem
|
*
|
||||||
* HEIMspiel in diesem Zeitraum — drei Heimspiele am Fest, drei Gegnerseiten.
|
* 1. `veranstaltung` (Slug) mit Zeitraum — die Sportfest-Ausgabe: jede weitere
|
||||||
* Der Gegner ist dann der andere Verein der Begegnung. Reihenfolge wie in
|
* Mannschaft aus matchcenter.json mit einem HEIMspiel in diesem Zeitraum.
|
||||||
|
* Am Fest spielen alle zuhause, das ist der Aufhänger.
|
||||||
|
* 2. `gegner_teams` (Liste von Team-Keys, im Chat gepflegt) — für ein normales
|
||||||
|
* Spielwochenende, an dem auch die anderen Mannschaften antreten. Genommen
|
||||||
|
* wird ihr nächstes Spiel im Fenster von zwei Tagen vor bis drei Tage nach
|
||||||
|
* dem Ausgabedatum, AUCH auswärts: der Gegner der Frauen ist auch dann eine
|
||||||
|
* Seite wert, wenn wir zu ihm fahren. Ohne Spiel in diesem Fenster entsteht
|
||||||
|
* keine Seite; bin/stadionzeitung-add.php meldet das.
|
||||||
|
*
|
||||||
|
* Der Gegner ist jeweils der andere Verein der Begegnung. Reihenfolge wie in
|
||||||
* matchcenter.json (= config 'matchcenter.teams').
|
* matchcenter.json (= config 'matchcenter.teams').
|
||||||
*
|
*
|
||||||
* Rückgabe: [team_key => ['opponent' => string, 'date' => 'YYYY-MM-DD']]
|
* Rückgabe: [team_key => ['opponent' => string, 'date' => 'YYYY-MM-DD']]
|
||||||
@@ -1013,14 +1038,19 @@ function stadionzeitung_gegner_fixtures(array $issue): array
|
|||||||
$issueTeam = (string) ($issue['team_key'] ?? '');
|
$issueTeam = (string) ($issue['team_key'] ?? '');
|
||||||
$issueOpponent = trim((string) ($issue['opponent'] ?? ''));
|
$issueOpponent = trim((string) ($issue['opponent'] ?? ''));
|
||||||
|
|
||||||
$von = '';
|
$eventVon = '';
|
||||||
$bis = '';
|
$eventBis = '';
|
||||||
$veranstaltung = veranstaltung_find((string) ($issue['veranstaltung'] ?? ''));
|
$veranstaltung = veranstaltung_find((string) ($issue['veranstaltung'] ?? ''));
|
||||||
if ($veranstaltung !== null) {
|
if ($veranstaltung !== null) {
|
||||||
$von = (string) ($veranstaltung['date_start'] ?? '');
|
$eventVon = (string) ($veranstaltung['date_start'] ?? '');
|
||||||
$bis = (string) ($veranstaltung['date_end'] ?? $von);
|
$eventBis = (string) ($veranstaltung['date_end'] ?? $eventVon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$extraTeams = array_values(array_filter(array_map(
|
||||||
|
static fn ($k): string => trim((string) $k),
|
||||||
|
is_array($issue['gegner_teams'] ?? null) ? $issue['gegner_teams'] : []
|
||||||
|
)));
|
||||||
|
|
||||||
$fixtures = [];
|
$fixtures = [];
|
||||||
foreach (json_load('matchcenter')['teams'] ?? [] as $team) {
|
foreach (json_load('matchcenter')['teams'] ?? [] as $team) {
|
||||||
$key = (string) ($team['key'] ?? '');
|
$key = (string) ($team['key'] ?? '');
|
||||||
@@ -1033,18 +1063,32 @@ function stadionzeitung_gegner_fixtures(array $issue): array
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Weg 2: ausdrücklich benannte Mannschaft, Fenster ums Ausgabedatum,
|
||||||
|
// Heim wie auswärts. Steht sie in `gegner_teams`, gilt dieses Fenster
|
||||||
|
// und nicht der Veranstaltungszeitraum.
|
||||||
|
if (in_array($key, $extraTeams, true) && $date !== '') {
|
||||||
|
$von = date('Y-m-d', strtotime($date . ' -2 days'));
|
||||||
|
$bis = date('Y-m-d', strtotime($date . ' +3 days'));
|
||||||
|
$nurHeim = false;
|
||||||
|
} else {
|
||||||
|
$von = $eventVon;
|
||||||
|
$bis = $eventBis;
|
||||||
|
$nurHeim = true;
|
||||||
|
}
|
||||||
if ($von === '') {
|
if ($von === '') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
foreach ($team['next_matches'] ?? [] as $match) {
|
foreach ($team['next_matches'] ?? [] as $match) {
|
||||||
if (empty($match['is_home'])) {
|
$istHeim = !empty($match['is_home']);
|
||||||
|
if ($nurHeim && !$istHeim) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$matchDate = substr((string) ($match['kickoff'] ?? ''), 0, 10);
|
$matchDate = substr((string) ($match['kickoff'] ?? ''), 0, 10);
|
||||||
if ($matchDate < $von || $matchDate > $bis) {
|
if ($matchDate < $von || $matchDate > $bis) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$opponent = trim((string) ($match['away'] ?? ''));
|
$opponent = trim((string) ($istHeim ? ($match['away'] ?? '') : ($match['home'] ?? '')));
|
||||||
if ($opponent === '' || $opponent === 'SPIELFREI') {
|
if ($opponent === '' || $opponent === 'SPIELFREI') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1140,14 +1184,53 @@ function stadionzeitung_build_snapshot(array $issue): array
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `opponent` behält die BFV-Schreibweise (der Namensvergleich oben
|
||||||
|
// hängt daran), gedruckt wird `opponent_display`: bei der tragenden
|
||||||
|
// Mannschaft der Kurzname der Ausgabe, sonst der volle Name.
|
||||||
|
$gegnerDisplay = strcasecmp($gegnerName, trim((string) ($issue['opponent'] ?? ''))) === 0
|
||||||
|
? stadionzeitung_opponent_display($issue)
|
||||||
|
: $gegnerName;
|
||||||
|
|
||||||
|
// Tabellenausschnitt statt zweier loser Platz-Ziffern im Kopf: ein
|
||||||
|
// Fenster um beide Mannschaften herum (mit einer Zeile Luft davor
|
||||||
|
// und dahinter). Liegen sie weit auseinander, werden es zwei
|
||||||
|
// Blöcke — gerendert wird beides mit der geteilten
|
||||||
|
// league-table-Komponente, nie mit einer zweiten Tabelle.
|
||||||
|
$ausschnitt = [];
|
||||||
|
if ($ownRow !== null && $opponentRow !== null && $table !== []) {
|
||||||
|
$ranks = [(int) ($ownRow['rank'] ?? 0), (int) ($opponentRow['rank'] ?? 0)];
|
||||||
|
sort($ranks);
|
||||||
|
$fenster = static function (int $von, int $bis) use ($table): array {
|
||||||
|
return array_values(array_filter($table, static function (array $row) use ($von, $bis): bool {
|
||||||
|
$rank = (int) ($row['rank'] ?? 0);
|
||||||
|
return $rank >= $von && $rank <= $bis;
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
if ($ranks[1] - $ranks[0] <= 5) {
|
||||||
|
$block = $fenster($ranks[0], $ranks[1]);
|
||||||
|
if ($block !== []) {
|
||||||
|
$ausschnitt[] = $block;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foreach ($ranks as $rank) {
|
||||||
|
$block = $fenster($rank, $rank + 1);
|
||||||
|
if ($block !== []) {
|
||||||
|
$ausschnitt[] = $block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$gegner[$key] = [
|
$gegner[$key] = [
|
||||||
'team_name' => (string) ($team['name'] ?? ''),
|
'team_name' => (string) ($team['name'] ?? ''),
|
||||||
'opponent' => $gegnerName,
|
'opponent' => $gegnerName,
|
||||||
|
'opponent_display' => $gegnerDisplay,
|
||||||
'league' => (string) ($team['league'] ?? ''),
|
'league' => (string) ($team['league'] ?? ''),
|
||||||
'season' => (string) ($team['season'] ?? ''),
|
'season' => (string) ($team['season'] ?? ''),
|
||||||
'fixture' => $fixture,
|
'fixture' => $fixture,
|
||||||
'own' => $ownRow,
|
'own' => $ownRow,
|
||||||
'opponent_stats' => $opponentRow,
|
'opponent_stats' => $opponentRow,
|
||||||
|
'table_excerpt' => $ausschnitt,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,14 +78,14 @@ $firstImage = $issue['images'][0] ?? null;
|
|||||||
$cover = ($firstImage !== null && ($firstImage['type'] ?? 'image') === 'image') ? $firstImage : null;
|
$cover = ($firstImage !== null && ($firstImage['type'] ?? 'image') === 'image') ? $firstImage : null;
|
||||||
|
|
||||||
$meta = [
|
$meta = [
|
||||||
'title' => 'Stadionzeitung gegen ' . $issue['opponent'] . ($isDruck ? ' (Druckfassung)' : ''),
|
'title' => 'Stadionzeitung gegen ' . stadionzeitung_opponent_display($issue) . ($isDruck ? ' (Druckfassung)' : ''),
|
||||||
'description' => 'Stadionzeitung zum Heimspiel gegen ' . $issue['opponent'] . ' am ' . german_date($issue['date']) . ' – digital zum Durchblättern.',
|
'description' => 'Stadionzeitung zum Heimspiel gegen ' . stadionzeitung_opponent_display($issue) . ' am ' . german_date($issue['date']) . ' – digital zum Durchblättern.',
|
||||||
'scripts' => [$isDruck ? 'stadionzeitung-druck.js' : 'stadionzeitung-viewer.js'],
|
'scripts' => [$isDruck ? 'stadionzeitung-druck.js' : 'stadionzeitung-viewer.js'],
|
||||||
'schema' => page_schema([
|
'schema' => page_schema([
|
||||||
['name' => 'Startseite', 'slug' => ''],
|
['name' => 'Startseite', 'slug' => ''],
|
||||||
['name' => 'Fußball', 'slug' => 'fussball'],
|
['name' => 'Fußball', 'slug' => 'fussball'],
|
||||||
['name' => 'Stadionzeitung', 'slug' => 'stadionzeitung'],
|
['name' => 'Stadionzeitung', 'slug' => 'stadionzeitung'],
|
||||||
['name' => 'Gegen ' . $issue['opponent'], 'slug' => 'stadionzeitung/' . $issue['slug']],
|
['name' => 'Gegen ' . stadionzeitung_opponent_display($issue), 'slug' => 'stadionzeitung/' . $issue['slug']],
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
if ($cover !== null) {
|
if ($cover !== null) {
|
||||||
@@ -113,7 +113,7 @@ $druckBlaetter = (int) ceil($druckSeiten / 4);
|
|||||||
<a class="stadionzeitung-issue-head__back" href="<?= e($isDruck ? url('stadionzeitung/' . $issue['slug']) : url('stadionzeitung')) ?>">
|
<a class="stadionzeitung-issue-head__back" href="<?= e($isDruck ? url('stadionzeitung/' . $issue['slug']) : url('stadionzeitung')) ?>">
|
||||||
<?= icon('chevron-left') ?> <?= $isDruck ? 'Ausgabe' : 'Archiv' ?>
|
<?= icon('chevron-left') ?> <?= $isDruck ? 'Ausgabe' : 'Archiv' ?>
|
||||||
</a>
|
</a>
|
||||||
<p class="stadionzeitung-issue-head__title">Gegen <?= e($issue['opponent']) ?></p>
|
<p class="stadionzeitung-issue-head__title">Gegen <?= e(stadionzeitung_opponent_display($issue)) ?></p>
|
||||||
<?php if ($isDruck): ?>
|
<?php if ($isDruck): ?>
|
||||||
<p class="stadionzeitung-issue-head__actions">
|
<p class="stadionzeitung-issue-head__actions">
|
||||||
<button class="btn btn--sm stadionzeitung-issue-head__print" type="button" data-druck-print hidden>
|
<button class="btn btn--sm stadionzeitung-issue-head__print" type="button" data-druck-print hidden>
|
||||||
|
|||||||
@@ -202,6 +202,11 @@ $oldAufruf = $oldIssue['aufruf'] ?? null;
|
|||||||
// Turnierpläne, Gegnerseiten je Heimspiel) und Cover-Kennung — chat-gepflegt.
|
// Turnierpläne, Gegnerseiten je Heimspiel) und Cover-Kennung — chat-gepflegt.
|
||||||
$oldVeranstaltung = $oldIssue['veranstaltung'] ?? null;
|
$oldVeranstaltung = $oldIssue['veranstaltung'] ?? null;
|
||||||
$oldCover = $oldIssue['cover'] ?? null;
|
$oldCover = $oldIssue['cover'] ?? null;
|
||||||
|
// Anzeigename des Gegners fürs Cover und die Liste ("Wirsberg/Kupferberg" statt
|
||||||
|
// der BFV-Zeile) und die Liste der Mannschaften, die zusätzlich eine
|
||||||
|
// Gegnerseite bekommen — beides Chat-Pflege, überlebt erneute Läufe wie vorwort.
|
||||||
|
$oldOpponentShort = $oldIssue['opponent_short'] ?? null;
|
||||||
|
$oldGegnerTeams = is_array($oldIssue['gegner_teams'] ?? null) ? array_values($oldIssue['gegner_teams']) : null;
|
||||||
$veranstaltung = null;
|
$veranstaltung = null;
|
||||||
if (is_string($oldVeranstaltung) && $oldVeranstaltung !== '') {
|
if (is_string($oldVeranstaltung) && $oldVeranstaltung !== '') {
|
||||||
$veranstaltung = veranstaltung_find($oldVeranstaltung);
|
$veranstaltung = veranstaltung_find($oldVeranstaltung);
|
||||||
@@ -376,13 +381,22 @@ try {
|
|||||||
// dieselbe Funktion, aus der stadionzeitung_build_snapshot() die Daten baut,
|
// dieselbe Funktion, aus der stadionzeitung_build_snapshot() die Daten baut,
|
||||||
// damit nie eine Seite ohne Daten oder Daten ohne Seite entstehen.
|
// damit nie eine Seite ohne Daten oder Daten ohne Seite entstehen.
|
||||||
$gegnerSeiten = [];
|
$gegnerSeiten = [];
|
||||||
foreach (stadionzeitung_gegner_fixtures([
|
$gegnerFixtures = stadionzeitung_gegner_fixtures([
|
||||||
'date' => $date, 'team_key' => $teamKey, 'opponent' => $opponent,
|
'date' => $date, 'team_key' => $teamKey, 'opponent' => $opponent,
|
||||||
'veranstaltung' => $oldVeranstaltung,
|
'veranstaltung' => $oldVeranstaltung, 'gegner_teams' => $oldGegnerTeams ?? [],
|
||||||
]) as $gegnerTeam => $fixture) {
|
]);
|
||||||
|
foreach ($gegnerFixtures as $gegnerTeam => $fixture) {
|
||||||
$gegnerSeiten[] = $live('gegner', ['team' => (string) $gegnerTeam]);
|
$gegnerSeiten[] = $live('gegner', ['team' => (string) $gegnerTeam]);
|
||||||
$log("Gegnerseite {$gegnerTeam}: {$fixture['opponent']} ({$fixture['date']}).");
|
$log("Gegnerseite {$gegnerTeam}: {$fixture['opponent']} ({$fixture['date']}).");
|
||||||
}
|
}
|
||||||
|
// Eine benannte Mannschaft ohne Spiel im Fenster bekommt keine Seite. Das ist
|
||||||
|
// richtig so, darf aber nicht stillschweigend passieren: ein Tippfehler im
|
||||||
|
// Team-Key sähe genauso aus wie ein spielfreies Wochenende.
|
||||||
|
foreach ($oldGegnerTeams ?? [] as $gewuenscht) {
|
||||||
|
if (!isset($gegnerFixtures[(string) $gewuenscht])) {
|
||||||
|
$log("gegner_teams: '{$gewuenscht}' hat kein Spiel im Fenster um {$date} (oder der Team-Key ist falsch) — keine Gegnerseite.", 'WARN');
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($gegnerSeiten === []) {
|
if ($gegnerSeiten === []) {
|
||||||
// Ohne Treffer (Gegner leer) bleibt die Seite der tragenden Mannschaft —
|
// Ohne Treffer (Gegner leer) bleibt die Seite der tragenden Mannschaft —
|
||||||
// sie meldet dann selbst, dass keine Vergleichsdaten vorliegen.
|
// sie meldet dann selbst, dass keine Vergleichsdaten vorliegen.
|
||||||
@@ -425,7 +439,7 @@ try {
|
|||||||
if ($rueckseite !== null) {
|
if ($rueckseite !== null) {
|
||||||
$bloecke[] = [
|
$bloecke[] = [
|
||||||
'name' => 'rueckseite',
|
'name' => 'rueckseite',
|
||||||
'seiten' => [$bild($rueckseite, 'rueckseite', "Rückseite der Stadionzeitung gegen {$opponent}")],
|
'seiten' => [$bild($rueckseite, 'rueckseite', "Rückseite der Stadionzeitung gegen " . stadionzeitung_opponent_display(['opponent' => $opponent, 'opponent_short' => $oldOpponentShort]))],
|
||||||
'anzeigen_danach' => false,
|
'anzeigen_danach' => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -734,6 +748,8 @@ if ($oldIssue !== null && ($oldIssue['status'] ?? '') === 'veroeffentlicht') {
|
|||||||
$issues[] = [
|
$issues[] = [
|
||||||
'slug' => $slug,
|
'slug' => $slug,
|
||||||
'opponent' => $opponent,
|
'opponent' => $opponent,
|
||||||
|
'opponent_short' => $oldOpponentShort,
|
||||||
|
'gegner_teams' => $oldGegnerTeams,
|
||||||
'date' => $date,
|
'date' => $date,
|
||||||
'team_key' => $teamKey,
|
'team_key' => $teamKey,
|
||||||
'status' => 'entwurf',
|
'status' => 'entwurf',
|
||||||
|
|||||||
@@ -1,5 +1,520 @@
|
|||||||
{
|
{
|
||||||
"issues": [
|
"issues": [
|
||||||
|
{
|
||||||
|
"slug": "2026-09-19-sg-wirsberg-kupferberg",
|
||||||
|
"opponent": "(SG1) TSV Wirsberg I/ FC Kupferberg I",
|
||||||
|
"opponent_short": "Wirsberg/Kupferberg",
|
||||||
|
"gegner_teams": [
|
||||||
|
"zweite-mannschaft",
|
||||||
|
"damen"
|
||||||
|
],
|
||||||
|
"date": "2026-09-19",
|
||||||
|
"team_key": "erste-mannschaft",
|
||||||
|
"status": "entwurf",
|
||||||
|
"pages": 44,
|
||||||
|
"player_image": {
|
||||||
|
"base": "img/stadionzeitung/2026-09-19-sg-wirsberg-kupferberg/spieler",
|
||||||
|
"widths": [
|
||||||
|
320,
|
||||||
|
640,
|
||||||
|
1000
|
||||||
|
],
|
||||||
|
"ext": "png",
|
||||||
|
"alt": ""
|
||||||
|
},
|
||||||
|
"qr_image": "img/stadionzeitung/2026-09-19-sg-wirsberg-kupferberg/qr.png",
|
||||||
|
"vorwort": {
|
||||||
|
"heading": "Herzlich willkommen im Katzbachtal",
|
||||||
|
"paragraphs": [
|
||||||
|
"Liebe Fußballfreunde,",
|
||||||
|
"herzlich willkommen im Katzbachtal. Ein besonderer Gruß gilt dem Schiedsrichtergespann und unseren Gästen von der SG Wirsberg/Kupferberg.",
|
||||||
|
"Das 0:2 in Trebgast war ein Rückschlag. Die letzten beiden Heimspiele haben wir aber gewonnen, und an diese Auftritte wollen wir heute gegen den Spitzenreiter anknüpfen.",
|
||||||
|
"Und es bleibt ein volles Wochenende: Um 16 Uhr spielt unsere Zweite gegen Leuchau II, am Sonntag fahren unsere Frauen als Tabellenführer nach Weidenberg. Darauf freuen wir uns. Auf geht's!"
|
||||||
|
],
|
||||||
|
"closing": "Rot-schwarze Grüße",
|
||||||
|
"signee_name": "Benedikt Höfner",
|
||||||
|
"signee_role": "Sportlicher Leiter"
|
||||||
|
},
|
||||||
|
"interview": {
|
||||||
|
"teaser": "Unser Trainer Ali Kilic über die Niederlage in Trebgast und den heutigen Gegner",
|
||||||
|
"heading": "Zwei Fragen an Ali Kilic",
|
||||||
|
"person_name": "Ali Kilic",
|
||||||
|
"person_role": "Trainer 1. Mannschaft",
|
||||||
|
"intro": null,
|
||||||
|
"fragen": [
|
||||||
|
{
|
||||||
|
"frage": "Ali, in Trebgast hat es mit 0:2 nicht geklappt. Wie hast du das Spiel gesehen?",
|
||||||
|
"antwort": "Der Sieg für Trebgast war grundsätzlich verdient. Wir sind früh in Rückstand geraten, und das hat ihnen bei dem regnerischen Wetter und den schwierigen Verhältnissen in die Karten gespielt. Gerade in unserem Ballbesitzspiel hatten wir zu viele einfache Fehler in der Annahme und der Mitnahme des Balls. Nach der Halbzeitpause hatten wir zwar eine gute Drangphase, aber ohne echte Durchschlagskraft und ohne klare Chancen."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frage": "Heute kommt der Spitzenreiter mit sieben Siegen aus acht Spielen. Was erwartest du?",
|
||||||
|
"antwort": "Wirsberg hat sich mit teilweise starken Spielern verstärkt und steht nicht unerwartet oben in der Tabelle. Wir rechnen damit, dass sie sich anders als Trebgast nicht so tief hinten reinstellen, sondern aktiver sind. Darauf müssen wir uns vorbereiten und gerade in der Defensivarbeit wieder konzentriert sein. Nach vorne bekommen wir dafür vielleicht mehr Räume, die wir trotz einiger Ausfälle nutzen wollen."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"photo": "img/persons/ali-kilic.jpg"
|
||||||
|
},
|
||||||
|
"aufruf": null,
|
||||||
|
"veranstaltung": null,
|
||||||
|
"cover": null,
|
||||||
|
"verfasser_photo": {
|
||||||
|
"base": "img/stadionzeitung/2026-09-19-sg-wirsberg-kupferberg/verfasser",
|
||||||
|
"widths": [
|
||||||
|
120,
|
||||||
|
222
|
||||||
|
],
|
||||||
|
"alt": ""
|
||||||
|
},
|
||||||
|
"termine_snapshot": [
|
||||||
|
{
|
||||||
|
"date": "2026-09-20",
|
||||||
|
"date_end": null,
|
||||||
|
"title": "(SG) SV Weidenberg/TSV Bayreuth-St. Johannis 2 – TSV 08 Kulmbach",
|
||||||
|
"department": "fussball",
|
||||||
|
"type": "spiel",
|
||||||
|
"meta": "Auswärtsspiel · Frauen KL Ost",
|
||||||
|
"href": "/matchcenter",
|
||||||
|
"kickoff": "2026-09-20T13:00",
|
||||||
|
"team_name": "Frauenmannschaft",
|
||||||
|
"league": "Frauen KL Ost",
|
||||||
|
"raw": {
|
||||||
|
"kickoff": "2026-09-20T13:00",
|
||||||
|
"date_display": "So, 20.09.",
|
||||||
|
"time_display": "13:00 Uhr",
|
||||||
|
"competition": "Liga",
|
||||||
|
"home": "(SG) SV Weidenberg/TSV Bayreuth-St. Johannis 2",
|
||||||
|
"away": "TSV 08 Kulmbach",
|
||||||
|
"home_crest": "img/crests/00ES8GNK4O000013VV0AG08LVUPGND5I.png",
|
||||||
|
"away_crest": "img/crests/00ES8GNK3G000012VV0AG08LVUPGND5I.png",
|
||||||
|
"is_home": false,
|
||||||
|
"team_key": "damen",
|
||||||
|
"team_name": "Frauenmannschaft"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-09-27",
|
||||||
|
"date_end": null,
|
||||||
|
"title": "TSV 08 Kulmbach – (SG) SpVgg Döbra/FC Konradsreuth/ATS Selbitz",
|
||||||
|
"department": "fussball",
|
||||||
|
"type": "spiel",
|
||||||
|
"meta": "Heimspiel · Frauen KL Ost",
|
||||||
|
"href": "/matchcenter",
|
||||||
|
"kickoff": "2026-09-27T10:30",
|
||||||
|
"team_name": "Frauenmannschaft",
|
||||||
|
"league": "Frauen KL Ost",
|
||||||
|
"raw": {
|
||||||
|
"kickoff": "2026-09-27T10:30",
|
||||||
|
"date_display": "So, 27.09.",
|
||||||
|
"time_display": "10:30 Uhr",
|
||||||
|
"competition": "Liga",
|
||||||
|
"home": "TSV 08 Kulmbach",
|
||||||
|
"away": "(SG) SpVgg Döbra/FC Konradsreuth/ATS Selbitz",
|
||||||
|
"home_crest": "img/crests/00ES8GNK3G000012VV0AG08LVUPGND5I.png",
|
||||||
|
"away_crest": "img/crests/00ES8GNK8G00000IVV0AG08LVUPGND5I.png",
|
||||||
|
"is_home": true,
|
||||||
|
"team_key": "damen",
|
||||||
|
"team_name": "Frauenmannschaft"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-09-27",
|
||||||
|
"date_end": null,
|
||||||
|
"title": "1. FC Kirchleus – TSV 08 Kulmbach",
|
||||||
|
"department": "fussball",
|
||||||
|
"type": "spiel",
|
||||||
|
"meta": "Auswärtsspiel · Kreisklasse",
|
||||||
|
"href": "/matchcenter",
|
||||||
|
"kickoff": "2026-09-27T14:00",
|
||||||
|
"team_name": "1. Mannschaft",
|
||||||
|
"league": "Kreisklasse",
|
||||||
|
"raw": {
|
||||||
|
"kickoff": "2026-09-27T14:00",
|
||||||
|
"date_display": "So, 27.09.",
|
||||||
|
"time_display": "14:00 Uhr",
|
||||||
|
"competition": "Liga",
|
||||||
|
"home": "1. FC Kirchleus",
|
||||||
|
"away": "TSV 08 Kulmbach",
|
||||||
|
"home_crest": "img/crests/00ES8GNK3G00000RVV0AG08LVUPGND5I.png",
|
||||||
|
"away_crest": "img/crests/00ES8GNK3G000012VV0AG08LVUPGND5I.png",
|
||||||
|
"is_home": false,
|
||||||
|
"team_key": "erste-mannschaft",
|
||||||
|
"team_name": "1. Mannschaft"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2027-02-20",
|
||||||
|
"date_end": null,
|
||||||
|
"title": "Tagesskifahrt 2027",
|
||||||
|
"department": "verein",
|
||||||
|
"type": "event",
|
||||||
|
"meta": "Skigebiet Hochzillertal",
|
||||||
|
"href": "/veranstaltungen/tagesskifahrt-2027",
|
||||||
|
"time": "",
|
||||||
|
"location": "Skigebiet Hochzillertal",
|
||||||
|
"raw": {
|
||||||
|
"slug": "tagesskifahrt-2027",
|
||||||
|
"title": "Tagesskifahrt 2027",
|
||||||
|
"date_start": "2027-02-20",
|
||||||
|
"date_end": null,
|
||||||
|
"time": "",
|
||||||
|
"location": "Skigebiet Hochzillertal",
|
||||||
|
"department": "verein",
|
||||||
|
"status": "veroeffentlicht",
|
||||||
|
"text": "Wir fahren für einen Tag zum Skifahren ins Skigebiet Hochzillertal. Die Plätze sind auf 40 Personen begrenzt, angemeldet wird bei Fabian Früh.",
|
||||||
|
"ankuendigung": [
|
||||||
|
"Einmal im Winter machen wir uns gemeinsam auf und fahren für einen Tag ins Skigebiet Hochzillertal bei Kaltenbach. Ob Erwachsene, Jugendliche oder Kinder: Auf den Pisten ist für alle etwas dabei. Die Fahrt ist auf 40 Personen begrenzt, wer dabei sein möchte, meldet sich am besten früh.",
|
||||||
|
"Erwachsene zahlen 115 Euro, Jugendliche 95 Euro und Kinder 70 Euro. Anmelden könnt ihr euch bei Fabian Früh unter 0160 97516797."
|
||||||
|
],
|
||||||
|
"rueckblick": null,
|
||||||
|
"karte": "",
|
||||||
|
"anfahrt": [],
|
||||||
|
"plakate": [
|
||||||
|
{
|
||||||
|
"base": "img/veranstaltungen/tagesskifahrt-2027/plakat-01",
|
||||||
|
"widths": [
|
||||||
|
640,
|
||||||
|
1000,
|
||||||
|
1024
|
||||||
|
],
|
||||||
|
"alt": "Plakat zu unserer Tagesskifahrt am Samstag, 20. Februar 2027 ins Skigebiet Hochzillertal, mit den Preisen für Erwachsene, Jugendliche und Kinder und der Anmeldung bei Fabian Früh.",
|
||||||
|
"source": "1-ankuendigung.jpeg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"galerie": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "cover"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "vorwort"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "interview"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/eldorado",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: eldorado Radsporttechnik Kulmbach"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/egu",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Erfrischungsgetränke-Union Kulmbacher Gruppe GmbH"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "ergebnisse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/hofmann-graef",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Buchhandlung Hofmann, Kulmbach, und Automobilwerkstatt Gräf, Thurnau"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/muench-richterstegner",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Planungsbüro Münch und Richter & Stegner Steuerungstechnik"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "gegner",
|
||||||
|
"team": "erste-mannschaft"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "gegner",
|
||||||
|
"team": "zweite-mannschaft"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "gegner",
|
||||||
|
"team": "damen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/malteser-herrmann",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Malteser-Hilfsdienst und Schlosserei Herrmann, Kulmbach"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/apotheken-wenkel",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Apotheke im Fritz, Stadtpark Apotheke und Schlüsseldienst Wenkel, Kulmbach"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "tabelle",
|
||||||
|
"team": "erste-mannschaft"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "tabelle",
|
||||||
|
"team": "zweite-mannschaft"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "tabelle",
|
||||||
|
"team": "damen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/karl-geyer",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Geyer Farbe + Raum, Kulmbach, Starthilfe Boden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "vorschau"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/gsb",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: GSB Security und GSB Gebäude Service, Bindlach"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/physiofit-wohlfart",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Physio Fit Neudrossenfeld und Textilpflege Wohlfart, Kulmbach"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "termine"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/sport2000-pizza",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Sport 2000 Downstairs und Kulmbacher Pizza-Service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/schulz-csu",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Schreinerei Schulz, Mainleus, und CSU Stadtverband Kulmbach"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "news"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/mattis-ka2",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Karl Mattis Flaschnerei und KA2 Fitness, Kulmbach"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "kontakt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/motor-nuetzel",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Motor-Nützel Vertriebs-GmbH, Bayreuth"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/vereinskarte-edeka",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1410
|
||||||
|
],
|
||||||
|
"alt": "Eigenanzeige: So funktioniert die Vereinskarte beim E center Seidl Kulmbach"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "mitglied"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/trapper-verkehrsakademie",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Trapper GmbH Entsorgungsfachbetrieb und Verkehrsakademie, Kulmbach"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/ohnemueller-dippold",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Gaststätte und Metzgerei Ohne-Müller, Kulmbach-Weiher, und Bäckerei Dippold, Melkendorf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "historie"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/hanft",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Hanft Bürotechnik GmbH, Kulmbach"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "sportheim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/elma-kleinheinz",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: elma GmbH Bayreuth und Metzgerei Kleinheinz, Kulmbach"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/stadionzeitung-digital",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Eigenanzeige: Unsere Stadionzeitung gibt es jetzt auch online, mit QR-Code zum Scannen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "partner"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "danke",
|
||||||
|
"anzeige": {
|
||||||
|
"base": "img/anzeigen/emons",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1141
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Emons Spedition Himmelkron, Ihr Partner für nationale und internationale Transporte"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/gagel-pickelmann",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Gagel Bäder und Mineralölhandel Pickelmann"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/schramm",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Anzeige: Landgasthof und Metzgerei Schramm, Forstlahm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "momente"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/anzeigen/tagesskifahrt-2027",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1440
|
||||||
|
],
|
||||||
|
"alt": "Eigenanzeige: Unsere Tagesskifahrt ins Hochzillertal am 20. Februar 2027"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "live",
|
||||||
|
"page": "impressum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"base": "img/stadionzeitung/2026-09-19-sg-wirsberg-kupferberg/rueckseite",
|
||||||
|
"widths": [
|
||||||
|
480,
|
||||||
|
960,
|
||||||
|
1410
|
||||||
|
],
|
||||||
|
"alt": "Rückseite der Stadionzeitung gegen Wirsberg/Kupferberg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"slug": "2026-09-04-ats-wartenfels",
|
"slug": "2026-09-04-ats-wartenfels",
|
||||||
"opponent": "ATS Wartenfels",
|
"opponent": "ATS Wartenfels",
|
||||||
|
|||||||
@@ -7261,74 +7261,136 @@ a.news-feature__link:hover .news-feature__more {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* === stadionzeitung-ergebnisse (Live-Seite) === */
|
/* === stadionzeitung-ergebnisse (Live-Seite) === */
|
||||||
/* Ergebnis-Rückblick: zwei Zeilen pro Spiel (Heim über Gast, wie das
|
/* Ergebnis-Rückblick als Anzeigetafel: Wappen und Vereinsname je Seite, das
|
||||||
„Nächste Spiele"-Band) — lange Vereinsnamen passen auf der schmalen
|
Ergebnis groß in der Mitte (Vorbild kicker/Sofascore). Vierter Entwurf,
|
||||||
A5-Seite nie in eine Zeile. Eigene Mannschaft über Gewicht der Body-Schrift
|
19.09.2026 — die drei Vorgänger scheiterten alle an derselben Ursache:
|
||||||
markiert (Abel hat echte Schnitte — anders als Coolvetica erlaubt, siehe
|
sie waren Listenzeilen, und eine Zeile stellt Heim und Gast untereinander
|
||||||
CLAUDE.md „nie über font-weight" bei Coolvetica). */
|
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 {
|
.stadionzeitung-ergebnisse__team {
|
||||||
margin-bottom: 4.2cqi;
|
padding-bottom: 4.6cqi;
|
||||||
}
|
margin-bottom: 4.6cqi;
|
||||||
|
|
||||||
.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;
|
|
||||||
border-bottom: 1px solid var(--clr-glass-border);
|
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;
|
margin-bottom: 0;
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stadionzeitung-ergebnisse__meta {
|
/* Mannschaftszeile bewusst leise: die Gliederung macht der Abstand zwischen
|
||||||
font-size: 2.5cqi;
|
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);
|
color: var(--clr-text-faint);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Das Zeilen-Layout gehört auf die ZEILE, nicht auf das Wappen darin (stand
|
.stadionzeitung-ergebnisse__match {
|
||||||
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;
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1.7cqi;
|
gap: 2.1cqi;
|
||||||
padding-block: 0.6cqi;
|
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 {
|
.stadionzeitung-ergebnisse__club {
|
||||||
min-width: 0;
|
max-width: 100%;
|
||||||
font-size: 3.75cqi;
|
overflow: hidden;
|
||||||
line-height: 1.2;
|
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);
|
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-family: var(--ff-heading);
|
||||||
font-size: 5.8cqi;
|
font-size: 7.4cqi;
|
||||||
line-height: 1;
|
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) === */
|
/* === 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 {
|
.stadionzeitung-gegner {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
@@ -7336,90 +7398,204 @@ a.news-feature__link:hover .news-feature__more {
|
|||||||
.stadionzeitung-gegner__duell {
|
.stadionzeitung-gegner__duell {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
||||||
align-items: center;
|
align-items: start;
|
||||||
gap: 2.5cqi;
|
gap: 2.5cqi;
|
||||||
margin-bottom: 5cqi;
|
margin-top: 2.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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 {
|
.stadionzeitung-gegner__club {
|
||||||
--crest-size: 12cqi;
|
--crest-size: 11cqi;
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-rows: auto minmax(7.4cqi, auto);
|
||||||
align-items: center;
|
justify-items: center;
|
||||||
gap: 1.7cqi;
|
gap: 1.2cqi;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stadionzeitung-gegner__club strong {
|
.stadionzeitung-gegner__club-name {
|
||||||
font-size: 3.2cqi;
|
font-size: 3.2cqi;
|
||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stadionzeitung-gegner__club--own strong {
|
/* Mittelspalte: „gegen" und der Anstoß stehen zusammen, damit im Duell nur
|
||||||
color: var(--clr-accent);
|
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
|
||||||
.stadionzeitung-gegner__match {
|
jetzt im Ausschnitt unter den Balken. */
|
||||||
display: flex;
|
.stadionzeitung-gegner__vs {
|
||||||
flex-direction: column;
|
display: grid;
|
||||||
align-items: center;
|
justify-items: center;
|
||||||
gap: 0.5cqi;
|
gap: 1.2cqi;
|
||||||
font-size: 2.5cqi;
|
padding-top: 3.3cqi;
|
||||||
color: var(--clr-text-muted);
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stadionzeitung-gegner__match strong {
|
.stadionzeitung-gegner__vs-wort {
|
||||||
font-size: 2.9cqi;
|
font-size: 2.7cqi;
|
||||||
color: var(--clr-text);
|
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 {
|
.stadionzeitung-gegner__vergleich {
|
||||||
border-block: 1px solid var(--clr-glass-border);
|
margin-top: 2.5cqi;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stadionzeitung-gegner__vergleich-head,
|
.stadionzeitung-gegner__vergleich-head,
|
||||||
.stadionzeitung-gegner__vergleich-row {
|
.stadionzeitung-gegner__vergleich-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 2fr 1fr;
|
grid-template-columns: 6cqi minmax(0, 1fr) 24cqi minmax(0, 1fr) 6cqi;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 1.7cqi;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stadionzeitung-gegner__vergleich-head {
|
.stadionzeitung-gegner__vergleich-head {
|
||||||
padding-block: 1.7cqi;
|
padding-bottom: 1.4cqi;
|
||||||
font-size: 2.5cqi;
|
margin-bottom: 1.4cqi;
|
||||||
font-weight: var(--fw-bold);
|
border-bottom: 1px solid var(--clr-glass-border);
|
||||||
|
font-size: 2.4cqi;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: var(--ls-caps);
|
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 {
|
.stadionzeitung-gegner__vergleich-row {
|
||||||
min-height: 6.5cqi;
|
min-height: 5.8cqi;
|
||||||
border-top: 1px solid var(--clr-glass-border);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
font-size: 2.8cqi;
|
||||||
color: var(--clr-text-muted);
|
color: var(--clr-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stadionzeitung-gegner__vergleich-row strong {
|
/* Balkenpaar: der linke wächst aus der Mitte nach links, der rechte nach
|
||||||
font-family: var(--ff-heading);
|
rechts. Die Spur bleibt sichtbar (helle Fläche), damit auch ein Wert von
|
||||||
font-size: 4.8cqi;
|
null als Zeile lesbar ist. */
|
||||||
line-height: 1;
|
.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 {
|
.stadionzeitung-gegner__balken--own {
|
||||||
color: var(--clr-accent);
|
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 {
|
.stadionzeitung-gegner__empty {
|
||||||
padding: 4.2cqi;
|
padding: 4.2cqi;
|
||||||
border: 1px solid var(--clr-glass-border);
|
border: 1px solid var(--clr-glass-border);
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 589 B |
|
After Width: | Height: | Size: 398 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 222 KiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 840 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 440 B After Width: | Height: | Size: 426 B |
|
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 419 B |
|
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 432 B |
|
Before Width: | Height: | Size: 503 B After Width: | Height: | Size: 488 B |
|
Before Width: | Height: | Size: 421 B After Width: | Height: | Size: 422 B |
|
Before Width: | Height: | Size: 497 B After Width: | Height: | Size: 500 B |
|
Before Width: | Height: | Size: 496 B After Width: | Height: | Size: 506 B |
|
Before Width: | Height: | Size: 426 B After Width: | Height: | Size: 427 B |