diff --git a/CLAUDE.md b/CLAUDE.md index 1141e1f..004d1fb 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`, `rueckblick` (Text/Bildnachweis; `image` bleibt script-verwaltet), `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 | +| 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`, `beitrag` (Text/Quelle/Bildnachweis; `image` bleibt script-verwaltet), `rueckblick` (Text/Bildnachweis; `image` bleibt script-verwaltet), `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`) | @@ -521,6 +521,19 @@ Besucher laden alle Matchcenter-Inhalte ausschließlich von unserer Domain. ## Stadionzeitung (Ausgaben-Archiv) +**CLI-Abrufe bei lokalem DNS-Ausfall:** `TSV_HTTP_DOH_URL=https://1.1.1.1/dns-query` +kann für `bin/matchcenter-sync.php` und `bin/instagram-add.php` gesetzt werden. +Optional, keine Änderung am System-DNS; Cron/Web ignoriert den Override. + +**Redaktioneller Lesetipp (24.10.2026):** optionales chat-gepflegtes `beitrag`-Feld +(`kicker`, `heading`, `paragraphs[]`, `source`, `source_url` als HTTPS-Adresse, +`cta_label`, `image_credit`, `image.alt`). `bin/stadionzeitung-add.php` übernimmt +das Feld über erneute Läufe, schreibt `image` aus `/beitrag.*` und erzeugt +`qr-beitrag.png` direkt zur externen Quelle. Die Seite steht im anzeigenfreien +Aufschlag hinter Vorwort/Interview; `cover.teaser` kündigt sie unabhängig von +einem Interview an. Rendering gemeinsam für Viewer/Druck über +`stadionzeitung-beitrag.php`; Preflight prüft Quelle, Foto und QR. + Digitale Fassung der Stadionzeitung zu jedem Heimspiel — Archiv-Übersicht (`/stadionzeitung`) als **Liste über die volle Breite, Cover links und die Angaben rechts daneben** (dasselbe Muster wie `.veranstaltung-card`, und aus demselben Grund: der Bestand ist klein). Vorher war das ein diff --git a/app/components/stadionzeitung-beitrag.php b/app/components/stadionzeitung-beitrag.php new file mode 100644 index 0000000..a86675c --- /dev/null +++ b/app/components/stadionzeitung-beitrag.php @@ -0,0 +1,44 @@ + +
+
+

+

+
+ +
+ $beitrag['image'] ?? [], 'sizes' => '(min-width: 520px) 430px, 90vw', 'class' => 'stadionzeitung-beitrag__foto', 'eager' => $eager ?? false]); ?> +
+
+ +

+ +

+ + +
+

+

Den vollständigen Beitrag lesen

+ +
+ +
+ QR-Code zum Originalartikel + Zum Artikel +
+ +
+ +
diff --git a/app/components/stadionzeitung-seite.php b/app/components/stadionzeitung-seite.php index b2452f9..9fcae62 100644 --- a/app/components/stadionzeitung-seite.php +++ b/app/components/stadionzeitung-seite.php @@ -50,6 +50,9 @@ $type = $entry['type'] ?? 'image'; $issue]); ?> + + $issue, 'eager' => $eager]); ?> + $issue]); ?> diff --git a/bin/instagram-add.php b/bin/instagram-add.php index be15509..01d248e 100644 --- a/bin/instagram-add.php +++ b/bin/instagram-add.php @@ -149,6 +149,11 @@ if (!$lock || !flock($lock, LOCK_EX | LOCK_NB)) { // --- HTTP-Helfer, unverändert aus bin/instagram-sync.php übernommen --- $fetch = static function (string $url, array $headers = []): string|false { $ch = curl_init($url); + // Optional wie im Matchcenter-Sync, wenn der lokale DNS-Dienst ausfällt. + $dohUrl = getenv('TSV_HTTP_DOH_URL') ?: ''; + if ($dohUrl !== '') { + curl_setopt($ch, CURLOPT_DOH_URL, $dohUrl); + } curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, diff --git a/bin/matchcenter-sync.php b/bin/matchcenter-sync.php index 118b9b9..0d702cc 100644 --- a/bin/matchcenter-sync.php +++ b/bin/matchcenter-sync.php @@ -94,6 +94,11 @@ if (!$lock || !flock($lock, LOCK_EX | LOCK_NB)) { // --- HTTP-Helfer mit Browser-typischen Headern (gegen HTTP 418) --- $fetch = static function (string $url, bool $json = true) use ($referer): string|false { $ch = curl_init($url); + // Optionaler CLI-Ausweg bei defektem lokalem DNS; keine Systemänderung. + $dohUrl = PHP_SAPI === 'cli' ? (getenv('TSV_HTTP_DOH_URL') ?: '') : ''; + if ($dohUrl !== '') { + curl_setopt($ch, CURLOPT_DOH_URL, $dohUrl); + } curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, diff --git a/bin/preflight.php b/bin/preflight.php index 00e2b47..04058b3 100644 --- a/bin/preflight.php +++ b/bin/preflight.php @@ -393,6 +393,30 @@ if (is_file($szFile)) { if (is_file($szFile)) { $szData = $szData ?? (json_decode((string) file_get_contents($szFile), true) ?: []); foreach ($szData['issues'] ?? [] as $ausgabe) { + if (is_array($ausgabe['beitrag'] ?? null)) { + $beitrag = $ausgabe['beitrag']; + foreach (['heading', 'source', 'source_url', 'image_credit'] as $feld) { + if (trim((string) ($beitrag[$feld] ?? '')) === '') { + $errors[] = "Stadionzeitung '{$ausgabe['slug']}': Beitrag ohne {$feld}."; + } + } + $quelle = (string) ($beitrag['source_url'] ?? ''); + if (!filter_var($quelle, FILTER_VALIDATE_URL) || parse_url($quelle, PHP_URL_SCHEME) !== 'https') { + $errors[] = "Stadionzeitung '{$ausgabe['slug']}': Beitrag mit ungültiger Quellen-URL."; + } + $foto = $beitrag['image'] ?? []; + if (empty($foto['base']) || empty($foto['widths']) || empty($foto['alt'])) { + $errors[] = "Stadionzeitung '{$ausgabe['slug']}': Beitragsfoto oder Alt-Text fehlt."; + } + foreach ($foto['widths'] ?? [] as $w) { + if (!is_file(PUBLIC_PATH . '/assets/' . ($foto['base'] ?? '') . "-{$w}.jpg")) { + $errors[] = "Stadionzeitung '{$ausgabe['slug']}': Beitragsfoto fehlt ({$w}px)."; + } + } + if (!is_file(PUBLIC_PATH . '/assets/img/stadionzeitung/' . $ausgabe['slug'] . '/qr-beitrag.png')) { + $errors[] = "Stadionzeitung '{$ausgabe['slug']}': Beitrags-QR fehlt."; + } + } if (is_array($ausgabe['rueckblick'] ?? null)) { $rueckblick = $ausgabe['rueckblick']; $foto = $rueckblick['image'] ?? []; diff --git a/bin/stadionzeitung-add.php b/bin/stadionzeitung-add.php index 5cf8b18..db01929 100644 --- a/bin/stadionzeitung-add.php +++ b/bin/stadionzeitung-add.php @@ -161,6 +161,7 @@ $rueckseite = $findOne('rueckseite'); $spieler = $findOne('spieler'); $verfasser = $findOne('verfasser'); $rueckblickFoto = $findOne('rueckblick'); +$beitragFoto = $findOne('beitrag'); $galerieQuellen = $findAll('galerie'); // Anzeigen kommen NICHT mehr aus dem Ablieferordner, sondern aus dem dauerhaften // Bestand (data/anzeigen.json, gepflegt über bin/anzeige-add.php). Ein noch @@ -201,6 +202,16 @@ foreach ($existing['issues'] ?? [] as $entry) { $oldVorwort = $oldIssue['vorwort'] ?? null; // Interview wie das Vorwort chat-gepflegt — überlebt erneute Läufe ebenso. $oldInterview = $oldIssue['interview'] ?? null; +$beitrag = $oldIssue['beitrag'] ?? null; +if (is_array($beitrag) && $beitragFoto === null) { + $fail('Redaktioneller Beitrag vorhanden, aber beitrag.* fehlt im Ablieferordner.'); +} +if (is_array($beitrag)) { + $beitragLink = trim((string) ($beitrag['source_url'] ?? '')); + if (!filter_var($beitragLink, FILTER_VALIDATE_URL) || parse_url($beitragLink, PHP_URL_SCHEME) !== 'https') { + $fail('Beitrag: source_url muss eine gültige HTTPS-Adresse sein.'); + } +} // Aufruf-Seite (z. B. Sportfest-Helferaufruf) ebenso chat-gepflegt. $oldAufruf = $oldIssue['aufruf'] ?? null; // Sportfest-Ausgabe: Slug-Verweis auf data/veranstaltungen.json (Programm, @@ -417,6 +428,10 @@ try { if ($oldInterview !== null) { $aufschlag[] = $live('interview'); } + if (is_array($beitrag)) { + $beitrag['image'] = $bild($beitragFoto, 'beitrag', (string) ($beitrag['image']['alt'] ?? $beitrag['heading'] ?? 'Vereinsbeitrag')); + $aufschlag[] = $live('beitrag'); + } // Aufruf-Seite (Helferaufruf o. Ä.) reiht sich in denselben anzeigenfreien // Aufschlag ein, direkt hinter Vorwort/Interview — ein redaktioneller // Einstieg ohne Werbung dazwischen (chat-gepflegtes `aufruf`-Feld). @@ -761,6 +776,12 @@ foreach (['mitmachen', 'news', 'termine', 'matchcenter', 'mitglied-werden', 'his // (qr-aufruf.png) — der wird bei jedem Lauf ohnehin neu befüllt. club_url() // wie überall, damit der gedruckte Code auf die echte Domain zeigt. $aufrufLink = trim((string) ($oldAufruf['link'] ?? '')); +$beitragLink = trim((string) ($beitrag['source_url'] ?? '')); +if ($beitragLink !== '') { + if (!generate_qr_png($beitragLink, "{$destDir}/qr-beitrag.png")) { + $fail('QR-Code für den redaktionellen Beitrag konnte nicht erzeugt werden.'); + } +} if ($aufrufLink !== '') { if (generate_qr_png(club_url($aufrufLink), "{$destDir}/qr-aufruf.png")) { $log("QR-Code für die Aufruf-Seite (/{$aufrufLink}) erzeugt."); @@ -838,6 +859,7 @@ $issues[] = [ 'qr_image' => $qrImage, 'vorwort' => $oldVorwort, 'interview' => $oldInterview, + 'beitrag' => $beitrag, 'aufruf' => $oldAufruf, 'veranstaltung' => $oldVeranstaltung, 'cover' => $oldCover, diff --git a/data/instagram.json b/data/instagram.json index 252a308..562f77e 100644 --- a/data/instagram.json +++ b/data/instagram.json @@ -1,7 +1,25 @@ { - "fetched_at": 1792183583, + "fetched_at": 1792834512, "username": "tsv08kulmbach", "posts": [ + { + "shortcode": "De0vwrajLXG", + "caption": "🔴⚫️ Vorschau", + "alt": "🔴⚫️ Vorschau", + "taken_at": 1792731262, + "is_video": false, + "image": "img/instagram/De0vwrajLXG.jpg", + "url": "https://www.instagram.com/p/De0vwrajLXG/" + }, + { + "shortcode": "Derh4uhDLDh", + "caption": "🔴⚫️ Ergebnisse \nKann sich sehen lassen 🙌", + "alt": "🔴⚫️ Ergebnisse", + "taken_at": 1792421998, + "is_video": false, + "image": "img/instagram/Derh4uhDLDh.jpg", + "url": "https://www.instagram.com/p/Derh4uhDLDh/" + }, { "shortcode": "DehQgDrDB0Z", "caption": "🔴⚫️ Vorschau", diff --git a/data/stadionzeitung.json b/data/stadionzeitung.json index bf52dab..edca1ca 100644 --- a/data/stadionzeitung.json +++ b/data/stadionzeitung.json @@ -1,5 +1,618 @@ { "issues": [ + { + "slug": "2026-10-24-tdc-lindau-2", + "opponent": "TDC Lindau 2", + "opponent_short": null, + "gegner_teams": [], + "date": "2026-10-24", + "team_key": "erste-mannschaft", + "status": "entwurf", + "pages": 44, + "player_image": { + "base": "img/stadionzeitung/2026-10-24-tdc-lindau-2/spieler", + "widths": [ + 320, + 640, + 1000 + ], + "ext": "png", + "alt": "" + }, + "qr_image": "img/stadionzeitung/2026-10-24-tdc-lindau-2/qr.png", + "vorwort": { + "heading": "Willkommen im Katzbachtal", + "paragraphs": [ + "Liebe Fußballfreunde,", + "herzlich willkommen im Katzbachtal! Wir begrüßen unsere Gäste von TDC Lindau 2, ihre Fans und den Schiedsrichter. Unterstützt unsere Mannschaft lautstark und fair – auf ein spannendes Heimspiel!", + "Auch unsere Jugend steht heute im Mittelpunkt: Die Bayerische Rundschau berichtet über den Aufschwung bei unseren Nachwuchskickern. Den Lesetipp findet ihr in diesem Heft. Danke an alle, die sich für unseren Verein einsetzen!" + ], + "closing": "Rot-schwarze Grüße", + "signee_name": "Benedikt Höfner", + "signee_role": "Sportlicher Leiter" + }, + "interview": null, + "beitrag": { + "kicker": "Unsere Jugend · Lesetipp", + "heading": "Unsere Jugend wächst", + "paragraphs": [ + "Rund 90 Jungen und Mädchen spielen beim TSV 08 Kulmbach Fußball. Die Bayerische Rundschau berichtet über den Aufschwung unserer Jugend und das Engagement hinter den Kulissen.", + "Was Fabian Früh und Kerstin Tauber antreibt und welche Aufgaben noch warten, lest ihr im Beitrag von Christian Schuberth." + ], + "source": "Christian Schuberth · Bayerische Rundschau · 20.10.2026: „TSV 08 gibt im Nachwuchsbereich wieder Gas“.", + "source_url": "https://www.fraenkischertag.de/sport/lokalsport-kulmbach/das-steckt-hinter-dem-nachwuchs-boom-beim-tsv-08-kulmbach-art-595346", + "cta_label": "Bei der Bayerischen Rundschau", + "image_credit": "Foto: privat · Quelle: Bayerische Rundschau", + "image": { + "type": "image", + "base": "img/stadionzeitung/2026-10-24-tdc-lindau-2/beitrag", + "widths": [ + 480, + 742 + ], + "alt": "Fabian Früh mit vier Nachwuchskickern des TSV 08 Kulmbach auf dem Fußballplatz." + } + }, + "aufruf": null, + "veranstaltung": null, + "cover": { + "teaser": "Unsere Jugend wächst – die Bayerische Rundschau berichtet" + }, + "galerie": { + "kicker": "Rückblick", + "heading": "Erinnerungen an Kirchleus", + "bilder": [ + { + "source": "01-IMG_0586_01.jpg", + "base": "img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-01", + "widths": [ + 640, + 1200 + ], + "alt": "Einer unserer Spieler holt in Kirchleus zum Schuss aus, ein Gegenspieler kommt einen Schritt zu spät." + }, + { + "source": "02-IMG_0692_01.jpg", + "base": "img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-02", + "widths": [ + 640, + 1200 + ], + "alt": "Zweikampf um den Ball: Einer unserer Spieler setzt sich gegen einen Kirchleuser durch." + }, + { + "source": "03-IMG_0565_01.jpg", + "base": "img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-03", + "widths": [ + 640, + 1200 + ], + "alt": "Einer unserer Spieler treibt den Ball nach vorne, ein Kirchleuser läuft heran." + }, + { + "source": "04-IMG_0612_01.jpg", + "base": "img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-04", + "widths": [ + 640, + 1200 + ], + "alt": "An der Eckfahne nimmt einer unserer Spieler den Ball an." + }, + { + "source": "05-IMG_0571_01.jpg", + "base": "img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-05", + "widths": [ + 640, + 1200 + ], + "alt": "Einer unserer Spieler läuft in der Herbstsonne über den Platz." + } + ], + "text": "Ein Rückblick auf unser Auswärtsspiel am 27. September beim 1. FC Kirchleus, das wir knapp mit 2:3 verloren haben." + }, + "rueckblick": { + "kicker": "Letztes Heimspiel", + "heading": "Fünf Tore, drei Punkte", + "meta": "TSV 08 Kulmbach – SV Fortuna Untersteinach · 17. Oktober 2026", + "result": "5:0", + "paragraphs": [ + "Unsere Erste gewann das letzte Heimspiel gegen den SV Fortuna Untersteinach mit 5:0. Drei Punkte und fünf Tore ohne Gegentreffer – die Freude darüber zeigt unser Mannschaftsfoto nach dem Spiel.", + "Heute geht es im Katzbachtal gegen TDC Lindau 2 weiter. Wir freuen uns auf eure Unterstützung!" + ], + "credit": "Foto/Grafik: TSV 08 Kulmbach · Instagram, 19.10.2026", + "source_url": "https://www.instagram.com/p/Derh4uhDLDh/", + "image": { + "type": "image", + "base": "img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckblick", + "widths": [ + 480, + 640 + ], + "alt": "Unsere Mannschaft feiert in der Kabine den 5:0-Heimsieg gegen Untersteinach; darunter stehen das Ergebnis und die Vereinswappen." + } + }, + "verfasser_photo": { + "base": "img/stadionzeitung/2026-10-24-tdc-lindau-2/verfasser", + "widths": [ + 120, + 222 + ], + "alt": "" + }, + "termine_snapshot": [ + { + "date": "2026-11-01", + "date_end": null, + "title": "TSV 08 Kulmbach – SSV Schwarzenbach a.Wald", + "department": "fussball", + "type": "spiel", + "meta": "Heimspiel · Frauen KL Ost", + "href": "/matchcenter", + "kickoff": "2026-11-01T10:30", + "team_name": "Frauenmannschaft", + "league": "Frauen KL Ost", + "raw": { + "kickoff": "2026-11-01T10:30", + "date_display": "So, 01.11.", + "time_display": "10:30 Uhr", + "competition": "Liga", + "home": "TSV 08 Kulmbach", + "away": "SSV Schwarzenbach a.Wald", + "home_crest": "img/crests/00ES8GNK3G000012VV0AG08LVUPGND5I.png", + "away_crest": "img/crests/00ES8GNK9O00000PVV0AG08LVUPGND5I.png", + "is_home": true, + "team_key": "damen", + "team_name": "Frauenmannschaft" + } + }, + { + "date": "2026-11-01", + "date_end": null, + "title": "SV Hutschdorf – TSV 08 Kulmbach", + "department": "fussball", + "type": "spiel", + "meta": "Auswärtsspiel · Kreisklasse", + "href": "/matchcenter", + "kickoff": "2026-11-01T14:00", + "team_name": "1. Mannschaft", + "league": "Kreisklasse", + "raw": { + "kickoff": "2026-11-01T14:00", + "date_display": "So, 01.11.", + "time_display": "14:00 Uhr", + "competition": "Liga", + "home": "SV Hutschdorf", + "away": "TSV 08 Kulmbach", + "home_crest": "img/crests/00ES8GNK3G00000LVV0AG08LVUPGND5I.png", + "away_crest": "img/crests/00ES8GNK3G000012VV0AG08LVUPGND5I.png", + "is_home": false, + "team_key": "erste-mannschaft", + "team_name": "1. Mannschaft" + } + }, + { + "date": "2026-11-07", + "date_end": null, + "title": "TSV 08 Kulmbach – SpVgg Wonsees", + "department": "fussball", + "type": "spiel", + "meta": "Heimspiel · Kreisklasse", + "href": "/matchcenter", + "kickoff": "2026-11-07T14:00", + "team_name": "1. Mannschaft", + "league": "Kreisklasse", + "raw": { + "kickoff": "2026-11-07T14:00", + "date_display": "Sa, 07.11.", + "time_display": "14:00 Uhr", + "competition": "Liga", + "home": "TSV 08 Kulmbach", + "away": "SpVgg Wonsees", + "home_crest": "img/crests/00ES8GNK3G000012VV0AG08LVUPGND5I.png", + "away_crest": "img/crests/00ES8GNK4O000016VV0AG08LVUPGND5I.png", + "is_home": true, + "team_key": "erste-mannschaft", + "team_name": "1. Mannschaft" + } + }, + { + "date": "2026-11-08", + "date_end": null, + "title": "TSV 08 Kulmbach – ATG Tröstau", + "department": "fussball", + "type": "spiel", + "meta": "Heimspiel · Frauen KL Ost", + "href": "/matchcenter", + "kickoff": "2026-11-08T11:00", + "team_name": "Frauenmannschaft", + "league": "Frauen KL Ost", + "raw": { + "kickoff": "2026-11-08T11:00", + "date_display": "So, 08.11.", + "time_display": "11:00 Uhr", + "competition": "Liga", + "home": "TSV 08 Kulmbach", + "away": "ATG Tröstau", + "home_crest": "img/crests/00ES8GNK3G000012VV0AG08LVUPGND5I.png", + "away_crest": "img/crests/00ES8GNKDG00000OVV0AG08LVUPGND5I.png", + "is_home": true, + "team_key": "damen", + "team_name": "Frauenmannschaft" + } + }, + { + "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": "beitrag" + }, + { + "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": "live", + "page": "rueckblick" + }, + { + "type": "image", + "base": "img/anzeigen/muench-richterstegner", + "widths": [ + 480, + 960, + 1440 + ], + "alt": "Anzeige: Planungsbüro Münch und Richter & Stegner Steuerungstechnik" + }, + { + "type": "image", + "base": "img/anzeigen/malteser-herrmann", + "widths": [ + 480, + 960, + 1440 + ], + "alt": "Anzeige: Malteser-Hilfsdienst und Schlosserei Herrmann, Kulmbach" + }, + { + "type": "live", + "page": "galerie" + }, + { + "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": "gegner", + "team": "erste-mannschaft" + }, + { + "type": "image", + "base": "img/anzeigen/karl-geyer", + "widths": [ + 480, + 960, + 1440 + ], + "alt": "Anzeige: Geyer Farbe + Raum, Kulmbach, Starthilfe Boden" + }, + { + "type": "image", + "base": "img/anzeigen/gsb", + "widths": [ + 480, + 960, + 1440 + ], + "alt": "Anzeige: GSB Security und GSB Gebäude Service, Bindlach" + }, + { + "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/physiofit-wohlfart", + "widths": [ + 480, + 960, + 1440 + ], + "alt": "Anzeige: Physio Fit Neudrossenfeld und Textilpflege Wohlfart, Kulmbach" + }, + { + "type": "live", + "page": "vorschau" + }, + { + "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": "termine" + }, + { + "type": "image", + "base": "img/anzeigen/mattis-ka2", + "widths": [ + 480, + 960, + 1440 + ], + "alt": "Anzeige: Karl Mattis Flaschnerei und KA2 Fitness, Kulmbach" + }, + { + "type": "live", + "page": "news" + }, + { + "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": "kontakt" + }, + { + "type": "image", + "base": "img/anzeigen/trapper-verkehrsakademie", + "widths": [ + 480, + 960, + 1440 + ], + "alt": "Anzeige: Trapper GmbH Entsorgungsfachbetrieb und Verkehrsakademie, Kulmbach" + }, + { + "type": "live", + "page": "mitglied" + }, + { + "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": "image", + "base": "img/anzeigen/hanft", + "widths": [ + 480, + 960, + 1440 + ], + "alt": "Anzeige: Hanft Bürotechnik GmbH, Kulmbach" + }, + { + "type": "live", + "page": "historie" + }, + { + "type": "image", + "base": "img/anzeigen/elma-kleinheinz", + "widths": [ + 480, + 960, + 1440 + ], + "alt": "Anzeige: elma GmbH Bayreuth und Metzgerei Kleinheinz, Kulmbach" + }, + { + "type": "live", + "page": "sportheim" + }, + { + "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": "image", + "base": "img/anzeigen/gagel-pickelmann", + "widths": [ + 480, + 960, + 1440 + ], + "alt": "Anzeige: Gagel Bäder und Mineralölhandel Pickelmann" + }, + { + "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/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-10-24-tdc-lindau-2/rueckseite", + "widths": [ + 480, + 960, + 1410 + ], + "alt": "Rückseite der Stadionzeitung gegen TDC Lindau 2" + } + ] + }, { "slug": "2026-10-17-sv-fortuna-untersteinach", "opponent": "SV Fortuna Untersteinach", diff --git a/public/assets/css/components.css b/public/assets/css/components.css index d0a0be8..62ada24 100644 --- a/public/assets/css/components.css +++ b/public/assets/css/components.css @@ -8628,6 +8628,45 @@ p.stadionzeitung-turnier__label:first-of-type { object-fit: cover; } +/* === stadionzeitung-beitrag (redaktioneller Lesetipp) === */ +.stadionzeitung-beitrag { + display: flex; + flex-direction: column; + min-height: 100%; +} +.stadionzeitung-beitrag__figure { + margin: 0 0 3cqi; +} +.stadionzeitung-beitrag .stadionzeitung-page__heading { + font-size: 7cqi; +} +.stadionzeitung-beitrag .stadionzeitung-vorwort__text { + font-size: 3.2cqi; + line-height: 1.4; + margin-bottom: 2.4cqi; +} +.stadionzeitung-beitrag__foto { + display: block; + width: 100%; + height: 48cqi; + object-fit: contain; +} +.stadionzeitung-beitrag__source { + font-size: 2.5cqi; + line-height: 1.4; + color: var(--clr-text-muted); + margin: 0 0 4cqi; +} +.stadionzeitung-beitrag .stadionzeitung-page__cta { + margin-top: auto; + color: inherit; + text-decoration: none; +} +.stadionzeitung-beitrag .stadionzeitung-page__cta-qr { + width: 20cqi; + height: 20cqi; +} + /* === stadionzeitung-rueckblick (redaktionelle Seite mit Bildnachweis) === */ .stadionzeitung-rueckblick { display: flex; @@ -8657,6 +8696,8 @@ p.stadionzeitung-turnier__label:first-of-type { display: block; width: 100%; height: auto; + max-height: 65cqi; + object-fit: contain; } .stadionzeitung-rueckblick__credit { diff --git a/public/assets/img/crests/01S7GFJ77S000000VS548984VTJ68QLL.png b/public/assets/img/crests/01S7GFJ77S000000VS548984VTJ68QLL.png new file mode 100644 index 0000000..165b4a5 Binary files /dev/null and b/public/assets/img/crests/01S7GFJ77S000000VS548984VTJ68QLL.png differ diff --git a/public/assets/img/instagram/De0vwrajLXG.jpg b/public/assets/img/instagram/De0vwrajLXG.jpg new file mode 100644 index 0000000..95c45aa Binary files /dev/null and b/public/assets/img/instagram/De0vwrajLXG.jpg differ diff --git a/public/assets/img/instagram/Derh4uhDLDh.jpg b/public/assets/img/instagram/Derh4uhDLDh.jpg new file mode 100644 index 0000000..be7de10 Binary files /dev/null and b/public/assets/img/instagram/Derh4uhDLDh.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/beitrag-480.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/beitrag-480.jpg new file mode 100644 index 0000000..46bb0ee Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/beitrag-480.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/beitrag-742.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/beitrag-742.jpg new file mode 100644 index 0000000..796d508 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/beitrag-742.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-01-1200.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-01-1200.jpg new file mode 100644 index 0000000..bd6ccec Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-01-1200.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-01-640.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-01-640.jpg new file mode 100644 index 0000000..b9205e0 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-01-640.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-02-1200.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-02-1200.jpg new file mode 100644 index 0000000..79271e2 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-02-1200.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-02-640.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-02-640.jpg new file mode 100644 index 0000000..c7686e2 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-02-640.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-03-1200.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-03-1200.jpg new file mode 100644 index 0000000..8aab24e Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-03-1200.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-03-640.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-03-640.jpg new file mode 100644 index 0000000..6e79645 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-03-640.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-04-1200.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-04-1200.jpg new file mode 100644 index 0000000..f916b82 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-04-1200.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-04-640.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-04-640.jpg new file mode 100644 index 0000000..814bb7f Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-04-640.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-05-1200.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-05-1200.jpg new file mode 100644 index 0000000..5ede061 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-05-1200.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-05-640.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-05-640.jpg new file mode 100644 index 0000000..95c00d6 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/galerie-05-640.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/qr-beitrag.png b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/qr-beitrag.png new file mode 100644 index 0000000..32cb1e3 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/qr-beitrag.png differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/qr.png b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/qr.png new file mode 100644 index 0000000..fa461d5 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/qr.png differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckblick-480.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckblick-480.jpg new file mode 100644 index 0000000..0bf033b Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckblick-480.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckblick-640.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckblick-640.jpg new file mode 100644 index 0000000..e62b5aa Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckblick-640.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckseite-1410.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckseite-1410.jpg new file mode 100644 index 0000000..bed8a2c Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckseite-1410.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckseite-480.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckseite-480.jpg new file mode 100644 index 0000000..10a184c Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckseite-480.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckseite-960.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckseite-960.jpg new file mode 100644 index 0000000..fc7ca9c Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/rueckseite-960.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/spieler-1000.png b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/spieler-1000.png new file mode 100644 index 0000000..c64c1a0 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/spieler-1000.png differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/spieler-320.png b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/spieler-320.png new file mode 100644 index 0000000..6d807e2 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/spieler-320.png differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/spieler-640.png b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/spieler-640.png new file mode 100644 index 0000000..cfcd852 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/spieler-640.png differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/verfasser-120.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/verfasser-120.jpg new file mode 100644 index 0000000..4ea2141 Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/verfasser-120.jpg differ diff --git a/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/verfasser-222.jpg b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/verfasser-222.jpg new file mode 100644 index 0000000..a378e1b Binary files /dev/null and b/public/assets/img/stadionzeitung/2026-10-24-tdc-lindau-2/verfasser-222.jpg differ