Stadionzeitung gegen TDC Lindau 2 mit Jugendbeitrag vorbereiten

This commit is contained in:
2026-10-24 11:52:57 +02:00
parent bd8f1dd129
commit fd305673aa
37 changed files with 790 additions and 2 deletions

View File

@@ -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 <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`) |
| 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 `<ordner>/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

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/** Redaktioneller Lesetipp. Text/Quelle: Chat-Pflege; Foto/QR: add-Skript. */
$beitrag = $issue['beitrag'] ?? null;
if (!is_array($beitrag)) {
return;
}
$sourceUrl = (string) ($beitrag['source_url'] ?? '');
$validUrl = filter_var($sourceUrl, FILTER_VALIDATE_URL)
&& parse_url($sourceUrl, PHP_URL_SCHEME) === 'https';
$qrPath = 'img/stadionzeitung/' . (string) ($issue['slug'] ?? '') . '/qr-beitrag.png';
?>
<div class="stadionzeitung-beitrag">
<header class="stadionzeitung-page__head">
<p class="stadionzeitung-page__kicker"><?= e((string) ($beitrag['kicker'] ?? 'Lesetipp')) ?></p>
<h2 class="stadionzeitung-page__heading"><?= e((string) ($beitrag['heading'] ?? '')) ?></h2>
</header>
<span class="stadionzeitung-page__rule" aria-hidden="true"></span>
<figure class="stadionzeitung-beitrag__figure">
<?php component('img', ['image' => $beitrag['image'] ?? [], 'sizes' => '(min-width: 520px) 430px, 90vw', 'class' => 'stadionzeitung-beitrag__foto', 'eager' => $eager ?? false]); ?>
<figcaption class="stadionzeitung-rueckblick__credit"><?= e((string) ($beitrag['image_credit'] ?? '')) ?></figcaption>
</figure>
<?php foreach ($beitrag['paragraphs'] ?? [] as $paragraph): ?>
<p class="stadionzeitung-vorwort__text"><?= e((string) $paragraph) ?></p>
<?php endforeach; ?>
<p class="stadionzeitung-beitrag__source"><?= e((string) ($beitrag['source'] ?? '')) ?></p>
<?php if ($validUrl): ?>
<a class="stadionzeitung-page__cta" href="<?= e($sourceUrl) ?>" target="_blank" rel="noopener noreferrer">
<div class="stadionzeitung-page__cta-text">
<p class="stadionzeitung-page__cta-label"><?= e((string) ($beitrag['cta_label'] ?? 'Lesetipp')) ?></p>
<p class="stadionzeitung-page__cta-title">Den vollständigen Beitrag lesen</p>
<p class="stadionzeitung-page__cta-link"><?= e((string) parse_url($sourceUrl, PHP_URL_HOST)) ?></p>
</div>
<?php if (is_file(PUBLIC_PATH . '/assets/' . $qrPath)): ?>
<div class="stadionzeitung-page__cta-abriss">
<img class="stadionzeitung-page__cta-qr" src="<?= e(asset($qrPath)) ?>" alt="QR-Code zum Originalartikel" width="100" height="100" loading="lazy" decoding="async">
<span class="stadionzeitung-page__cta-scan">Zum Artikel</span>
</div>
<?php endif; ?>
</a>
<?php endif; ?>
</div>

View File

@@ -50,6 +50,9 @@ $type = $entry['type'] ?? 'image';
<?php case 'interview': ?>
<?php component('stadionzeitung-interview', ['issue' => $issue]); ?>
<?php break; ?>
<?php case 'beitrag': ?>
<?php component('stadionzeitung-beitrag', ['issue' => $issue, 'eager' => $eager]); ?>
<?php break; ?>
<?php case 'aufruf': ?>
<?php component('stadionzeitung-aufruf', ['issue' => $issue]); ?>
<?php break; ?>

View File

@@ -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,

View File

@@ -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,

View File

@@ -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'] ?? [];

View File

@@ -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,

View File

@@ -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",

View File

@@ -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",

View File

@@ -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 {

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB