From f2f193b223712119c44f323e76eb4e59b2ede31a Mon Sep 17 00:00:00 2001
From: fs
Date: Thu, 3 Sep 2026 10:57:52 +0200
Subject: [PATCH] Sportfest-Ausgabe der Stadionzeitung zum Heimspiel gegen ATS
Wartenfels
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Neue Live-Seiten, geschaltet vom chat-gepflegten Feld `veranstaltung` (Slug aus
data/veranstaltungen.json) — Programm und Turnierpläne kommen damit aus derselben
Quelle wie die Website, nicht als Kopie ins Heft:
- Programm-Seite (programm[], rendert zugleich auf /veranstaltungen/sportfest-2026)
- vier Turnierplan-Seiten (turniere[]) mit leerer Ergebnisspalte und leerer
Abschlusstabelle zum Eintragen am Platz
- Gegnerseite je Mannschaft mit Heimspiel am Fest statt nur der tragenden:
stadionzeitung_gegner_fixtures() ist die einzige Stelle, die das entscheidet,
Seitenplanung und Snapshot lesen beide dort. `gegner` ist deshalb eine Map je
Team-Key; die Vorschau überspringt jedes Spiel mit eigener Gegnerseite
- Danke-Seite mit Partner-Logo-Wand aus data/partners.json und QR auf
/partner-werden, dazu das Anzeigen-Feld `platzierung` für Motive mit festem
Platz auf einer Live-Seite (Emons über der Logo-Wand). Vorher klebte der Dank
als leere Fläche unten an einem Anzeigen-Bild
- Cover einer Sonderausgabe: das Fest als Titelgeschichte statt einer der drei
Begegnungen, dazu das Hauptplakat schräg gelegt an Stelle des Spielerfotos
Ausgabe 2026-09-04-ats-wartenfels als ENTWURF angelegt, 48 Seiten (12 Bögen),
Tabellen auf gerader Seite, längster Anzeigenblock zwei. Veröffentlicht wird auf
dem Server, dort liegt der frische Matchcenter-Stand.
Nebenbei behoben: bin/anzeige-add.php räumte beim Anlegen alle Motive weg, deren
Slug mit demselben Text beginnt (`emons` löschte `emons-partnerdank` und
`emons-stuebinger`). Muster jetzt auf die Breiten-Endung eingeschränkt.
Co-Authored-By: Claude Opus 5 (1M context)
Claude-Session: https://claude.ai/code/session_01KubsajzuV6N6QsSd3xKNjC
---
CLAUDE.md | 110 ++-
app/components/stadionzeitung-cards.php | 11 +-
app/components/stadionzeitung-cover.php | 70 +-
app/components/stadionzeitung-danke.php | 65 ++
app/components/stadionzeitung-gegner.php | 16 +-
app/components/stadionzeitung-programm.php | 71 ++
app/components/stadionzeitung-seite.php | 28 +-
app/components/stadionzeitung-turnier.php | 158 ++++
app/components/stadionzeitung-viewer.php | 2 +-
app/helpers.php | 138 ++-
app/pages/veranstaltung.php | 14 +-
bin/anzeige-add.php | 6 +-
bin/preflight.php | 35 +
bin/stadionzeitung-add.php | 114 ++-
data/anzeigen.json | 27 +-
data/stadionzeitung.json | 842 ++++++++++++++++++
data/veranstaltungen.json | 297 +++++-
public/assets/css/components.css | 346 +++++++
public/assets/img/anzeigen/emons-1141.jpg | Bin 0 -> 140145 bytes
public/assets/img/anzeigen/emons-480.jpg | Bin 0 -> 37373 bytes
public/assets/img/anzeigen/emons-960.jpg | Bin 0 -> 105523 bytes
.../img/anzeigen/emons-partnerdank-1440.jpg | Bin 212030 -> 0 bytes
.../img/anzeigen/emons-partnerdank-480.jpg | Bin 39594 -> 0 bytes
.../img/anzeigen/emons-partnerdank-960.jpg | Bin 111016 -> 0 bytes
.../2026-09-04-ats-wartenfels/qr.png | Bin 0 -> 579 bytes
.../rueckseite-1410.jpg | Bin 0 -> 407148 bytes
.../rueckseite-480.jpg | Bin 0 -> 80526 bytes
.../rueckseite-960.jpg | Bin 0 -> 227527 bytes
.../spieler-1000.png | Bin 0 -> 1971598 bytes
.../2026-09-04-ats-wartenfels/spieler-320.png | Bin 0 -> 229284 bytes
.../2026-09-04-ats-wartenfels/spieler-640.png | Bin 0 -> 823095 bytes
.../verfasser-120.jpg | Bin 0 -> 3109 bytes
.../verfasser-222.jpg | Bin 0 -> 6893 bytes
.../assets/img/stadionzeitung/qr-historie.png | Bin 425 -> 440 bytes
.../img/stadionzeitung/qr-instagram.png | Bin 430 -> 431 bytes
.../img/stadionzeitung/qr-mitmachen.png | Bin 421 -> 424 bytes
public/assets/img/stadionzeitung/qr-news.png | Bin 427 -> 421 bytes
.../img/stadionzeitung/qr-partner-werden.png | Bin 503 -> 497 bytes
.../stadionzeitung/qr-sportheimbuchung.png | Bin 505 -> 496 bytes
.../assets/img/stadionzeitung/qr-termine.png | Bin 426 -> 426 bytes
40 files changed, 2273 insertions(+), 77 deletions(-)
create mode 100644 app/components/stadionzeitung-danke.php
create mode 100644 app/components/stadionzeitung-programm.php
create mode 100644 app/components/stadionzeitung-turnier.php
create mode 100644 public/assets/img/anzeigen/emons-1141.jpg
create mode 100644 public/assets/img/anzeigen/emons-480.jpg
create mode 100644 public/assets/img/anzeigen/emons-960.jpg
delete mode 100644 public/assets/img/anzeigen/emons-partnerdank-1440.jpg
delete mode 100644 public/assets/img/anzeigen/emons-partnerdank-480.jpg
delete mode 100644 public/assets/img/anzeigen/emons-partnerdank-960.jpg
create mode 100644 public/assets/img/stadionzeitung/2026-09-04-ats-wartenfels/qr.png
create mode 100644 public/assets/img/stadionzeitung/2026-09-04-ats-wartenfels/rueckseite-1410.jpg
create mode 100644 public/assets/img/stadionzeitung/2026-09-04-ats-wartenfels/rueckseite-480.jpg
create mode 100644 public/assets/img/stadionzeitung/2026-09-04-ats-wartenfels/rueckseite-960.jpg
create mode 100644 public/assets/img/stadionzeitung/2026-09-04-ats-wartenfels/spieler-1000.png
create mode 100644 public/assets/img/stadionzeitung/2026-09-04-ats-wartenfels/spieler-320.png
create mode 100644 public/assets/img/stadionzeitung/2026-09-04-ats-wartenfels/spieler-640.png
create mode 100644 public/assets/img/stadionzeitung/2026-09-04-ats-wartenfels/verfasser-120.jpg
create mode 100644 public/assets/img/stadionzeitung/2026-09-04-ats-wartenfels/verfasser-222.jpg
diff --git a/CLAUDE.md b/CLAUDE.md
index b866950..eb9e6eb 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -54,10 +54,10 @@ selbst gehosteter Stack ohne Abhängigkeiten von Drittanbietern.
| Partner/Sponsoren | `data/partners.json` | — |
| Instagram-Cache | `data/instagram.json` + `public/assets/img/instagram/` | **maschinenverwaltet von `bin/instagram-sync.php` — nie von Hand editieren** |
| 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` und `interview`.** 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` 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 |
+| 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 |
+| 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, 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`) |
| News (`/news` + optionale Detailseiten) | `data/news.json` (redaktionelle Meldungen; `artikel[]` = Fließtext-Blöcke, `status` = Entwurf/veröffentlicht) — Instagram-Posts und Ergebnisse kommen weiterhin nur aus `data/instagram.json`/`data/matchcenter.json`, werden auf der News-Seite nur gemergt | Von Claude im Chat gepflegt wie `events.json`/`teams.json` — **keine** Automatisierung, kein Bin-Skript. Instagram/Ergebnisse nie hier duplizieren. `text` bleibt der Teaser (Feed, Meta-Description, Article-Schema), der Artikel steht in `artikel[]` |
| Dateiablage `/dateien` (Konfiguration) | `public/dateien/_filesconfig.php` (Rechte pro Konto: `storage/dateien/system/users/admin/config.php`) | **`storage/dateien/system/config/config.php` von Hand editieren** — die App generiert sie selbst und überschreibt sie bei jedem Update. Passwörter/Lizenz gehören in `config.php` (`dateien.*`) |
| Icons | `public/assets/icons/` (Bootstrap Icons, lokal) via `icon()`-Helper | Inline-SVG-Pfade von Hand ins Markup, Emoji/Unicode-Glyphen, gezeichnete CSS-Icons, Icon-Font/CDN |
@@ -516,18 +516,23 @@ kein Download-Link — nur die Web-Ansicht. Der Ablauf pro Heimspiel ist damit:
**Feste Vorlage** (seit Interview 29.07.2026, Anzeigen-Verteilung überarbeitet 31.07.2026) — der
Inhalt ist eine Liste von **Blöcken**, die Anzeigen füllen die Lücken dazwischen (⌷ = Lücke):
```
-Cover → [Vorwort → Interview (nur wenn die Ausgabe eins trägt)] ⌷ Ergebnis-Rückblick ⌷
- Gegnerstatistik der tragenden Mannschaft (automatisch aus deren Tabelle) ⌷
+Cover → [Vorwort → Programm* → Interview → Aufruf (jedes nur wenn die Ausgabe es trägt)] ⌷
+ [Turnierpläne*: je Turnier eine Seite, EIN Block] ⌷ Ergebnis-Rückblick ⌷
+ [Gegnerstatistik: normal nur die tragende Mannschaft, in der Sportfest-Ausgabe je
+ Mannschaft mit Heimspiel am Fest eine — EIN Block, automatisch aus deren Tabelle] ⌷
[je Mannschaft eine Tabelle, Reihenfolge = matchcenter.json — EIN Block, dazwischen nie eine
Anzeige; Erste + Zweite bilden im gefalteten Heft eine Doppelseite] ⌷ Vorschau ⌷ Termine ⌷
News ⌷ Ansprechpartner ⌷ Mitglied-werden-Poster ⌷ Historie-Teaser (Website-Verweis +
Material-Aufruf an club.email_historie) ⌷ Sportheim-Seite (Vermietungs-CTA im Stil der alten
- Canva-Seite) ⌷ Partner-werden-Seite (Anzeigen-Akquise im eigenen Heft, Sie-Form) ⌷
+ Canva-Seite) ⌷ Partner-werden-Seite (Anzeigen-Akquise im eigenen Heft, Sie-Form) →
+ Danke-Seite (Partner-Logo-Wand + QR auf /partner-werden, ohne Lücke davor: Akquise und Dank
+ gehören zusammen) ⌷
Momente-Seite (2×2-Galerie der neuesten Instagram-Fotos + Folge-uns-QR, Snapshot beim
Veröffentlichen) ⌷ Impressum (aus club.json) → Rückseite
```
Alle Seiten außer Rückseite und Vorwort-Fallback sind Live-Seiten. Nach dem Cover, im Aufschlag
-und vor der Rückseite steht **keine** Lücke (Begründung unten).
+und vor der Rückseite steht **keine** Lücke (Begründung unten). \* = nur Sonderausgabe, siehe
+„Sportfest-Ausgabe" unten.
**Doppelseiten-Regel fürs Falt-Heft:** aufgeschlagen liegen die Seiten (2,3), (4,5), … nebeneinander
(gerade Seite links). Die erste Tabellenseite muss deshalb auf einer GERADEN Seitenzahl liegen, damit
die Tabellen nebeneinander stehen. **Das rechnet das Skript selbst** (seit 31.07.2026): es verschiebt
@@ -548,12 +553,30 @@ Lücken bekommen 7 Lücken zwei. Zwei hintereinander ist das Optimum, nicht eins
Zweier-Paar auf einer Doppelseite landen kann, schlägt man das Heft gelegentlich auf zwei Anzeigen auf.
Ein `images[]`-Eintrag hat
`"type": "image"` (wie gehabt, `base`/`widths`/`alt`) oder `"type": "live"` (`page`: `cover` |
-`vorwort` | `interview` | `aufruf` | `tabelle` | `termine` | `news` | `ergebnisse` | `gegner` | `vorschau` | `kontakt` | `mitglied` | `historie` | `sportheim` | `partner` | `momente` | `impressum`, kein Bild;
-`tabelle` zusätzlich mit `team`: Team-Key aus `matchcenter.json`, ohne `team` greift der Team-Key
-der Ausgabe). Fehlt `type` (alte Phase-1-Ausgaben) → Fallback `image`, bricht nicht. **Gerendert
+`vorwort` | `interview` | `programm` | `aufruf` | `turnier` | `tabelle` | `termine` | `news` | `ergebnisse` | `gegner` | `vorschau` | `kontakt` | `mitglied` | `historie` | `sportheim` | `partner` | `danke` | `momente` | `impressum`, kein Bild;
+`tabelle` und `gegner` zusätzlich mit `team`: Team-Key aus `matchcenter.json`, ohne `team` greift der
+Team-Key der Ausgabe; `turnier` mit `turnier`: Key aus `turniere[]` der Ausgaben-Veranstaltung;
+`danke` mit `anzeige`: das platzierte Anzeigenmotiv, siehe unten). Fehlt `type` (alte Phase-1-Ausgaben) → Fallback `image`, bricht nicht. **Gerendert
wird jede Seite (Bild wie Live) über `app/components/stadionzeitung-seite.php`** — die eine
gemeinsame Render-Stelle für Viewer und Druckfassung, nie zwei Implementierungen derselben Seite.
+**Danke-Seite + platzierte Anzeigen (03.09.2026).** Der Dank an die Partner war bis dahin unten an
+ein Anzeigen-Bild geklebt (ein Canva-Motiv, das Sponsorenanzeige und Dank in einer Datei hatte) und
+bestand aus Wappen, Titel und einer Zeile auf leerer Fläche. Jetzt ist es eine Live-Seite
+(`stadionzeitung-danke.php`): die **Logo-Wand aus `data/partners.json`** (Single Source, dieselbe
+Quelle wie die Startseite), Kopf und der QR auf `/partner-werden`. Dazu ein neues, generisches Feld
+im Anzeigenbestand: **`platzierung`** nimmt eine Anzeige aus dem Streu-Pool und gibt ihr einen festen
+Platz auf einer Live-Seite (derzeit `danke`, dort sitzt die Emons-Anzeige über der Logo-Wand).
+- **Das Motiv steht im Seiteneintrag, nicht in der Komponente**: `bin/stadionzeitung-add.php` schreibt
+ es als `anzeige` in den `images[]`-Eintrag. Dadurch ist es eingefroren wie jede andere Anzeige, und
+ eine veröffentlichte Ausgabe ändert sich nicht, wenn der Sponsor später ein neues Motiv liefert.
+- **Die Seitenzahl bleibt gleich**: eine Anzeige verlässt den Pool, eine Live-Seite kommt dazu.
+- Das Kombi-Motiv wurde an seiner roten Trennlinie zerschnitten, damit die Sponsorenanzeige
+ vollständig bleibt und der Dank nicht zweimal im Heft steht.
+- **Falle in `bin/anzeige-add.php`** (dabei gefunden und behoben): das Aufräum-Muster lautete
+ `{$slug}-*.jpg` und traf damit jeden Slug, der den neuen als Präfix hat — `emons` löschte die
+ Motive von `emons-partnerdank`. Jetzt `{$slug}-[0-9]*.jpg`.
+
**Entwurf / Veröffentlicht (`status`-Feld):** `bin/stadionzeitung-add.php` legt jede Ausgabe als
`entwurf` an — unsichtbar in Archiv und Sitemap, `noindex`, aber unter ihrer direkten URL erreichbar
(zum Gegenlesen ohne Login-System; der Ausgaben-Kopf zeigt einen „Entwurf"-Badge). Im Entwurf rechnen
@@ -635,6 +658,73 @@ Fragen/Antworten, Claude trägt sie ins `interview`-Feld der Ausgabe ein (`tease
Live-Seite nur ein, wenn das Feld existiert — Ausgaben ohne Interview haben weder Seite noch
Teaser-Zeile. Fragen in Abel fett, Antworten in `--ff-serif` (dieselbe Begründung wie beim Vorwort).
+**Sportfest-Ausgabe (03.09.2026): ein Slug-Verweis schaltet drei Seitentypen zu.** Trägt eine Ausgabe
+das chat-gepflegte Feld `veranstaltung` (Slug aus `data/veranstaltungen.json`), kommen dazu: die
+**Programm-Seite** (`stadionzeitung-programm.php`, aus `programm[]` der Veranstaltung), ein Block
+**Turnierplan-Seiten** (`stadionzeitung-turnier.php`, je Eintrag in `turniere[]` eine Seite) und eine
+**Gegnerseite je Mannschaft mit Heimspiel im Zeitraum** der Veranstaltung statt nur für die tragende.
+Dazu trägt `cover.teaser` die Coverzeile „Im Heft"; das Feld überlebt erneute Skript-Läufe wie
+`vorwort`. **`stadionzeitung_teaser()`** ist die eine Quelle dieser Zeile für Cover **und**
+Archivkarte: `cover.teaser` vor `interview.teaser` — eine Sonderausgabe ohne Interview hat sonst keine.
+**Das Cover einer Sonderausgabe zeigt das FEST, nicht die Begegnung** (Felix, 03.09.2026): Wappen
+und „Heimspiel gegen …" entfallen, an ihrer Stelle steht der Veranstaltungstitel in der
+Display-Schrift plus Zeitraum (Quelle: `veranstaltungen.json`, nie neu getippt). Grund: ein Fest
+bringt mehrere Heimspiele mit, eines davon aufs Cover zu heben wäre schlicht falsch. Ausgelöst wird
+das vom `veranstaltung`-Feld, nicht von einem eigenen Schalter.
+**Statt des Spielerfotos steht das HAUPTPLAKAT der Veranstaltung rechts** (erstes Plakat aus
+`veranstaltungen.json`, dieselbe Quelle wie die Detailseite, nie eine Kopie): leicht schräg gelegt
+(4 Grad) und an zwei gegenüberliegenden Ecken gerundet, links oben und rechts unten, mit Schatten
+statt Rahmen — wie ein aufs Cover gelegter Aushang (Felix, 03.09.2026). Es ist mit 66cqi deutlich
+kleiner als das Spielerfoto (98cqi): ein Freisteller ist schmal, ein Plakat im Hochformat läge bei
+derselben Höhe über der Typografie unten links. Die Textspalte wird dafür auf 48cqi verengt.
+Ohne Plakat greift das Spielerfoto, ohne beides trägt der Hintergrund die Seite. `box-shadow` ist
+hier unbedenklich; die Druck-Falle des Projekts betrifft `text-shadow` auf umbrechendem Text.
+
+**Sonst bleibt das Cover unverändert, und das ist eine Entscheidung:** eine rote Kennung
+„Sportfest-Ausgabe" als Kasten im Kopf und ein kräftiger roter Doppelrahmen waren gebaut und wurden
+am 03.09.2026 wieder entfernt (Felix: „sieht schrecklich aus"). Der Rahmen lief auf z2 quer über das
+freigestellte Spielerfoto, und der Kasten stach aus dem ruhigen Kopf heraus. Die Ausgabe kennzeichnet
+sich durch den großen Fest-Titel unten links ausreichend selbst. **Kein zweites Rot in den Kopf, und
+keinen farbigen Rahmen über das Spielerfoto** — der Kicker bleibt „Saison · Datum" wie überall.
+- **Die Daten liegen in `veranstaltungen.json`, nicht in der Ausgabe** — eine Quelle für Website und
+ Heft: dasselbe `programm[]` rendert `veranstaltung-programm.php` auf `/veranstaltungen/` und
+ `stadionzeitung-programm.php` auf dem A5-Blatt (zwei Darstellungen, nie zwei Datenstände).
+ `turniere[]` ist bisher **nur** Heft-Inhalt (Website-Darstellung offen), Feldform:
+ `{key, title, date, start, spieldauer, felder?, modus?, teams[], spiele[]{nr, zeit, feld?, heim, gast}}`.
+- **`stadionzeitung_gegner_fixtures()`** (`helpers.php`) ist die EINZIGE Stelle, die entscheidet,
+ welche Mannschaft eine Gegnerseite bekommt. `bin/stadionzeitung-add.php` plant die Seiten damit,
+ `stadionzeitung_build_snapshot()` baut die Daten damit — sonst entstünde eine Seite ohne Daten oder
+ umgekehrt. `gegner` ist deshalb eine **Map je Team-Key** (wie `tabellen`) statt eines einzelnen
+ Objekts; Ausgaben ohne `team` am Seiteneintrag fallen auf die tragende Mannschaft zurück. Ein
+ eingefrorener Snapshot mit der alten Ein-Objekt-Form existiert nicht (die einzige veröffentlichte
+ Ausgabe ist älter als die Gegnerseite und trägt dort `null`), deshalb gibt es dafür bewusst
+ **keinen** Kompatibilitäts-Zweig.
+- **Die Vorschau überspringt jedes Spiel mit eigener Gegnerseite**, nicht mehr nur das Heftspiel:
+ Zweite und Frauen stünden sonst zweimal im Heft. Sie zeigt dann die Spiele nach dem Fest.
+- **Programm und Turnierpläne sind live, ohne Snapshot** (wie Kontakt/Aufruf): chat-gepflegte Daten,
+ die kein Sync wegräumt. Eine spätere Korrektur am Programm ändert damit auch eine archivierte
+ Ausgabe — bewusst in Kauf genommen, dieselbe Abwägung wie bei der Kontakt-Seite.
+- **Die Turnierseite ist zum Beschreiben:** Spielplan mit LEERER Ergebnisspalte (Schreiblinien) und
+ leere Abschlusstabelle. Die Zeilenhöhe macht dort die **Schreiblinie**, nicht die Schriftgröße
+ (gemessen: eine kleinere Schrift brachte genau einen Pixel) — wer die E-Jugend-Seite mit zehn
+ Spielen aufs Blatt bringen muss, geht an `.stadionzeitung-turnier--dicht .__blank`. Die
+ Programm-Seite läuft aus demselben Grund mit straffen Abständen: zwölf Punkte plus drei Tagestitel
+ liefen mit den üblichen 3.3cqi 59px über die Blattkante. **Nach jeder Änderung Seitenhöhe messen.**
+- **Beim Messen die scrollbaren Seiten mitnehmen.** Vorwort, Termine und Sportheim tragen
+ `overflow-y: auto`: dort wächst nicht die Seite, sondern das Kind wird scrollbar — eine Messung
+ von `scrollHeight - clientHeight` auf `.stadionzeitung-page` meldet 0, und im Druck fehlt trotzdem
+ das Ende des Textes. Genau so fiel ein sechsabsätziges Vorwort erst in der Druckvorschau auf
+ (03.09.2026, seitdem vier Absätze). Verlässlich ist: Unterkante der Signatur gegen die Blattkante
+ prüfen, im Viewer **und** in `?druck=1`.
+- **Seitenzahl aufs Vielfache von 4 bringen, ohne Inhalt zu erfinden:** eine zeitgebundene
+ Eigenanzeige lässt sich per `bis` in `data/anzeigen.json` für diese Ausgabe abschalten (der Filter
+ vergleicht mit dem Ausgabedatum). So kam die Sportfest-Ausgabe von 49 auf 48 Seiten: das
+ Programmplakat als Anzeige wurde abgelöst, weil die gesetzte Programmseite es ersetzt.
+- **Ablauf ist zweistufig** (wie beim Vorwort): erst `bin/stadionzeitung-add.php` laufen lassen, dann
+ `veranstaltung`/`cover` in die Ausgabe eintragen, dann **erneut** laufen lassen. Vorher existiert
+ 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.
+
**Cover (automatisch generiert, Phase 3):** einzige fotografische Stadionzeitungs-Seite —
`app/components/stadionzeitung-cover.php`. Echtes Magazin-Cover statt Website-Baustein: **kein**
Stadionfoto mehr als Hintergrund, stattdessen ein ruhiger Verlauf in Vereinsfarben, auf dem das
diff --git a/app/components/stadionzeitung-cards.php b/app/components/stadionzeitung-cards.php
index 4f616fa..215fa17 100644
--- a/app/components/stadionzeitung-cards.php
+++ b/app/components/stadionzeitung-cards.php
@@ -36,11 +36,12 @@ $issues = $issues ?? [];
= e(german_date($issue['date'])) ?>
· = (int) $issue['pages'] ?> Seiten
-
+
= e($kartenTeaser) ?>
diff --git a/app/components/stadionzeitung-cover.php b/app/components/stadionzeitung-cover.php
index e750af6..ffd2c14 100644
--- a/app/components/stadionzeitung-cover.php
+++ b/app/components/stadionzeitung-cover.php
@@ -69,18 +69,64 @@ if ($match !== null) {
// Gedruckte Zeile neben dem QR-Code — immer die echte Domain (club_domain()).
$linkText = club_domain();
-$kicker = trim($season . ($date !== '' ? ' · ' . german_date($date) : ''));
+// Aufmacher unten links: normal die Begegnung (Wappen + „Heimspiel gegen …").
+// Zeigt die Ausgabe auf eine Veranstaltung (Sportfest-Ausgabe), tritt an deren
+// Stelle das FEST als Titelgeschichte — denn ein Fest bringt mehrere Heimspiele
+// mit (Erste, Zweite, Frauen), und ein einzelnes davon aufs Cover zu heben wäre
+// falsch (Felix, 03.09.2026). Titel und Zeitraum kommen aus
+// data/veranstaltungen.json, nie neu getippt.
+$feature = null;
+$featureV = veranstaltung_find((string) ($issue['veranstaltung'] ?? ''));
+if ($featureV !== null) {
+ $feature = [
+ 'title' => trim((string) ($featureV['title'] ?? '')),
+ 'range' => german_date_range((string) ($featureV['date_start'] ?? ''), $featureV['date_end'] ?? null),
+ ];
+}
+
+// Bildmotiv rechts: normal das freigestellte Spielerfoto. Eine Sonderausgabe mit
+// Veranstaltung zeigt stattdessen deren HAUPTPLAKAT (erstes Plakat, dieselbe
+// Quelle wie /veranstaltungen/, nie eine Kopie) — leicht schräg gelegt mit
+// zwei abgerundeten Ecken, wie ein aufs Cover gelegter Aushang (Felix,
+// 03.09.2026). Ohne Plakat bleibt es beim Spielerfoto, ohne beides trägt der
+// Hintergrund die Seite allein.
+$poster = null;
+if ($featureV !== null) {
+ $ersteseite = $featureV['plakate'][0] ?? null;
+ if (is_array($ersteseite) && !empty($ersteseite['base'])) {
+ $poster = $ersteseite;
+ }
+}
+
+// Kicker: Saison · Datum, auf jedem Cover gleich. Eine Sonderausgabe bekommt
+// bewusst KEINE zusätzliche Kennung im Kopf: ein roter Kasten „Sportfest-Ausgabe"
+// war gebaut und am 03.09.2026 wieder entfernt (Felix) — er stach aus dem
+// ruhigen Kopf heraus, und die Ausgabe kennzeichnet sich unten links ohnehin
+// selbst durch den großen Fest-Titel.
+$kicker = implode(' · ', array_filter([
+ $season,
+ $date !== '' ? german_date($date) : '',
+], static fn (string $s): bool => $s !== ''));
// Zweite Coverzeile (Magazin-Praxis: Teaser auf die Geschichte im Heft) —
-// erscheint automatisch, sobald die Ausgabe ein Interview trägt
-// (interview-Feld, chat-gepflegt wie das Vorwort).
-$teaser = trim((string) ($issue['interview']['teaser'] ?? ''));
+// aus stadionzeitung_teaser(): `cover.teaser` einer Sonderausgabe, sonst die
+// Coverzeile des Interviews. Dieselbe Funktion speist die Archivkarte.
+$teaser = stadionzeitung_teaser($issue);
?>
-
+
$heroImage, 'sizes' => '480px', 'class' => 'stadionzeitung-cover__bg']); ?>
-
+
+
+ ['base' => $poster['base'], 'widths' => $poster['widths'] ?? [], 'alt' => ''],
+ 'class' => 'stadionzeitung-cover__poster',
+ 'sizes' => '(max-width: 600px) 55vw, 260px',
+ ]); ?>
+
Rund ums Heimspiel im Katzbachtal
+
+
+
+
= e($feature['title']) ?>
+
+
= e($feature['range']) ?>
+
+
+
@@ -124,6 +181,7 @@ $teaser = trim((string) ($issue['interview']['teaser'] ?? ''));
= e(trim($teamName . ($league !== '' ? ' · ' . $league : ''))) ?>
+
Im Heft
diff --git a/app/components/stadionzeitung-danke.php b/app/components/stadionzeitung-danke.php
new file mode 100644
index 0000000..741591c
--- /dev/null
+++ b/app/components/stadionzeitung-danke.php
@@ -0,0 +1,65 @@
+
+
+
+ $anzeige, 'sizes' => '420px', 'class' => 'stadionzeitung-danke__anzeige']); ?>
+
+
+ Unsere Partner
+
+ Danke für die Unterstützung
+
+
+
+
+
+
+ loading="lazy" decoding="async">
+
+
+
+
+
+
+
Mitmachen
+
Werden Sie unser Partner
+
= e(club_domain('partner-werden')) ?>
+
+
+
+
+
Scan mich
+
+
+
+
diff --git a/app/components/stadionzeitung-gegner.php b/app/components/stadionzeitung-gegner.php
index 9a206d3..a211bb5 100644
--- a/app/components/stadionzeitung-gegner.php
+++ b/app/components/stadionzeitung-gegner.php
@@ -7,9 +7,14 @@ declare(strict_types=1);
* aufgebauten Stadionzeitungs-Snapshot; die Komponente rendert nur. Dadurch
* sind Entwurf und veröffentlichte Druckausgabe konsistent und archivfest.
*
- * Props: $info stadionzeitung_live_data($issue)['gegner']
+ * Props: $info stadionzeitung_live_data($issue)['gegner'][team] — ein Eintrag der
+ * Map je Team-Key (team_name/opponent/league/season/fixture/own/
+ * opponent_stats). Normal nur die tragende Mannschaft; in der
+ * Sportfest-Ausgabe eine Seite je Heimspiel am Fest, deshalb
+ * nennt der Kicker die Mannschaft.
*/
$info = is_array($info ?? null) ? $info : [];
+$teamName = trim((string) ($info['team_name'] ?? ''));
$fixture = is_array($info['fixture'] ?? null) ? $info['fixture'] : [];
$own = is_array($info['own'] ?? null) ? $info['own'] : null;
$opponent = is_array($info['opponent_stats'] ?? null) ? $info['opponent_stats'] : null;
@@ -39,7 +44,7 @@ $format = static function (?array $row, string $key, bool $signed = false): stri
?>
- Unser Gegner
+ = e($teamName !== '' ? 'Unser Gegner · ' . $teamName : 'Unser Gegner') ?>
= e($opponentName) ?>
= e(trim(implode(' · ', array_filter([(string) ($info['league'] ?? ''), (string) ($info['season'] ?? '')])))) ?>
@@ -70,9 +75,12 @@ $format = static function (?array $row, string $key, bool $signed = false): stri
- TSV 08
+
+ Wir
Vergleich
- ASV
+ Gegner
diff --git a/app/components/stadionzeitung-programm.php b/app/components/stadionzeitung-programm.php
new file mode 100644
index 0000000..95367be
--- /dev/null
+++ b/app/components/stadionzeitung-programm.php
@@ -0,0 +1,71 @@
+
+
+
+ = e((string) ($v['title'] ?? 'Veranstaltung')) ?>
+ Programm
+
+ = e($sub) ?>
+
+
+
+
+
+
+
+ = e($tagText) ?>
+
+
+
+
+
+
+ = e($zeit) ?>
+
+
+
+
+ = e((string) ($punkt['titel'] ?? '')) ?>
+
+ = e((string) $punkt['text']) ?>
+
+
+
+
+
+
+
+
diff --git a/app/components/stadionzeitung-seite.php b/app/components/stadionzeitung-seite.php
index 6638955..e3fa035 100644
--- a/app/components/stadionzeitung-seite.php
+++ b/app/components/stadionzeitung-seite.php
@@ -9,9 +9,12 @@ declare(strict_types=1);
* anderer Anordnung (Lesereihenfolge vs. Broschüren-Reihenfolge). Props:
* $entry Eintrag aus issue.images[]:
* type 'image' (base/widths/alt),
- * type 'live' (page: cover|vorwort|interview|aufruf|tabelle|termine|
- * news|ergebnisse|gegner|vorschau|kontakt; tabelle optional
- * mit team: Team-Key aus matchcenter.json),
+ * type 'live' (page: cover|vorwort|interview|programm|aufruf|turnier|
+ * tabelle|termine|news|ergebnisse|gegner|vorschau|kontakt|
+ * mitglied|historie|sportheim|partner|danke|momente|impressum;
+ * tabelle und gegner optional mit team: Team-Key aus
+ * matchcenter.json; turnier mit turnier: Key aus
+ * veranstaltungen.json → turniere[] der Ausgaben-Veranstaltung),
* type 'leer' (weiße Füllseite, nur Druckfassung — hält die
* Seitenzahl auf einem Vielfachen von 4)
* $issue Eintrag aus data/stadionzeitung.json
@@ -56,7 +59,19 @@ $type = $entry['type'] ?? 'image';
$data['ergebnisse'] ?? []]); ?>
- $data['gegner'] ?? null]); ?>
+ $data['gegner'][$gegnerTeam] ?? null]);
+?>
+
+
+ $issue]); ?>
+
+
+ $issue, 'turnier' => (string) ($entry['turnier'] ?? '')]); ?>
$data['vorschau'] ?? []]); ?>
@@ -76,6 +91,11 @@ $type = $entry['type'] ?? 'image';
+
+
+ $entry['anzeige'] ?? null]); ?>
+
$data['momente'] ?? []]); ?>
diff --git a/app/components/stadionzeitung-turnier.php b/app/components/stadionzeitung-turnier.php
new file mode 100644
index 0000000..91e44bc
--- /dev/null
+++ b/app/components/stadionzeitung-turnier.php
@@ -0,0 +1,158 @@
+ $clubName !== ''
+ && str_starts_with(mb_strtolower(trim($team)), mb_strtolower($clubName));
+
+$date = (string) ($t['date'] ?? '');
+$weekday = $date !== '' ? german_weekday($date) : '';
+$kicker = trim(implode(' · ', array_filter([
+ (string) ($v['title'] ?? ''),
+ trim(($weekday !== '' ? $weekday . ', ' : '') . ($date !== '' ? german_date($date) : '')),
+])));
+
+$teams = array_values(array_filter(
+ array_map(static fn ($x): string => trim((string) $x), $t['teams'] ?? []),
+ static fn (string $x): bool => $x !== ''
+));
+$spiele = array_values(array_filter($t['spiele'] ?? [], 'is_array'));
+$felder = (int) ($t['felder'] ?? 0);
+$hatFeld = $felder > 1;
+
+$meta = [];
+if (!empty($t['start'])) {
+ $meta[] = 'Beginn ' . (string) $t['start'] . ' Uhr';
+}
+if (!empty($t['spieldauer'])) {
+ $meta[] = 'Spieldauer ' . (int) $t['spieldauer'] . ' Min.';
+}
+if ($hatFeld) {
+ $meta[] = $felder . ' Felder';
+}
+if (!empty($t['modus'])) {
+ $meta[] = (string) $t['modus'];
+}
+$sub = trim(implode(' · ', $meta));
+$title = trim((string) ($t['title'] ?? $turnierKey));
+// Ab neun Spielen (E-Jugend: zehn) rücken die Zeilen enger, sonst läuft der
+// Spielplan mit der Abschlusstabelle über die Blattkante.
+$dicht = count($spiele) > 8;
+?>
+
+
+
+ = e($kicker) ?>
+
+ = e('Turnier ' . $title) ?>
+
+ = e($sub) ?>
+
+
+
+
+
Teilnehmer
+
+ $team): ?>
+= $i > 0 ? ' · ' : '' ?>= e($team) ?>
+
+
+
+
+
+
+ Spielplan
+
+
+ Nr.
+ Zeit
+
+ Feld
+
+ Begegnung
+ Ergebnis
+
+
+
+ $spiel): ?>
+
+
+ = e((string) ($spiel['nr'] ?? ($i + 1))) ?>
+ = e($zeit) ?>
+
+ = e((string) ($spiel['feld'] ?? '')) ?>
+
+ = e($heim) ?> gegen = e($gast) ?>
+ :
+
+
+
+
+
+
+
+ Platzierung zum Eintragen
+
+
+ Pl.
+ Teilnehmer
+ T
+ TD
+ Pkt
+
+
+
+
+
+ = $i ?>.
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/stadionzeitung-viewer.php b/app/components/stadionzeitung-viewer.php
index 3f92d7e..df4a7b1 100644
--- a/app/components/stadionzeitung-viewer.php
+++ b/app/components/stadionzeitung-viewer.php
@@ -5,7 +5,7 @@ declare(strict_types=1);
/**
* Swipe-Viewer für eine Stadionzeitung-Ausgabe. Props:
* $issue Eintrag aus data/stadionzeitung.json (opponent, team_key, status,
- * images[]{type: 'image'{base,widths,alt} | 'live'{page[,team]}})
+ * images[]{type: 'image'{base,widths,alt} | 'live'{page[,team][,turnier]}})
*
* Ohne JS: alle Seiten normal gestapelt (stadionzeitung-seite-Komponente) —
* voll lesbar, sequentiell auch per Screenreader. Mit JS
diff --git a/app/helpers.php b/app/helpers.php
index 169ed88..d404e07 100644
--- a/app/helpers.php
+++ b/app/helpers.php
@@ -568,6 +568,27 @@ function veranstaltungen_load(): array
return $items;
}
+/**
+ * Eine Veranstaltung per Slug, UNGEFILTERT (auch Entwürfe) — für die
+ * Detailseite (app/pages/veranstaltung.php) und die Stadionzeitungs-Live-Seiten
+ * der Sportfest-Ausgabe (Programm, Turnierpläne, Gegnerseiten je Heimspiel).
+ * Ungefiltert aus demselben Grund wie die Detailseite: ein Entwurf muss sich
+ * gegenlesen lassen, und eine Entwurfs-Ausgabe der Stadionzeitung darf auf
+ * einen Entwurfs-Termin zeigen. null, wenn es den Slug nicht gibt.
+ */
+function veranstaltung_find(string $slug): ?array
+{
+ if ($slug === '') {
+ return null;
+ }
+ foreach (json_load('veranstaltungen')['veranstaltungen'] ?? [] as $entry) {
+ if (($entry['slug'] ?? '') === $slug) {
+ return $entry;
+ }
+ }
+ return null;
+}
+
/**
* Steht ein Termin zum Bezugsdatum noch an? Maßgeblich ist das ENDE, nicht der
* Beginn: ein mehrtägiges Fest (Sportfest, Freitag bis Sonntag) gehört am
@@ -955,6 +976,86 @@ function stadionzeitung_is_published(array $issue): bool
return ($issue['status'] ?? 'veroeffentlicht') !== 'entwurf';
}
+/**
+ * Coverzeile „Im Heft" einer Ausgabe — die EINE Quelle für das Cover
+ * (stadionzeitung-cover.php) und die Archivkarte (stadionzeitung-cards.php),
+ * damit beide nie Verschiedenes sagen. Eine Sonderausgabe (Sportfest) setzt
+ * `cover.teaser` chat-gepflegt; sonst trägt die Coverzeile des Interviews
+ * (`interview.teaser`). Leer, wenn es weder das eine noch das andere gibt.
+ */
+function stadionzeitung_teaser(array $issue): string
+{
+ $teaser = trim((string) ($issue['cover']['teaser'] ?? ''));
+ if ($teaser !== '') {
+ return $teaser;
+ }
+ return trim((string) ($issue['interview']['teaser'] ?? ''));
+}
+
+/**
+ * Welche Mannschaften bekommen in einer Ausgabe eine Gegnerseite — die EINE
+ * Logik dafür, benutzt von bin/stadionzeitung-add.php (Seitenplanung) UND
+ * stadionzeitung_build_snapshot() (Daten), damit nie eine Seite ohne Daten
+ * oder Daten ohne Seite entstehen.
+ *
+ * Immer: die tragende Mannschaft (Gegner = `opponent`, Tag = `date`).
+ * Zusätzlich, wenn die Ausgabe eine `veranstaltung` (Slug) mit Zeitraum trägt
+ * (Sportfest-Ausgabe): jede weitere Mannschaft aus matchcenter.json mit einem
+ * HEIMspiel in diesem Zeitraum — drei Heimspiele am Fest, drei Gegnerseiten.
+ * Der Gegner ist dann der andere Verein der Begegnung. Reihenfolge wie in
+ * matchcenter.json (= config 'matchcenter.teams').
+ *
+ * Rückgabe: [team_key => ['opponent' => string, 'date' => 'YYYY-MM-DD']]
+ */
+function stadionzeitung_gegner_fixtures(array $issue): array
+{
+ $date = (string) ($issue['date'] ?? '');
+ $issueTeam = (string) ($issue['team_key'] ?? '');
+ $issueOpponent = trim((string) ($issue['opponent'] ?? ''));
+
+ $von = '';
+ $bis = '';
+ $veranstaltung = veranstaltung_find((string) ($issue['veranstaltung'] ?? ''));
+ if ($veranstaltung !== null) {
+ $von = (string) ($veranstaltung['date_start'] ?? '');
+ $bis = (string) ($veranstaltung['date_end'] ?? $von);
+ }
+
+ $fixtures = [];
+ foreach (json_load('matchcenter')['teams'] ?? [] as $team) {
+ $key = (string) ($team['key'] ?? '');
+ if ($key === '') {
+ continue;
+ }
+ if ($key === $issueTeam) {
+ if ($issueOpponent !== '') {
+ $fixtures[$key] = ['opponent' => $issueOpponent, 'date' => $date];
+ }
+ continue;
+ }
+ if ($von === '') {
+ continue;
+ }
+ foreach ($team['next_matches'] ?? [] as $match) {
+ if (empty($match['is_home'])) {
+ continue;
+ }
+ $matchDate = substr((string) ($match['kickoff'] ?? ''), 0, 10);
+ if ($matchDate < $von || $matchDate > $bis) {
+ continue;
+ }
+ $opponent = trim((string) ($match['away'] ?? ''));
+ if ($opponent === '' || $opponent === 'SPIELFREI') {
+ continue;
+ }
+ $fixtures[$key] = ['opponent' => $opponent, 'date' => $matchDate];
+ break;
+ }
+ }
+
+ return $fixtures;
+}
+
/**
* Die neueste VERÖFFENTLICHTE Ausgabe (oder null, wenn es keine gibt) — für die
* Kurz-URL /stadionzeitung/aktuell, auf die gedruckte QR-Codes zeigen. Sortiert
@@ -988,12 +1089,15 @@ function stadionzeitung_build_snapshot(array $issue): array
{
$date = (string) ($issue['date'] ?? '');
$issueTeam = (string) ($issue['team_key'] ?? '');
- $issueOpponent = trim((string) ($issue['opponent'] ?? ''));
$tabellen = [];
$ergebnisse = [];
$vorschau = [];
- $gegner = null;
+ // Gegnerseiten als Map je Team-Key (wie `tabellen`): welche Mannschaften
+ // eine bekommen, entscheidet stadionzeitung_gegner_fixtures() — dieselbe
+ // Funktion, mit der bin/stadionzeitung-add.php die Seiten einplant.
+ $gegnerFixtures = stadionzeitung_gegner_fixtures($issue);
+ $gegner = [];
foreach (json_load('matchcenter')['teams'] ?? [] as $team) {
$key = (string) ($team['key'] ?? '');
if ($key === '') {
@@ -1006,11 +1110,14 @@ function stadionzeitung_build_snapshot(array $issue): array
'table' => $team['table'] ?? [],
];
- // Gegnerseite der tragenden Mannschaft: ausschließlich aus demselben
- // Matchcenter-Datensatz wie die Tabelle ableiten. So gibt es keinen
- // zweiten BFV-Abruf und der Stand wird beim Veröffentlichen zusammen
- // mit den übrigen Live-Daten eingefroren.
- if ($key === $issueTeam && $issueOpponent !== '') {
+ // Gegnerseite(n): ausschließlich aus demselben Matchcenter-Datensatz
+ // wie die Tabelle ableiten. So gibt es keinen zweiten BFV-Abruf und der
+ // Stand wird beim Veröffentlichen zusammen mit den übrigen Live-Daten
+ // eingefroren. Normal nur die tragende Mannschaft; in der Sportfest-
+ // Ausgabe jede Mannschaft mit Heimspiel am Fest (s. gegner_fixtures).
+ if (isset($gegnerFixtures[$key])) {
+ $gegnerName = $gegnerFixtures[$key]['opponent'];
+ $gegnerDate = $gegnerFixtures[$key]['date'];
$table = is_array($team['table'] ?? null) ? $team['table'] : [];
$ownRow = null;
$opponentRow = null;
@@ -1018,7 +1125,7 @@ function stadionzeitung_build_snapshot(array $issue): array
if (!empty($row['is_own_club'])) {
$ownRow = $row;
}
- if (strcasecmp(trim((string) ($row['club'] ?? '')), $issueOpponent) === 0) {
+ if (strcasecmp(trim((string) ($row['club'] ?? '')), $gegnerName) === 0) {
$opponentRow = $row;
}
}
@@ -1027,15 +1134,15 @@ function stadionzeitung_build_snapshot(array $issue): array
foreach ($team['next_matches'] ?? [] as $match) {
$matchDate = substr((string) ($match['kickoff'] ?? ''), 0, 10);
$clubs = [(string) ($match['home'] ?? ''), (string) ($match['away'] ?? '')];
- if ($matchDate === $date && in_array($issueOpponent, $clubs, true)) {
+ if ($matchDate === $gegnerDate && in_array($gegnerName, $clubs, true)) {
$fixture = $match;
break;
}
}
- $gegner = [
+ $gegner[$key] = [
'team_name' => (string) ($team['name'] ?? ''),
- 'opponent' => $issueOpponent,
+ 'opponent' => $gegnerName,
'league' => (string) ($team['league'] ?? ''),
'season' => (string) ($team['season'] ?? ''),
'fixture' => $fixture,
@@ -1064,7 +1171,11 @@ function stadionzeitung_build_snapshot(array $issue): array
// Vorschau: das nächste Spiel dieser Mannschaft NACH dem Spieltag der
// Ausgabe. Das Heft-Spiel selbst fliegt raus (das Cover zeigt es
- // schon); ein Spiel einer ANDEREN Mannschaft am selben Tag bleibt drin.
+ // schon), und ebenso jedes Spiel, das eine eigene Gegnerseite hat
+ // (Sportfest-Ausgabe: Zweite und Frauen stünden sonst doppelt im Heft).
+ // Ein Spiel einer ANDEREN Mannschaft am selben Tag ohne Gegnerseite
+ // bleibt drin.
+ $gegnerDate = $gegnerFixtures[$key]['date'] ?? null;
foreach ($team['next_matches'] ?? [] as $match) {
// Spielfreie Runden führt der BFV als Pseudo-Begegnung gegen
// "SPIELFREI" — kein echtes Spiel, im Heft überspringen.
@@ -1078,6 +1189,9 @@ function stadionzeitung_build_snapshot(array $issue): array
if ($matchDate === $date && $key === $issueTeam) {
continue;
}
+ if ($gegnerDate !== null && $matchDate === $gegnerDate) {
+ continue;
+ }
$vorschau[] = [
'team_key' => $key,
'team_name' => (string) ($team['name'] ?? ''),
diff --git a/app/pages/veranstaltung.php b/app/pages/veranstaltung.php
index 94e44e8..69d6bf7 100644
--- a/app/pages/veranstaltung.php
+++ b/app/pages/veranstaltung.php
@@ -9,9 +9,9 @@ declare(strict_types=1);
* data/veranstaltungen.json — Texte chat-gepflegt, `plakat` und `galerie`
* schreibt bin/veranstaltung-bilder.php.
*
- * Geladen wird hier bewusst UNGEFILTERT (nicht über veranstaltungen_load()):
- * ein Entwurf muss unter seiner direkten URL lesbar bleiben, damit er
- * gegengelesen werden kann. Sichtbar wird er dadurch nirgends sonst, und er
+ * Geladen wird hier bewusst UNGEFILTERT (veranstaltung_find(), nicht
+ * veranstaltungen_load()): ein Entwurf muss unter seiner direkten URL lesbar
+ * bleiben, damit er gegengelesen werden kann. Sichtbar wird er dadurch nirgends sonst, und er
* trägt noindex.
*
* Bewusst OHNE das übliche hero-Component (wie die Stadionzeitungs-Ausgabe):
@@ -23,13 +23,7 @@ declare(strict_types=1);
$urlSlug = trim((string) parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH), '/');
$veranstaltungSlug = substr($urlSlug, strlen('veranstaltungen/'));
-$v = null;
-foreach (json_load('veranstaltungen')['veranstaltungen'] ?? [] as $entry) {
- if (($entry['slug'] ?? '') === $veranstaltungSlug) {
- $v = $entry;
- break;
- }
-}
+$v = veranstaltung_find($veranstaltungSlug);
if ($v === null) {
// http_response_code(404) ist globaler Engine-State (kein Scope-Problem wie
diff --git a/bin/anzeige-add.php b/bin/anzeige-add.php
index 28a15a4..c8cf7ae 100644
--- a/bin/anzeige-add.php
+++ b/bin/anzeige-add.php
@@ -123,7 +123,11 @@ foreach ($sources as $src) {
// Alte Varianten dieses Slugs weg, sonst bleiben bei einem kleineren Motiv
// Breiten von vorher liegen und die JSON verspricht Dateien, die zum Bild
// nicht mehr passen.
- foreach (glob("{$destDir}/{$slug}-*.jpg") ?: [] as $alt) {
+ // Muster bewusst auf die BREITEN-Endung eingeschränkt (`-
.jpg`): mit
+ // `{$slug}-*.jpg` löscht ein Slug die Motive jedes Slugs, der ihn als Präfix
+ // hat — `emons` räumte so die Dateien von `emons-partnerdank` mit weg
+ // (03.09.2026 beobachtet, dort zufällig gewollt, generell Datenverlust).
+ foreach (glob("{$destDir}/{$slug}-[0-9]*.jpg") ?: [] as $alt) {
unlink($alt);
}
diff --git a/bin/preflight.php b/bin/preflight.php
index 48c2435..b12761e 100644
--- a/bin/preflight.php
+++ b/bin/preflight.php
@@ -326,6 +326,41 @@ if (is_file($szFile)) {
}
}
+// --- Stadionzeitung ↔ Veranstaltung (Sportfest-Ausgabe) ---
+// Eine Ausgabe mit `veranstaltung`-Feld zieht Programm, Turnierpläne und die
+// Gegnerseiten je Heimspiel aus data/veranstaltungen.json. Ein Tippfehler im Slug
+// oder im Turnier-Key rendert dort eine LEERE A5-Seite — ohne Fehlermeldung, weil
+// die Komponenten bewusst still zurückkehren. Deshalb hier laut.
+if (is_file($szFile)) {
+ $szData = $szData ?? (json_decode((string) file_get_contents($szFile), true) ?: []);
+ $vBySlug = array_column($veranstaltungRows, null, 'slug');
+ foreach ($szData['issues'] ?? [] as $ausgabe) {
+ $vSlug = (string) ($ausgabe['veranstaltung'] ?? '');
+ if ($vSlug === '') {
+ continue;
+ }
+ if (!isset($vBySlug[$vSlug])) {
+ $errors[] = "Stadionzeitung '{$ausgabe['slug']}': veranstaltung '{$vSlug}' fehlt in data/veranstaltungen.json.";
+ continue;
+ }
+ $turniere = $vBySlug[$vSlug]['turniere'] ?? [];
+ foreach ($turniere as $i => $turnier) {
+ foreach (['key', 'title', 'date', 'teams', 'spiele'] as $feld) {
+ if (empty($turnier[$feld])) {
+ $warnings[] = "data/veranstaltungen.json, '{$vSlug}' → turniere[{$i}]: Feld '{$feld}' fehlt oder ist leer.";
+ }
+ }
+ }
+ $keys = array_column($turniere, 'key');
+ foreach ($ausgabe['images'] ?? [] as $seite) {
+ if (($seite['type'] ?? '') === 'live' && ($seite['page'] ?? '') === 'turnier'
+ && !in_array((string) ($seite['turnier'] ?? ''), $keys, true)) {
+ $errors[] = "Stadionzeitung '{$ausgabe['slug']}': Turnierseite '" . ($seite['turnier'] ?? '') . "' hat keinen Eintrag in turniere[] von '{$vSlug}'.";
+ }
+ }
+ }
+}
+
// --- Inline-Styles (die CSP verwirft sie stillschweigend) ---
// Diese Prüfung existiert wegen eines echten Ausfalls am 31.07.2026: der
// Banner-Slider setzte sein Hintergrundbild per style="--banner-bg: url(…)". Auf
diff --git a/bin/stadionzeitung-add.php b/bin/stadionzeitung-add.php
index f966d30..c6a24f3 100644
--- a/bin/stadionzeitung-add.php
+++ b/bin/stadionzeitung-add.php
@@ -58,6 +58,16 @@ declare(strict_types=1);
* Interview → Aufruf. Für die Aufruf-Seite erzeugt das Skript zusätzlich den
* QR (qr-aufruf.png im Ausgaben-Ordner) aus `aufruf.link`.
*
+ * Sportfest-Ausgabe (03.09.2026): trägt die Ausgabe ein `veranstaltung`-Feld
+ * (Slug aus data/veranstaltungen.json, chat-gepflegt), kommen dazu: die
+ * Programm-Seite im Aufschlag (aus programm[] der Veranstaltung, dieselbe Liste
+ * wie die Website), ein Block Turnierplan-Seiten (je Eintrag in turniere[] eine
+ * Seite) direkt danach, und eine Gegnerseite je Mannschaft mit HEIMspiel im
+ * Zeitraum der Veranstaltung statt nur für die tragende Mannschaft — welche das
+ * sind, sagt stadionzeitung_gegner_fixtures() (helpers.php), dieselbe Funktion,
+ * aus der der Snapshot die Daten baut. `cover` ({edition, teaser}) kennzeichnet
+ * das Cover als Sonderausgabe. Beide Felder überleben erneute Läufe wie `vorwort`.
+ *
* Erneuter Aufruf mit demselben Slug ersetzt die Ausgabe vollständig (alte
* Seiten-Bilder werden vorher gelöscht) — idempotent für Korrekturen.
*/
@@ -188,6 +198,19 @@ $oldVorwort = $oldIssue['vorwort'] ?? null;
$oldInterview = $oldIssue['interview'] ?? null;
// Aufruf-Seite (z. B. Sportfest-Helferaufruf) ebenso chat-gepflegt.
$oldAufruf = $oldIssue['aufruf'] ?? null;
+// Sportfest-Ausgabe: Slug-Verweis auf data/veranstaltungen.json (Programm,
+// Turnierpläne, Gegnerseiten je Heimspiel) und Cover-Kennung — chat-gepflegt.
+$oldVeranstaltung = $oldIssue['veranstaltung'] ?? null;
+$oldCover = $oldIssue['cover'] ?? null;
+$veranstaltung = null;
+if (is_string($oldVeranstaltung) && $oldVeranstaltung !== '') {
+ $veranstaltung = veranstaltung_find($oldVeranstaltung);
+ if ($veranstaltung === null) {
+ $log("veranstaltung '{$oldVeranstaltung}' steht nicht in data/veranstaltungen.json — Programm-, Turnier- und Zusatz-Gegnerseiten entfallen.", 'WARN');
+ } else {
+ $log("Sportfest-Ausgabe: Veranstaltung '{$oldVeranstaltung}' (" . count($veranstaltung['programm'] ?? []) . ' Programmtage, ' . count($veranstaltung['turniere'] ?? []) . ' Turniere).');
+ }
+}
// --- Ziel-Ordner vorbereiten: bei erneutem Lauf alte Seiten dieser Ausgabe wegräumen ---
$destDir = PUBLIC_PATH . "/assets/img/stadionzeitung/{$slug}";
@@ -215,6 +238,28 @@ $widths = [480, 960, 1440];
// erscheint oft. Gepflegt wird der Bestand über bin/anzeige-add.php (Bilder) und
// im Chat (Sponsorenname, Reihenfolge, aktiv). Hier wird nichts gerendert, die
// Bilder liegen schon. ---
+// Anzeigen mit `platzierung` haben einen FESTEN Platz auf einer Live-Seite
+// (derzeit `danke`: die Emons-Anzeige über der Partner-Logo-Wand) und laufen
+// deshalb nicht im Streu-Pool mit — sonst stünden sie zweimal im Heft.
+$platzierteAnzeigen = [];
+foreach (json_load('anzeigen')['anzeigen'] ?? [] as $a) {
+ $platz = trim((string) ($a['platzierung'] ?? ''));
+ if ($platz === '' || ($a['aktiv'] ?? true) === false || empty($a['base'])) {
+ continue;
+ }
+ if (!empty($a['bis']) && (string) $a['bis'] < $date) {
+ continue;
+ }
+ if (isset($platzierteAnzeigen[$platz])) {
+ continue;
+ }
+ $platzierteAnzeigen[$platz] = [
+ 'base' => (string) $a['base'],
+ 'widths' => $a['widths'] ?? [],
+ 'alt' => (string) ($a['alt'] ?? ('Anzeige: ' . trim((string) ($a['sponsor'] ?? '')))),
+ ];
+}
+
$anzeigenSeiten = array_values(array_map(
static function (array $a): array {
$sponsor = trim((string) ($a['sponsor'] ?? ''));
@@ -237,6 +282,7 @@ $anzeigenSeiten = array_values(array_map(
// nächsten Handgriff im Heft zu bleiben.
static fn (array $a): bool => ($a['aktiv'] ?? true)
&& !empty($a['base'])
+ && empty($a['platzierung'])
&& (empty($a['bis']) || (string) $a['bis'] >= $date)
)
));
@@ -245,12 +291,8 @@ $anzeigenSeiten = array_values(array_map(
// Ausgaben-Bilder (Vorwort-Fallback, Rückseite) schon. Beide bekommen SPRECHENDE
// Dateinamen statt der früheren Positionsnummern (page-07) — die verschoben sich
// bei jeder Umverteilung. ---
-$live = static function (string $page, ?string $team = null): array {
- $entry = ['type' => 'live', 'page' => $page];
- if ($team !== null) {
- $entry['team'] = $team;
- }
- return $entry;
+$live = static function (string $page, array $extra = []): array {
+ return ['type' => 'live', 'page' => $page] + $extra;
};
$bild = static function (string $src, string $name, string $alt) use ($destDir, $widths, $slug): array {
@@ -293,6 +335,11 @@ try {
} elseif ($vorwort !== null) {
$aufschlag[] = $bild($vorwort, 'vorwort', 'Vorwort der Stadionzeitung');
}
+ // Programm-Seite der Sportfest-Ausgabe direkt hinter dem Vorwort: aufgeschlagen
+ // liegen Vorwort (links) und Programm (rechts) als Doppelseite 2/3 nebeneinander.
+ if ($veranstaltung !== null && !empty($veranstaltung['programm'])) {
+ $aufschlag[] = $live('programm');
+ }
// Interview-Seite nur, wenn die Ausgabe ein Interview trägt (chat-gepflegtes
// Feld) — das Cover zeigt dann automatisch die Teaser-Zeile dazu.
if ($oldInterview !== null) {
@@ -308,8 +355,40 @@ try {
$bloecke[] = ['name' => 'aufschlag', 'seiten' => $aufschlag, 'anzeigen_danach' => true];
}
+ // Turnierplan-Seiten der Sportfest-Ausgabe: je Turnier eine Seite, EIN Block
+ // (zusammenhängender Sportfest-Teil, keine Anzeige zwischen zwei Turnieren),
+ // gleich nach dem Aufschlag und vor dem Fußball-Teil.
+ $turniere = [];
+ foreach ($veranstaltung['turniere'] ?? [] as $turnier) {
+ if (is_array($turnier) && !empty($turnier['key'])) {
+ $turniere[] = $live('turnier', ['turnier' => (string) $turnier['key']]);
+ }
+ }
+ if ($turniere !== []) {
+ $bloecke[] = ['name' => 'turniere', 'seiten' => $turniere, 'anzeigen_danach' => true];
+ }
+
$bloecke[] = ['name' => 'ergebnisse', 'seiten' => [$live('ergebnisse')], 'anzeigen_danach' => true];
- $bloecke[] = ['name' => 'gegner', 'seiten' => [$live('gegner')], 'anzeigen_danach' => true];
+
+ // Gegnerseite(n): normal nur die tragende Mannschaft; in der Sportfest-Ausgabe
+ // je Mannschaft mit Heimspiel am Fest eine (drei Heimspiele, drei Seiten), als
+ // EIN Block. Welche das sind, entscheidet stadionzeitung_gegner_fixtures() —
+ // dieselbe Funktion, aus der stadionzeitung_build_snapshot() die Daten baut,
+ // damit nie eine Seite ohne Daten oder Daten ohne Seite entstehen.
+ $gegnerSeiten = [];
+ foreach (stadionzeitung_gegner_fixtures([
+ 'date' => $date, 'team_key' => $teamKey, 'opponent' => $opponent,
+ 'veranstaltung' => $oldVeranstaltung,
+ ]) as $gegnerTeam => $fixture) {
+ $gegnerSeiten[] = $live('gegner', ['team' => (string) $gegnerTeam]);
+ $log("Gegnerseite {$gegnerTeam}: {$fixture['opponent']} ({$fixture['date']}).");
+ }
+ if ($gegnerSeiten === []) {
+ // Ohne Treffer (Gegner leer) bleibt die Seite der tragenden Mannschaft —
+ // sie meldet dann selbst, dass keine Vergleichsdaten vorliegen.
+ $gegnerSeiten[] = $live('gegner', ['team' => $teamKey]);
+ }
+ $bloecke[] = ['name' => 'gegner', 'seiten' => $gegnerSeiten, 'anzeigen_danach' => true];
// Eine Tabellen-Seite je Mannschaft, Reihenfolge wie in matchcenter.json
// (= config 'matchcenter.teams'). Kommt eine Mannschaft dazu (z. B. die
@@ -317,15 +396,28 @@ try {
$tabellen = [];
foreach ($mc['teams'] ?? [] as $team) {
if (!empty($team['key'])) {
- $tabellen[] = $live('tabelle', (string) $team['key']);
+ $tabellen[] = $live('tabelle', ['team' => (string) $team['key']]);
}
}
if ($tabellen !== []) {
$bloecke[] = ['name' => 'tabellen', 'seiten' => $tabellen, 'anzeigen_danach' => true];
}
- foreach (['vorschau', 'termine', 'news', 'kontakt', 'mitglied', 'historie', 'sportheim', 'partner', 'momente'] as $seite) {
- $bloecke[] = ['name' => $seite, 'seiten' => [$live($seite)], 'anzeigen_danach' => true];
+ foreach (['vorschau', 'termine', 'news', 'kontakt', 'mitglied', 'historie', 'sportheim', 'partner', 'danke', 'momente'] as $seite) {
+ // Die Danke-Seite trägt ihr Anzeigenmotiv im Seiteneintrag (siehe oben),
+ // direkt hinter der Partner-werden-Seite: aufgeschlagen liegen damit
+ // Akquise und Dank als Partner-Doppelseite nebeneinander.
+ $extra = ($seite === 'danke' && isset($platzierteAnzeigen['danke']))
+ ? ['anzeige' => $platzierteAnzeigen['danke']]
+ : [];
+ // Hinter der Partner-werden-Seite bleibt die Lücke leer, damit die
+ // Danke-Seite direkt folgt: Akquise und Dank gehören zusammen und
+ // liegen aufgeschlagen im günstigen Fall als Doppelseite nebeneinander.
+ $bloecke[] = [
+ 'name' => $seite,
+ 'seiten' => [$live($seite, $extra)],
+ 'anzeigen_danach' => $seite !== 'partner',
+ ];
}
$bloecke[] = ['name' => 'impressum', 'seiten' => [$live('impressum')], 'anzeigen_danach' => false];
@@ -651,6 +743,8 @@ $issues[] = [
'vorwort' => $oldVorwort,
'interview' => $oldInterview,
'aufruf' => $oldAufruf,
+ 'veranstaltung' => $oldVeranstaltung,
+ 'cover' => $oldCover,
'verfasser_photo' => $verfasserPhoto,
'termine_snapshot' => $termineSnapshot,
'images' => $images,
diff --git a/data/anzeigen.json b/data/anzeigen.json
index bc3f70e..ed585b5 100644
--- a/data/anzeigen.json
+++ b/data/anzeigen.json
@@ -239,7 +239,7 @@
"slug": "sportfest-2026-programm",
"sponsor": "Programm Sportfest 2026",
"aktiv": true,
- "bis": "2026-09-06",
+ "bis": "2026-09-03",
"alt": "Eigenanzeige: Programm des Sportfests 2026 mit allen Spielen, Turnieren und der Kinderolympiade",
"base": "img/anzeigen/sportfest-2026-programm",
"widths": [
@@ -248,18 +248,6 @@
1440
]
},
- {
- "slug": "emons-partnerdank",
- "sponsor": "Emons Spedition und Dank an unsere Partner",
- "aktiv": true,
- "alt": "Anzeige: Emons Spedition, darunter unser Dank an alle Partner",
- "base": "img/anzeigen/emons-partnerdank",
- "widths": [
- 480,
- 960,
- 1440
- ]
- },
{
"slug": "gagel-pickelmann",
"sponsor": "Gagel Bäder und Mineralölhandel Pickelmann",
@@ -294,6 +282,19 @@
960,
1440
]
+ },
+ {
+ "slug": "emons",
+ "sponsor": "Emons Spedition",
+ "aktiv": true,
+ "base": "img/anzeigen/emons",
+ "widths": [
+ 480,
+ 960,
+ 1141
+ ],
+ "alt": "Anzeige: Emons Spedition Himmelkron, Ihr Partner für nationale und internationale Transporte",
+ "platzierung": "danke"
}
]
}
diff --git a/data/stadionzeitung.json b/data/stadionzeitung.json
index 7690dee..4144eb9 100644
--- a/data/stadionzeitung.json
+++ b/data/stadionzeitung.json
@@ -1,5 +1,847 @@
{
"issues": [
+ {
+ "slug": "2026-09-04-ats-wartenfels",
+ "opponent": "ATS Wartenfels",
+ "date": "2026-09-04",
+ "team_key": "erste-mannschaft",
+ "status": "entwurf",
+ "pages": 48,
+ "player_image": {
+ "base": "img/stadionzeitung/2026-09-04-ats-wartenfels/spieler",
+ "widths": [
+ 320,
+ 640,
+ 1000
+ ],
+ "ext": "png",
+ "alt": ""
+ },
+ "qr_image": "img/stadionzeitung/2026-09-04-ats-wartenfels/qr.png",
+ "vorwort": {
+ "heading": "Drei Tage Fußball im Katzbachtal",
+ "paragraphs": [
+ "Liebe Fußballfreunde,",
+ "herzlich willkommen zu unserem Sportfest. Ein besonderer Gruß gilt dem Schiedsrichtergespann und unseren Gästen vom ATS Wartenfels.",
+ "Bis Sonntag ist bei uns durchgehend Betrieb: am Samstag unsere Zweite, am Sonntag unsere Frauen, dazwischen die Turniere unserer Jugend. Danke an alle, die dieses Wochenende möglich machen.",
+ "Der 7:3-Heimsieg gegen Marktschorgast gibt uns Rückenwind. Leidenschaft, Wille, Heimsieg. Los geht's!"
+ ],
+ "closing": "Rot-schwarze Grüße",
+ "signee_name": "Benedikt Höfner",
+ "signee_role": "Sportlicher Leiter"
+ },
+ "interview": null,
+ "aufruf": null,
+ "veranstaltung": "sportfest-2026",
+ "cover": {
+ "teaser": "Das komplette Programm zum Sportfest und die Spielpläne aller vier Jugendturniere."
+ },
+ "verfasser_photo": {
+ "base": "img/stadionzeitung/2026-09-04-ats-wartenfels/verfasser",
+ "widths": [
+ 120,
+ 222
+ ],
+ "alt": ""
+ },
+ "termine_snapshot": [
+ {
+ "date": "2026-09-04",
+ "date_end": "2026-09-06",
+ "title": "Sportfest 2026",
+ "department": "verein",
+ "type": "event",
+ "meta": "16:30 Uhr · Hans-Rausch-Sportanlage",
+ "href": "/veranstaltungen/sportfest-2026",
+ "time": "16:30",
+ "location": "Hans-Rausch-Sportanlage",
+ "raw": {
+ "slug": "sportfest-2026",
+ "title": "Sportfest 2026",
+ "date_start": "2026-09-04",
+ "date_end": "2026-09-06",
+ "time": "16:30",
+ "location": "Hans-Rausch-Sportanlage",
+ "department": "verein",
+ "status": "veroeffentlicht",
+ "text": "Drei Tage Fußball, Essen, Trinken, Kaffee und Kuchen: bei uns ist für groß und klein alles dabei, für die Kinder steht eine Hüpfburg.",
+ "ankuendigung": null,
+ "rueckblick": null,
+ "karte": "club",
+ "anfahrt": [],
+ "programm": [
+ {
+ "tag": "2026-09-04",
+ "punkte": [
+ {
+ "zeit": "16:30",
+ "titel": "Spiel unserer E2-Jugend"
+ },
+ {
+ "zeit": "18:00",
+ "titel": "Unsere erste Mannschaft gegen ATS Wartenfels",
+ "text": "Kreisklasse"
+ }
+ ]
+ },
+ {
+ "tag": "2026-09-05",
+ "punkte": [
+ {
+ "zeit": "09:00",
+ "titel": "Turnier der G-Jugend"
+ },
+ {
+ "zeit": "12:00",
+ "titel": "Turnier der F1-Jugend"
+ },
+ {
+ "zeit": "12:00",
+ "titel": "Turnier der F2-Jugend"
+ },
+ {
+ "zeit": "14:30",
+ "titel": "Kinderolympiade",
+ "text": "bis 16:30 Uhr in der Turnhalle Maximum Gym"
+ },
+ {
+ "zeit": "15:00",
+ "titel": "Turnier der D1-Jugend"
+ },
+ {
+ "zeit": "18:00",
+ "titel": "Unsere zweite Mannschaft gegen SV Weidenberg III/SSV Warmensteinach II",
+ "text": "Reserve"
+ }
+ ]
+ },
+ {
+ "tag": "2026-09-06",
+ "punkte": [
+ {
+ "zeit": "11:00",
+ "titel": "Unsere Frauenmannschaft gegen 1. FC 1928 Schwarzenbach/Saale",
+ "text": "Kreisliga Ost"
+ },
+ {
+ "zeit": "13:00",
+ "titel": "Turnier der E1-Jugend"
+ },
+ {
+ "zeit": "15:00",
+ "titel": "Spiel unserer D2-Jugend"
+ },
+ {
+ "zeit": "16:30",
+ "titel": "Spiel unserer C/B-Jugend"
+ }
+ ]
+ }
+ ],
+ "turniere": [
+ {
+ "key": "f1",
+ "title": "F1-Jugend",
+ "date": "2026-09-05",
+ "start": "12:00",
+ "spieldauer": 10,
+ "modus": "Jeder gegen jeden mit Hin und Rückspiel",
+ "teams": [
+ "VfR Katschenreuth 1",
+ "SV Motschenbach 1",
+ "TSV Neudrossenfeld 1"
+ ],
+ "spiele": [
+ {
+ "nr": 1,
+ "zeit": "12:00",
+ "heim": "SV Motschenbach 1",
+ "gast": "TSV Neudrossenfeld 1"
+ },
+ {
+ "nr": 2,
+ "zeit": "12:15",
+ "heim": "VfR Katschenreuth 1",
+ "gast": "SV Motschenbach 1"
+ },
+ {
+ "nr": 3,
+ "zeit": "12:30",
+ "heim": "TSV Neudrossenfeld 1",
+ "gast": "VfR Katschenreuth 1"
+ },
+ {
+ "nr": 4,
+ "zeit": "12:45",
+ "heim": "TSV Neudrossenfeld 1",
+ "gast": "SV Motschenbach 1"
+ },
+ {
+ "nr": 5,
+ "zeit": "13:00",
+ "heim": "SV Motschenbach 1",
+ "gast": "VfR Katschenreuth 1"
+ },
+ {
+ "nr": 6,
+ "zeit": "13:15",
+ "heim": "VfR Katschenreuth 1",
+ "gast": "TSV Neudrossenfeld 1"
+ }
+ ]
+ },
+ {
+ "key": "f2",
+ "title": "F2-Jugend",
+ "date": "2026-09-05",
+ "start": "12:00",
+ "spieldauer": 15,
+ "modus": "Jeder gegen jeden",
+ "teams": [
+ "TSV 08 Kulmbach",
+ "SG Neuenmarkt",
+ "VfR Katschenreuth 2",
+ "SV Motschenbach 2"
+ ],
+ "spiele": [
+ {
+ "nr": 1,
+ "zeit": "12:00",
+ "heim": "SG Neuenmarkt",
+ "gast": "SV Motschenbach 2"
+ },
+ {
+ "nr": 2,
+ "zeit": "12:16",
+ "heim": "TSV 08 Kulmbach",
+ "gast": "VfR Katschenreuth 2"
+ },
+ {
+ "nr": 3,
+ "zeit": "12:32",
+ "heim": "SG Neuenmarkt",
+ "gast": "TSV 08 Kulmbach"
+ },
+ {
+ "nr": 4,
+ "zeit": "12:48",
+ "heim": "VfR Katschenreuth 2",
+ "gast": "SV Motschenbach 2"
+ },
+ {
+ "nr": 5,
+ "zeit": "13:04",
+ "heim": "SV Motschenbach 2",
+ "gast": "TSV 08 Kulmbach"
+ },
+ {
+ "nr": 6,
+ "zeit": "13:20",
+ "heim": "VfR Katschenreuth 2",
+ "gast": "SG Neuenmarkt"
+ }
+ ]
+ },
+ {
+ "key": "d1",
+ "title": "D1-Jugend",
+ "date": "2026-09-05",
+ "start": "15:00",
+ "spieldauer": 35,
+ "modus": "Jeder gegen jeden",
+ "teams": [
+ "TSV 08 Kulmbach",
+ "JFG Maintal/Friesenbachtal",
+ "VfB Kulmbach"
+ ],
+ "spiele": [
+ {
+ "nr": 1,
+ "zeit": "15:00",
+ "heim": "TSV 08 Kulmbach",
+ "gast": "JFG Maintal/Friesenbachtal"
+ },
+ {
+ "nr": 2,
+ "zeit": "15:45",
+ "heim": "JFG Maintal/Friesenbachtal",
+ "gast": "VfB Kulmbach"
+ },
+ {
+ "nr": 3,
+ "zeit": "16:30",
+ "heim": "VfB Kulmbach",
+ "gast": "TSV 08 Kulmbach"
+ }
+ ]
+ },
+ {
+ "key": "e1",
+ "title": "E1-Jugend",
+ "date": "2026-09-06",
+ "start": "13:00",
+ "spieldauer": 12,
+ "felder": 2,
+ "modus": "Jeder gegen jeden",
+ "teams": [
+ "TSV 08 Kulmbach",
+ "SV Motschenbach 2",
+ "VfB Kulmbach 2",
+ "VfR Katschenreuth 2",
+ "SG Wirsberg"
+ ],
+ "spiele": [
+ {
+ "nr": 1,
+ "zeit": "13:00",
+ "feld": 1,
+ "heim": "TSV 08 Kulmbach",
+ "gast": "SG Wirsberg"
+ },
+ {
+ "nr": 2,
+ "zeit": "13:00",
+ "feld": 2,
+ "heim": "SV Motschenbach 2",
+ "gast": "VfB Kulmbach 2"
+ },
+ {
+ "nr": 3,
+ "zeit": "13:16",
+ "feld": 1,
+ "heim": "VfR Katschenreuth 2",
+ "gast": "TSV 08 Kulmbach"
+ },
+ {
+ "nr": 4,
+ "zeit": "13:16",
+ "feld": 2,
+ "heim": "VfB Kulmbach 2",
+ "gast": "SG Wirsberg"
+ },
+ {
+ "nr": 5,
+ "zeit": "13:32",
+ "feld": 1,
+ "heim": "VfR Katschenreuth 2",
+ "gast": "SV Motschenbach 2"
+ },
+ {
+ "nr": 6,
+ "zeit": "13:32",
+ "feld": 2,
+ "heim": "VfB Kulmbach 2",
+ "gast": "TSV 08 Kulmbach"
+ },
+ {
+ "nr": 7,
+ "zeit": "13:48",
+ "feld": 1,
+ "heim": "SG Wirsberg",
+ "gast": "VfR Katschenreuth 2"
+ },
+ {
+ "nr": 8,
+ "zeit": "13:48",
+ "feld": 2,
+ "heim": "TSV 08 Kulmbach",
+ "gast": "SV Motschenbach 2"
+ },
+ {
+ "nr": 9,
+ "zeit": "14:04",
+ "feld": 1,
+ "heim": "VfR Katschenreuth 2",
+ "gast": "VfB Kulmbach 2"
+ },
+ {
+ "nr": 10,
+ "zeit": "14:04",
+ "feld": 2,
+ "heim": "SV Motschenbach 2",
+ "gast": "SG Wirsberg"
+ }
+ ]
+ }
+ ],
+ "plakate": [
+ {
+ "base": "img/veranstaltungen/sportfest-2026/plakat-01",
+ "widths": [
+ 640,
+ 1000,
+ 1587
+ ],
+ "alt": "Plakat zum Sportfest 2026 mit Luftbild unserer Sportanlage und den Terminen vom 4. bis 6. September",
+ "source": "1-ankuendigung.png"
+ },
+ {
+ "base": "img/veranstaltungen/sportfest-2026/plakat-02",
+ "widths": [
+ 640,
+ 1000,
+ 1587
+ ],
+ "alt": "Programmplakat zum Sportfest 2026 mit allen Spielen, Turnieren und der Kinderolympiade am Samstag",
+ "source": "2-programm.png"
+ }
+ ],
+ "galerie": []
+ }
+ },
+ {
+ "date": "2026-09-05",
+ "date_end": null,
+ "title": "(SG1) TSV Melkendorf III/ TSV 08 Kulmbach II – (SG 2) SV Weidenberg III/SSV Warmensteinach II (flex)",
+ "department": "fussball",
+ "type": "spiel",
+ "meta": "Heimspiel · Reserve BK 5 BT-KU",
+ "href": "/matchcenter",
+ "kickoff": "2026-09-05T18:00",
+ "team_name": "2. Mannschaft",
+ "league": "Reserve BK 5 BT-KU",
+ "raw": {
+ "kickoff": "2026-09-05T18:00",
+ "date_display": "Sa, 05.09.",
+ "time_display": "18:00 Uhr",
+ "competition": "Liga",
+ "home": "(SG1) TSV Melkendorf III/ TSV 08 Kulmbach II",
+ "away": "(SG 2) SV Weidenberg III/SSV Warmensteinach II (flex)",
+ "home_crest": "img/crests/00ES8GNK4O000002VV0AG08LVUPGND5I.png",
+ "away_crest": "img/crests/00ES8GNK4O000013VV0AG08LVUPGND5I.png",
+ "is_home": true,
+ "team_key": "zweite-mannschaft",
+ "team_name": "2. Mannschaft"
+ }
+ },
+ {
+ "date": "2026-09-06",
+ "date_end": null,
+ "title": "TSV 08 Kulmbach – 1. FC 1928 Schwarzenbach/Saale",
+ "department": "fussball",
+ "type": "spiel",
+ "meta": "Heimspiel · Frauen KL Ost",
+ "href": "/matchcenter",
+ "kickoff": "2026-09-06T11:00",
+ "team_name": "Frauenmannschaft",
+ "league": "Frauen KL Ost",
+ "raw": {
+ "kickoff": "2026-09-06T11:00",
+ "date_display": "So, 06.09.",
+ "time_display": "11:00 Uhr",
+ "competition": "Liga",
+ "home": "TSV 08 Kulmbach",
+ "away": "1. FC 1928 Schwarzenbach/Saale",
+ "home_crest": "img/crests/00ES8GNK3G000012VV0AG08LVUPGND5I.png",
+ "away_crest": "img/crests/00ES8GNK9O00000QVV0AG08LVUPGND5I.png",
+ "is_home": true,
+ "team_key": "damen",
+ "team_name": "Frauenmannschaft"
+ }
+ },
+ {
+ "date": "2026-09-13",
+ "date_end": null,
+ "title": "TSV 08 Kulmbach – TSV Engelmannsreuth",
+ "department": "fussball",
+ "type": "spiel",
+ "meta": "Heimspiel · Frauen KL Ost",
+ "href": "/matchcenter",
+ "kickoff": "2026-09-13T10:30",
+ "team_name": "Frauenmannschaft",
+ "league": "Frauen KL Ost",
+ "raw": {
+ "kickoff": "2026-09-13T10:30",
+ "date_display": "So, 13.09.",
+ "time_display": "10:30 Uhr",
+ "competition": "Liga",
+ "home": "TSV 08 Kulmbach",
+ "away": "TSV Engelmannsreuth",
+ "home_crest": "img/crests/00ES8GNK3G000012VV0AG08LVUPGND5I.png",
+ "away_crest": "img/crests/00ES8GNK3G000009VV0AG08LVUPGND5I.png",
+ "is_home": true,
+ "team_key": "damen",
+ "team_name": "Frauenmannschaft"
+ }
+ },
+ {
+ "date": "2026-09-13",
+ "date_end": null,
+ "title": "SV Lindenhardt II flex – (SG1) TSV Melkendorf III/ TSV 08 Kulmbach II",
+ "department": "fussball",
+ "type": "spiel",
+ "meta": "Auswärtsspiel · Reserve BK 5 BT-KU",
+ "href": "/matchcenter",
+ "kickoff": "2026-09-13T13:00",
+ "team_name": "2. Mannschaft",
+ "league": "Reserve BK 5 BT-KU",
+ "raw": {
+ "kickoff": "2026-09-13T13:00",
+ "date_display": "So, 13.09.",
+ "time_display": "13:00 Uhr",
+ "competition": "Liga",
+ "home": "SV Lindenhardt II flex",
+ "away": "(SG1) TSV Melkendorf III/ TSV 08 Kulmbach II",
+ "home_crest": "img/crests/00ES8GNK3G00001AVV0AG08LVUPGND5I.png",
+ "away_crest": "img/crests/00ES8GNK4O000002VV0AG08LVUPGND5I.png",
+ "is_home": false,
+ "team_key": "zweite-mannschaft",
+ "team_name": "2. Mannschaft"
+ }
+ }
+ ],
+ "images": [
+ {
+ "type": "live",
+ "page": "cover"
+ },
+ {
+ "type": "live",
+ "page": "vorwort"
+ },
+ {
+ "type": "live",
+ "page": "programm"
+ },
+ {
+ "type": "image",
+ "base": "img/anzeigen/eldorado",
+ "widths": [
+ 480,
+ 960,
+ 1440
+ ],
+ "alt": "Anzeige: eldorado Radsporttechnik Kulmbach"
+ },
+ {
+ "type": "live",
+ "page": "turnier",
+ "turnier": "f1"
+ },
+ {
+ "type": "live",
+ "page": "turnier",
+ "turnier": "f2"
+ },
+ {
+ "type": "live",
+ "page": "turnier",
+ "turnier": "d1"
+ },
+ {
+ "type": "live",
+ "page": "turnier",
+ "turnier": "e1"
+ },
+ {
+ "type": "image",
+ "base": "img/anzeigen/egu",
+ "widths": [
+ 480,
+ 960,
+ 1440
+ ],
+ "alt": "Anzeige: Erfrischungsgetränke-Union Kulmbacher Gruppe GmbH"
+ },
+ {
+ "type": "image",
+ "base": "img/anzeigen/hofmann-graef",
+ "widths": [
+ 480,
+ 960,
+ 1440
+ ],
+ "alt": "Anzeige: Buchhandlung Hofmann, Kulmbach, und Automobilwerkstatt Gräf, Thurnau"
+ },
+ {
+ "type": "live",
+ "page": "ergebnisse"
+ },
+ {
+ "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": "live",
+ "page": "historie"
+ },
+ {
+ "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": "sportheim"
+ },
+ {
+ "type": "image",
+ "base": "img/anzeigen/elma-kleinheinz",
+ "widths": [
+ 480,
+ 960,
+ 1440
+ ],
+ "alt": "Anzeige: elma GmbH Bayreuth und Metzgerei Kleinheinz, Kulmbach"
+ },
+ {
+ "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/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": "momente"
+ },
+ {
+ "type": "image",
+ "base": "img/anzeigen/schramm",
+ "widths": [
+ 480,
+ 960,
+ 1440
+ ],
+ "alt": "Anzeige: Landgasthof und Metzgerei Schramm, Forstlahm"
+ },
+ {
+ "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-04-ats-wartenfels/rueckseite",
+ "widths": [
+ 480,
+ 960,
+ 1410
+ ],
+ "alt": "Rückseite der Stadionzeitung gegen ATS Wartenfels"
+ }
+ ]
+ },
{
"slug": "2026-08-29-asv-marktschorgast",
"opponent": "ASV Marktschorgast",
diff --git a/data/veranstaltungen.json b/data/veranstaltungen.json
index 3bb7797..0b9202e 100644
--- a/data/veranstaltungen.json
+++ b/data/veranstaltungen.json
@@ -45,6 +45,301 @@
"rueckblick": null,
"karte": "club",
"anfahrt": [],
+ "programm": [
+ {
+ "tag": "2026-09-04",
+ "punkte": [
+ {
+ "zeit": "16:30",
+ "titel": "Spiel unserer E2-Jugend"
+ },
+ {
+ "zeit": "18:00",
+ "titel": "Unsere erste Mannschaft gegen ATS Wartenfels",
+ "text": "Kreisklasse"
+ }
+ ]
+ },
+ {
+ "tag": "2026-09-05",
+ "punkte": [
+ {
+ "zeit": "09:00",
+ "titel": "Turnier der G-Jugend"
+ },
+ {
+ "zeit": "12:00",
+ "titel": "Turnier der F1-Jugend"
+ },
+ {
+ "zeit": "12:00",
+ "titel": "Turnier der F2-Jugend"
+ },
+ {
+ "zeit": "14:30",
+ "titel": "Kinderolympiade",
+ "text": "bis 16:30 Uhr in der Turnhalle Maximum Gym"
+ },
+ {
+ "zeit": "15:00",
+ "titel": "Turnier der D1-Jugend"
+ },
+ {
+ "zeit": "18:00",
+ "titel": "Unsere zweite Mannschaft gegen SV Weidenberg III/SSV Warmensteinach II",
+ "text": "Reserve"
+ }
+ ]
+ },
+ {
+ "tag": "2026-09-06",
+ "punkte": [
+ {
+ "zeit": "11:00",
+ "titel": "Unsere Frauenmannschaft gegen 1. FC 1928 Schwarzenbach/Saale",
+ "text": "Kreisliga Ost"
+ },
+ {
+ "zeit": "13:00",
+ "titel": "Turnier der E1-Jugend"
+ },
+ {
+ "zeit": "15:00",
+ "titel": "Spiel unserer D2-Jugend"
+ },
+ {
+ "zeit": "16:30",
+ "titel": "Spiel unserer C/B-Jugend"
+ }
+ ]
+ }
+ ],
+ "turniere": [
+ {
+ "key": "f1",
+ "title": "F1-Jugend",
+ "date": "2026-09-05",
+ "start": "12:00",
+ "spieldauer": 10,
+ "modus": "Jeder gegen jeden mit Hin und Rückspiel",
+ "teams": [
+ "VfR Katschenreuth 1",
+ "SV Motschenbach 1",
+ "TSV Neudrossenfeld 1"
+ ],
+ "spiele": [
+ {
+ "nr": 1,
+ "zeit": "12:00",
+ "heim": "SV Motschenbach 1",
+ "gast": "TSV Neudrossenfeld 1"
+ },
+ {
+ "nr": 2,
+ "zeit": "12:15",
+ "heim": "VfR Katschenreuth 1",
+ "gast": "SV Motschenbach 1"
+ },
+ {
+ "nr": 3,
+ "zeit": "12:30",
+ "heim": "TSV Neudrossenfeld 1",
+ "gast": "VfR Katschenreuth 1"
+ },
+ {
+ "nr": 4,
+ "zeit": "12:45",
+ "heim": "TSV Neudrossenfeld 1",
+ "gast": "SV Motschenbach 1"
+ },
+ {
+ "nr": 5,
+ "zeit": "13:00",
+ "heim": "SV Motschenbach 1",
+ "gast": "VfR Katschenreuth 1"
+ },
+ {
+ "nr": 6,
+ "zeit": "13:15",
+ "heim": "VfR Katschenreuth 1",
+ "gast": "TSV Neudrossenfeld 1"
+ }
+ ]
+ },
+ {
+ "key": "f2",
+ "title": "F2-Jugend",
+ "date": "2026-09-05",
+ "start": "12:00",
+ "spieldauer": 15,
+ "modus": "Jeder gegen jeden",
+ "teams": [
+ "TSV 08 Kulmbach",
+ "SG Neuenmarkt",
+ "VfR Katschenreuth 2",
+ "SV Motschenbach 2"
+ ],
+ "spiele": [
+ {
+ "nr": 1,
+ "zeit": "12:00",
+ "heim": "SG Neuenmarkt",
+ "gast": "SV Motschenbach 2"
+ },
+ {
+ "nr": 2,
+ "zeit": "12:16",
+ "heim": "TSV 08 Kulmbach",
+ "gast": "VfR Katschenreuth 2"
+ },
+ {
+ "nr": 3,
+ "zeit": "12:32",
+ "heim": "SG Neuenmarkt",
+ "gast": "TSV 08 Kulmbach"
+ },
+ {
+ "nr": 4,
+ "zeit": "12:48",
+ "heim": "VfR Katschenreuth 2",
+ "gast": "SV Motschenbach 2"
+ },
+ {
+ "nr": 5,
+ "zeit": "13:04",
+ "heim": "SV Motschenbach 2",
+ "gast": "TSV 08 Kulmbach"
+ },
+ {
+ "nr": 6,
+ "zeit": "13:20",
+ "heim": "VfR Katschenreuth 2",
+ "gast": "SG Neuenmarkt"
+ }
+ ]
+ },
+ {
+ "key": "d1",
+ "title": "D1-Jugend",
+ "date": "2026-09-05",
+ "start": "15:00",
+ "spieldauer": 35,
+ "modus": "Jeder gegen jeden",
+ "teams": [
+ "TSV 08 Kulmbach",
+ "JFG Maintal/Friesenbachtal",
+ "VfB Kulmbach"
+ ],
+ "spiele": [
+ {
+ "nr": 1,
+ "zeit": "15:00",
+ "heim": "TSV 08 Kulmbach",
+ "gast": "JFG Maintal/Friesenbachtal"
+ },
+ {
+ "nr": 2,
+ "zeit": "15:45",
+ "heim": "JFG Maintal/Friesenbachtal",
+ "gast": "VfB Kulmbach"
+ },
+ {
+ "nr": 3,
+ "zeit": "16:30",
+ "heim": "VfB Kulmbach",
+ "gast": "TSV 08 Kulmbach"
+ }
+ ]
+ },
+ {
+ "key": "e1",
+ "title": "E1-Jugend",
+ "date": "2026-09-06",
+ "start": "13:00",
+ "spieldauer": 12,
+ "felder": 2,
+ "modus": "Jeder gegen jeden",
+ "teams": [
+ "TSV 08 Kulmbach",
+ "SV Motschenbach 2",
+ "VfB Kulmbach 2",
+ "VfR Katschenreuth 2",
+ "SG Wirsberg"
+ ],
+ "spiele": [
+ {
+ "nr": 1,
+ "zeit": "13:00",
+ "feld": 1,
+ "heim": "TSV 08 Kulmbach",
+ "gast": "SG Wirsberg"
+ },
+ {
+ "nr": 2,
+ "zeit": "13:00",
+ "feld": 2,
+ "heim": "SV Motschenbach 2",
+ "gast": "VfB Kulmbach 2"
+ },
+ {
+ "nr": 3,
+ "zeit": "13:16",
+ "feld": 1,
+ "heim": "VfR Katschenreuth 2",
+ "gast": "TSV 08 Kulmbach"
+ },
+ {
+ "nr": 4,
+ "zeit": "13:16",
+ "feld": 2,
+ "heim": "VfB Kulmbach 2",
+ "gast": "SG Wirsberg"
+ },
+ {
+ "nr": 5,
+ "zeit": "13:32",
+ "feld": 1,
+ "heim": "VfR Katschenreuth 2",
+ "gast": "SV Motschenbach 2"
+ },
+ {
+ "nr": 6,
+ "zeit": "13:32",
+ "feld": 2,
+ "heim": "VfB Kulmbach 2",
+ "gast": "TSV 08 Kulmbach"
+ },
+ {
+ "nr": 7,
+ "zeit": "13:48",
+ "feld": 1,
+ "heim": "SG Wirsberg",
+ "gast": "VfR Katschenreuth 2"
+ },
+ {
+ "nr": 8,
+ "zeit": "13:48",
+ "feld": 2,
+ "heim": "TSV 08 Kulmbach",
+ "gast": "SV Motschenbach 2"
+ },
+ {
+ "nr": 9,
+ "zeit": "14:04",
+ "feld": 1,
+ "heim": "VfR Katschenreuth 2",
+ "gast": "VfB Kulmbach 2"
+ },
+ {
+ "nr": 10,
+ "zeit": "14:04",
+ "feld": 2,
+ "heim": "SV Motschenbach 2",
+ "gast": "SG Wirsberg"
+ }
+ ]
+ }
+ ],
"plakate": [
{
"base": "img/veranstaltungen/sportfest-2026/plakat-01",
@@ -370,4 +665,4 @@
]
}
]
-}
\ No newline at end of file
+}
diff --git a/public/assets/css/components.css b/public/assets/css/components.css
index 5457dbc..b204b90 100644
--- a/public/assets/css/components.css
+++ b/public/assets/css/components.css
@@ -6097,6 +6097,25 @@ body.is-sz-full .back-to-top {
}
+/* Titelgeschichte einer Sonderausgabe: das Fest statt der Begegnung. Coolvetica
+ in Versalien wie der Masthead, aber kleiner — es soll die zweite Stimme auf
+ dem Cover sein, nicht mit der Wortmarke konkurrieren. */
+.stadionzeitung-cover__feature {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 1.7cqi;
+ text-align: left;
+}
+
+.stadionzeitung-cover__feature-title {
+ font-family: var(--ff-heading);
+ font-size: 13cqi;
+ line-height: 0.88;
+ text-transform: uppercase;
+ text-shadow: 0 2px 10px rgb(0 0 0 / 0.5);
+}
+
/* Vereins-Hero-Bild als Hintergrund (dieselbe Quelle wie die Startseite). */
.stadionzeitung-cover__bg {
position: absolute;
@@ -6135,6 +6154,63 @@ body.is-sz-full .back-to-top {
width: auto;
}
+/* Hauptplakat einer Sonderausgabe an der Stelle des Spielerfotos: rechts
+ randabfallend, leicht schräg gelegt und an zwei gegenüberliegenden Ecken
+ gerundet (links oben, rechts unten) — wie ein aufs Cover gelegter Aushang,
+ nicht wie ein eingesetztes Bild (Felix, 03.09.2026).
+
+ Warum kleiner als das Spielerfoto (98cqi hoch): ein Freisteller ist schmal,
+ ein Plakat im Hochformat ist bei gleicher Höhe knapp 60cqi breit und läge damit
+ über der Typografie unten links. Bei 66cqi Höhe belegt es die rechte Bildhälfte
+ und lässt Masthead und Fest-Titel frei — die Textspalte wird dafür schmaler
+ (siehe --poster .stadionzeitung-cover__corner).
+
+ Schatten statt Rahmen, damit das Plakat auf dem Foto-Hintergrund abhebt:
+ box-shadow ist unbedenklich, die Druck-Falle im Projekt betrifft text-shadow
+ auf umbrechendem Text (siehe .stadionzeitung-cover__corner). */
+.stadionzeitung-cover__poster {
+ position: absolute;
+ z-index: 1;
+ /* VOLLSTÄNDIG im Bild, nicht randabfallend wie das Spielerfoto: ein Plakat
+ trägt Text (Datum, Ort), und angeschnitten fehlte rechts genau der. Die
+ Rotation lässt die Ecken trotzdem über die Passepartout-Linie laufen, das
+ Plakat liegt also sichtbar „auf" der Seite. */
+ /* Unten rechts randabfallend („angeschnitten", Felix 03.09.2026): das Plakat
+ läuft über zwei Kanten hinaus und wirkt dadurch aufgelegt statt eingesetzt.
+ Angeschnitten wird nur die untere rechte Ecke, nicht die Kopfzeile mit den
+ Terminen — die bleibt lesbar. */
+ right: -1.5cqi;
+ /* bottom: 0 statt negativ — die Box endet an der Blattkante, angeschnitten
+ wird nur noch die gedrehte Ecke (4 Grad auf 53cqi Breite = knapp 4cqi
+ Überhang). Das hält den Anschnitt-Look und rückt das Plakat gleichzeitig
+ aus dem unteren Bereich nach oben (Felix, 03.09.2026). */
+ bottom: 0;
+ /* Höhe ist NICHT frei wählbar: das Plakat darf die Textspalte links nicht
+ berühren. Rechnung bei 4 Grad Drehung — belegte Breite = Höhe / 1,414
+ (Hochformat) + sin(4°) × Höhe (Überhang der gedrehten Ecken) ≈ 0,78 × Höhe.
+ Bei 72cqi sind das 56,2cqi; mit 1,5cqi Überhang rechts liegt die linke Kante
+ bei 45,3% und die Textspalte endet bei 44%. Der Überhang ist bewusst winzig:
+ vom Plakat bleiben so 97% sichtbar und seine Schlagzeile „Sportfest" steht
+ ganz im Bild (Felix, 03.09.2026) — bei 9cqi Überhang fehlte rechts ein
+ Siebtel und das Wort lief aus dem Bild. Angeschnitten wirkt es trotzdem,
+ das macht die gedrehte Ecke unten. Höhe und Überhang hängen also
+ zusammen: weiter nach links (kleinerer Überhang) heißt kleiner, größer
+ heißt weiter nach rechts — oder die Textspalte muss schmaler
+ (--poster .stadionzeitung-cover__corner). Nicht nur eine Zahl drehen. */
+ height: 72cqi;
+ width: auto;
+ border-radius: 5cqi 0 5cqi 0;
+ transform: rotate(4deg);
+ box-shadow: 0 3cqi 6cqi rgb(0 0 0 / 0.45);
+}
+
+/* Schmaler als das Standard-Cover (56cqi): das Plakat belegt die rechte Hälfte.
+ „Sportfest 2026" bricht dadurch zweizeilig — auf einem Cover trägt das eher,
+ als es schadet. */
+.stadionzeitung-cover--poster .stadionzeitung-cover__corner {
+ max-width: 38cqi;
+}
+
.stadionzeitung-cover__inner {
position: relative;
z-index: 2;
@@ -7452,6 +7528,225 @@ a.news-feature__link:hover .news-feature__more {
color: var(--clr-accent);
}
+/* === stadionzeitung-programm (Live-Seite, Sportfest-Ausgabe) === */
+/* Tagesprogramm aus data/veranstaltungen.json → programm[] (dieselbe Liste wie
+ die Website-Detailseite, hier fürs A5-Blatt gesetzt): je Tag ein Abel-Caps-
+ Tagestitel, darunter Zeit | Titel. Die Zeit trägt links den roten Strich wie
+ .veranstaltung-programm__zeit auf der Website — gleiche Sprache, andere
+ Skala. Maße in cqi (Selbst-Containment, siehe .stadionzeitung-page). */
+/* Abstände bewusst straff: ein dreitägiges Fest bringt gut zwölf Punkte plus
+ drei Tagestitel auf EIN A5-Blatt (gemessen: mit den üblichen 3.3cqi lief die
+ Seite 59px über die Blattkante). Wer hier lockerer setzt, misst die
+ Seitenhöhe nach. */
+.stadionzeitung-programm__tag {
+ margin-bottom: 2.2cqi;
+}
+
+.stadionzeitung-programm__tag:last-child {
+ margin-bottom: 0;
+}
+
+.stadionzeitung-programm__tag-titel {
+ margin-bottom: 0.9cqi;
+ padding-bottom: 0.6cqi;
+ border-bottom: 1px solid var(--clr-glass-border);
+ font-family: var(--ff-body);
+ font-weight: var(--fw-bold);
+ font-size: 2.9cqi;
+ text-transform: uppercase;
+ letter-spacing: var(--ls-caps);
+ color: var(--clr-text-muted);
+}
+
+.stadionzeitung-programm__liste {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.stadionzeitung-programm__punkt {
+ display: grid;
+ grid-template-columns: 11cqi minmax(0, 1fr);
+ gap: 2.2cqi;
+ align-items: baseline;
+ padding-block: 0.5cqi;
+}
+
+.stadionzeitung-programm__zeit {
+ padding-left: 1.4cqi;
+ border-left: 2px solid var(--clr-accent);
+ font-family: var(--ff-body);
+ font-weight: var(--fw-bold);
+ font-size: 3.3cqi;
+ font-variant-numeric: tabular-nums;
+ white-space: nowrap;
+}
+
+.stadionzeitung-programm__titel {
+ display: block;
+ font-size: 3.3cqi;
+ line-height: 1.3;
+}
+
+.stadionzeitung-programm__text {
+ display: block;
+ margin-top: 0.3cqi;
+ font-size: 2.7cqi;
+ line-height: 1.3;
+ color: var(--clr-text-muted);
+}
+
+/* === stadionzeitung-turnier (Live-Seite, Sportfest-Ausgabe) === */
+/* Ein Jugendturnier pro Blatt: Spielplan mit LEERER Ergebnisspalte
+ (Schreiblinien) und leere Abschlusstabelle — das Blatt ist zum Eintragen am
+ Platz gedacht, deshalb echte Schreibhöhe in den leeren Zellen. Eigenes Team
+ wie league-table tr.is-own (rote Tönung + Abel-Fett). Tabellenmaße an
+ .stadionzeitung-page .league-table angelehnt (2.7/2.3cqi). Der Modifier
+ --dicht (ab neun Spielen, E-Jugend) rückt die Zeilen enger, sonst läuft die
+ Abschlusstabelle über die Blattkante. */
+.stadionzeitung-turnier__label {
+ caption-side: top;
+ text-align: left;
+ margin: 3.3cqi 0 1.2cqi;
+ font-family: var(--ff-body);
+ font-weight: var(--fw-bold);
+ font-size: 2.9cqi;
+ text-transform: uppercase;
+ letter-spacing: var(--ls-caps);
+ color: var(--clr-text-muted);
+}
+
+p.stadionzeitung-turnier__label:first-of-type {
+ margin-top: 0;
+}
+
+.stadionzeitung-turnier__teams {
+ font-size: 3.1cqi;
+ line-height: 1.5;
+}
+
+.stadionzeitung-turnier__spiele,
+.stadionzeitung-turnier__tabelle {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 2.9cqi;
+ font-variant-numeric: tabular-nums;
+}
+
+.stadionzeitung-turnier__spiele thead th,
+.stadionzeitung-turnier__tabelle thead th {
+ padding: 0.8cqi 0.9cqi;
+ border-bottom: 1px solid var(--clr-glass-border-strong);
+ font-family: var(--ff-body);
+ font-weight: var(--fw-bold);
+ font-size: 2.3cqi;
+ text-transform: uppercase;
+ letter-spacing: var(--ls-caps);
+ color: var(--clr-text-muted);
+ text-align: left;
+}
+
+.stadionzeitung-turnier__spiele td,
+.stadionzeitung-turnier__tabelle td {
+ padding: 1cqi 0.9cqi;
+ border-bottom: 1px solid var(--clr-glass-border);
+ vertical-align: middle;
+ line-height: 1.25;
+}
+
+/* Dichte Variante ab neun Spielen: die E-Jugend spielt zehn Partien auf zwei
+ Feldern und braucht darunter noch eine Tabelle mit fünf Zeilen. Mit den
+ normalen Abständen lief das Blatt 69px über. Alle vier Stellen unten gehören
+ zusammen — eine allein reicht nicht. */
+.stadionzeitung-turnier--dicht .stadionzeitung-turnier__spiele td {
+ padding-block: 0.3cqi;
+}
+
+/* Die Zeilenhöhe des Spielplans macht NICHT die Schriftgröße, sondern die
+ Schreiblinie: sie ist das höchste Element in der Zeile. Bei zehn Spielen
+ zählt deshalb nur diese Stelle (eine kleinere Schrift brachte gemessen
+ einen einzigen Pixel). 2.8cqi sind auf A5 gut 4mm über der Linie, genug
+ für zwei Ziffern mit dem Stift. */
+.stadionzeitung-turnier--dicht .stadionzeitung-turnier__blank {
+ height: 2.8cqi;
+}
+
+.stadionzeitung-turnier--dicht .stadionzeitung-turnier__tabelle td {
+ padding-block: 0.6cqi;
+}
+
+/* 4cqi Zellhöhe sind auf A5 knapp 6mm plus Innenabstand — genug, um Tore und
+ Punkte mit dem Stift einzutragen, und es passt neben zehn Spielzeilen. */
+.stadionzeitung-turnier--dicht .stadionzeitung-turnier__tabelle td:empty {
+ height: 4cqi;
+}
+
+.stadionzeitung-turnier--dicht .stadionzeitung-turnier__label {
+ margin: 1.8cqi 0 0.7cqi;
+}
+
+.stadionzeitung-turnier--dicht .stadionzeitung-turnier__teams {
+ font-size: 2.9cqi;
+ line-height: 1.35;
+}
+
+.stadionzeitung-turnier__nr {
+ width: 6cqi;
+ color: var(--clr-text-muted);
+}
+
+.stadionzeitung-turnier__zeit {
+ width: 9cqi;
+ white-space: nowrap;
+}
+
+.stadionzeitung-turnier__paarung {
+ width: 100%;
+}
+
+.stadionzeitung-turnier__vs {
+ color: var(--clr-text-muted);
+}
+
+.stadionzeitung-turnier__team.is-own {
+ padding-inline: 0.6cqi;
+ border-radius: 0.6cqi;
+ background: rgb(var(--clr-accent-rgb) / 0.16);
+ font-weight: var(--fw-bold);
+}
+
+/* Leere Ergebnisspalte: zwei Schreiblinien um den Doppelpunkt, unten
+ ausgerichtet wie eine Zeile zum Ausfüllen. */
+.stadionzeitung-turnier__ergebnis {
+ white-space: nowrap;
+ text-align: center;
+}
+
+.stadionzeitung-turnier__blank {
+ display: inline-block;
+ width: 6cqi;
+ height: 3.4cqi;
+ border-bottom: 1px solid var(--clr-text-faint);
+ vertical-align: baseline;
+}
+
+.stadionzeitung-turnier__colon {
+ display: inline-block;
+ padding-inline: 0.8cqi;
+ font-weight: var(--fw-bold);
+}
+
+/* Schreibhöhe der leeren Tabellenzellen (Platzierung wird von Hand eingetragen). */
+.stadionzeitung-turnier__tabelle td:empty {
+ height: 5.6cqi;
+}
+
+.stadionzeitung-turnier__tabelle th:nth-child(n + 3),
+.stadionzeitung-turnier__tabelle td:nth-child(n + 3) {
+ width: 9cqi;
+ text-align: center;
+}
+
/* === stadionzeitung-news (Live-Seite) === */
/* Eigene Liste statt des geteilten news-feed von /news: feste Medienspalte
(Wappen/Foto/Icon-Badge in EINER Größe), damit alle Titel bündig stehen —
@@ -7864,6 +8159,57 @@ a.news-feature__link:hover .news-feature__more {
color: var(--clr-text);
}
+/* === stadionzeitung-danke (Live-Seite) === */
+/* Dank an die Partner: oben die platzierte Anzeige (Motiv aus dem Seiteneintrag,
+ siehe stadionzeitung-danke.php), darunter Kopf, Logo-Wand und der QR auf
+ /partner-werden. Vier Logos pro Reihe in EINER festen Zellhöhe, damit
+ unterschiedlich hohe Logos auf einer Linie sitzen — dieselbe Idee wie die
+ feste Medienspalte auf der News-Seite. Maße in cqi (Selbst-Containment). */
+.stadionzeitung-danke {
+ display: flex;
+ flex-direction: column;
+ min-height: 100%;
+}
+
+.stadionzeitung-danke__anzeige {
+ width: 100%;
+ height: auto;
+ margin-bottom: 5cqi;
+ border: 1px solid var(--clr-glass-border);
+}
+
+.stadionzeitung-danke__head {
+ margin-bottom: 3.3cqi;
+}
+
+.stadionzeitung-danke__logos {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 3.3cqi 2.5cqi;
+ margin: 0 0 auto;
+ padding: 0;
+ list-style: none;
+}
+
+.stadionzeitung-danke__logo {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ /* Feste Zellhöhe: Logos sind teils quer, teils hoch — ohne sie tanzt die Reihe. */
+ height: 11cqi;
+}
+
+/* Logos liegen hier auf Weiß statt auf dem dunklen Website-Grund: contain statt
+ cover, damit nichts beschnitten wird, und keine Tönung — ein Sponsorenlogo
+ wird nicht umgefärbt. */
+.stadionzeitung-danke__logo img {
+ max-width: 100%;
+ max-height: 100%;
+ width: auto;
+ height: auto;
+ object-fit: contain;
+}
+
/* === stadionzeitung-momente (Live-Seite, Heft-Abschluss) === */
/* 2×2-Galerie der neuesten Instagram-Fotos + Folge-uns-Ticket — quadratische
Kacheln wie im Instagram-Raster, bewusst ohne Bildunterschriften: die
diff --git a/public/assets/img/anzeigen/emons-1141.jpg b/public/assets/img/anzeigen/emons-1141.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7db2ecd3b0e828b9c4cb42881e3d68e482e18040
GIT binary patch
literal 140145
zcmd42by!qu+c!M4fPmo8F-V7?bTfo>r*sIC(%m8|I&^nP*U;V4NH-##0@B^@V!Q8s
z?|r||@jTD_{q@1Ij+u3>Yn|5_zca3wyXm_{&?8w%8A%WV0s_bg_y^tHf&|1>B}CPg
zRQV~*Ok^v^A%ck(Hv9RhE#Vbme`{_>|JY#rTb-vj?RRB`-V1
z-4BpB2o)I_1sNF?1qB5a6%`E~8y)?@19UtrEDUUXJOToIJTRD$?^+N$UY6nE5~=}7zBhw#3ZzI^bCwloLt;IynOuP
zFC-+Tq-A8))HO6;YH913n7%eMx3IKwa&~cbbNBEJc>m#J;HS^9$f)R;*tqzF#Ei_W
z?3~=Z{DSg|%Bt#`+PeC-_Kwc3?w;OnqhsR}lT*{*XO@;%R@c@yer|3Z93CB?oSvOu
zT;9ut07CjL)?bqShg`UTT!_fXNXTgSav>nP0T&W3GRhM+R6J1?G-HRyRP6o_z+w?!
z%D$pgbExj)zj7SGAfVw~qCL15?GMTRcY?k9|0LO8g8f^r8PG!{1i*MmxF9HKTaazZ
ze>tkaaA8|#`5PB{CMMR>3lI+xezh-&*2b-PD)k-cYk=Pe`gYMIEZy6-ILIZw%P-dz
zH!{tFUMo3s;4#Kz_iezx`a$;LPnE8_1tso40jR*U?dU*awFpHF2MtqMa|qBd+V{`S
zEkF5|q1Qw_=PykvM=D3lF_Yb{ndNZrKx{*}ev4mQ+QQB?tD(na2awx`q}R+P)L@puh3xGOl8ZUJ>!1qeb1~XMer}#sB%1zOQd(`eJBh(Yp;QKye6n
zpeO(0E8Lr-%y_V7S@6mV>lS}{^)XE4IHO0VA`BpDVtg+E-XC(t-Qvfl{rk55l2IFb
z;|ah`pAx{`#%Jg~cT(5PEKop!G{3Fla;#!X$cJ;TspAL8CG)3T&?RllI}o5U`rj(c
z2tpYY|0i`ossDIo`$&QSGdb{|)FJytm3N>^Z1<=;5Gug?)QCj5-s(T8GXPS&&b{%_YW-#z`zTl|>78`SH+Y{(`Hj*y$(+Q1G6)u0NUE`1D^h
z7a;?4eKD8pcg_4ap27bik^V=9P6Dw8-aF8_rY7W=^f!k8P}#lVTmD3D19r5E(f`~F
zgQ3)k;~q7y|Nb&y$$yh(F3ku+{)d$Y#$3?%uvX~BBkrDYiYk-%HIz14e)s1KQZWPk!?ABsbVTn0lVkz-X&Xp
zBRa;HXxco*t|Q=f8L;P!uR8905M=}J(te8)^oaB~I`5TXc#oey?OWWTI%2A7P6`;K
z<;76w&c~1NZD1?iX?X6;4=g|C2raMjTK5MZe2D
z0CD%h-T2-@_fdt-Za)jSH_5$ENB5u$^84UQVZ=GegEXWASY-9yx+}LP{|buqKok?W
z4QT!sJ|8X~z>M!jQud5)gO)t~8gW
zuflgA+56G|A5detackmB{X23)_6v{q{xTK*Gz>kXGsgjlTAcrG1vFGL76v$;q~9*p
z!jgUnLq<=knM3Z9Z_3}I2Ox&;-wf42HhPlLlQqcm&*rdmjmy8=?^iwt5X=F`c=;cW
z@uxHXc8oqa7|3Y<>5Nj>OgDc+kgGb7gaA+c=K#=uP-ZUA2%@a(0%A_d-yo^-mskJx
za{U;vHp>6Vd0s{jE1;>u<9}lP?}T5zmib>u*Uee^>wh6#0=Ve6cy52g|Gjt%{|YF~
zWvm%tY(ATZfo%6-wREfna5`wW3IJT_L%^WFvl0uCK`Y)d*0mmgdBGPdb&LNotxB&Y4h9S{M4ln{Di9ux?r2Z(6jaEuxud^I}EsfDZcW9uU3`k{0FI
zCZYFn_dX~5tK_)P321;r{E0PJFvin>`1{2D?>q`HRs_B0@^|T#C=S$A{SH8`EBbe{
zYS>3^$iK^o0alAOvEQ!o-<2d6nVfaX7oq=S!69j}Cfa`=v;JOV+y{lyq3giEYP9Vm
zNs%uSXl%Xr!R+9^toof>0`~iqy8rBVpSic42A;tMp3ENZ@EW-Lb@K1VSQl=o{t#pN
zELYvGiCSZob(29bCfGb(CC+^n0CAhwnhFO0t95bCI||xtBDARUe$-KNnQT
zW$MN&z^A^6>XdInwa&PF6q_+3X)!?co|-e=NF|X&1dA$3Nr*9e7ePWugeqx##2zpD
z;aRRtHW>yhk}G6S1*n8PQzYmRQNC^*WoN)dz$y};-eEf`xBt!Zm6$$Xif4573
z=H4Y^GuCBt_O6ScjWj1Da>((Wyq1k
zV90p;$E9JD5eWnN#an91S2OV=P#%bkKs}jQcD5x?Ptx6JGrm0RxKe*RifzgdlOIVm
z?x&a4Q+3HsmkE-fvlu|Ct(?&t(~B7OXS2vV<5^KMkZM9CfJyR#2f8QU(&i9;Jmnkd6quzpS|#8>Y|F(HR{S&|tQw
z>F8`yvSD7HBTY>fB&L<|d1p0n(jSv=O;Z@He(NDiqt_y@=8x9!x(Jph_dV!NyLF8F
z0}~h)2^uWU=Z2@bBVbk740?l=WFz9%EAq_@wqFmx>?_x9bfrjnDX)Dqv`
zlk!Qy$jn>1$A^CNa5%>Fpt5|obI6g=S+Q>Wk%z-pdIPHq@uf3_%X+g$Dfe5j8MgMJ
z*rcrtm!pml;`{^)!i!f@I!VGOI^UeQ>Y>VhvgQ0f);=V(Xu_3F+Y;b9NwUk4Avjp&
z8zM}W;Y&IpCw67Dx<4gVFf0`2hA0nX7qa5#Ct*cS=p+WWxn~U!-nTLFGt_E@lZ}Kk
z38z*DQXF?W>W$QTjvK!{$t?C^z>G8(j<%)9s3>-k-qBZH#dA0=v~r_cYsXQM7{l+a
z!O;*A_C4^lC%~
z&}=UOOySrr9VH{mhxHP~khK8g8Agnfl;BFDN}BYaj-m=4*Mo%%qac=6zKZf8?F2oO
zW!RPy6+r+`~J&~?>|RjAck!Tat07U5y2_+IDu0|1dm=;=aVn=1FKXMNJc*V
zNG4)W+K=QfvYB*%oBkdHNpJ)Jf^YPHNm}9o0PJ>!uy@OKQSt1mYS$WsUoJ1wv#jY(AABt<1VUYryPu#HmJ3%htQ
zGf`o?x0xu>;d6=NU>n!m1cnz_n`u9ed*&|WybXNIy+9YOjV}==46d&@gqqq
z%C3;^$skJF0ph$Ghz<`#=jq!xPP2?z9}8^7=Te+n=$=4zl8>Vk+(K4H=310=^GeIs
zW~GL7A_-f{DhXvE!(mfe-&eo5ZirG-7``l^z{JJg!+wpB8snei9yu4MT*r~JYO7%~
zT9M)|G5wNsnZ=Q(#aZuJTtt|f=CL8J$8*^znKRJhSqXc?BwfS!ya%tX3`LcqzU`s*
z#^Jh#yw8HpL30Z&x5lwh?3Grc4HIUAHokJppbXra`FtZptS9Nykj{Sefy=K%tIfS9
zT5-uQMYG*&zl(xlU)GIlkZ%?|w{qhR1+(bYIU>h36b3kakomsGh0mMVEn|tXHX!Z=0D((GWFT4
z`anHk2RHroEH~MDJr2fseiePxj@pyHM{ixYzz4X|bz&R35>i+lZlFqqTBP9dm=WU-
zK)h`G9lx79>GFhbceaZ0$C5oq-^e%XJ=4R`-~*i&Sbvu1&}#(tdpCEO;Y^rzyfDekw=~GV^{8!7(nEU=X&lZ4Hp3jWDhA%%pTK%wbgKf;8z&=?RERH^Lb&pM
z8_b#vVr9Q6Z!AIHpd#`P##m%;fiSMdqf+lb?Sq<7ADq_qwzMvr9*Ogts(boV)?Y5^
z+zQ+<#P%U@QrTn_UF6%+9U%3Lh+tzSx=6{@4ii8mI9&c1B%wZJ93^l>eok@=vHfR`
zZKex?@$o(7x22biNqKW3T0Y=`aohB#!Z5Qf9u=eCtrGA4(f2)4kkP1Jmf)y{miQkz
z3MAk|x>>1=7~7KtL^$Q?OV!5%|A-=HKxHvNdHHXTse{`ZGDj|&a3vohXGQ6tONozrv*g`@EVKv{
zMMfFFQp99C<+WRKKKdqtS3SYCMq>Bbv~nWhP=mbLh~KQbQ6t}gwh7)Z&h$QfW4<~&
zEI9wjBgDd!nhj%v??-CNcI3oKr47MezY%gAjAKWxK=+{mLo
zB`UMz2gu>J-L0VTb5PEOR$|@A^cGVy77G$8ufRv&9aDqz;2A5;AFtRNW0#({bg@m=
zown3gjb?r%7W;Ui@0Hl7x^l(JV&y)eLtfV1=z{;P%wUu|?7L2s+4~^P)RA9bEayzw
z$$OOMHsH>_kjeR@&d|t*s0Ic6%Z*#i2~|Q4#1C$c$ouC|oWr+zv#g(ghEBftpdEK!
zVEHnQJ?3WeTaC#y4+xESKmZwMO*_^tebZh~KKE&zhLD0zO+xDPL&SjLsT#t8(l=Te
zh58HPrk`n&GpmPv6clOZdiG}Gu`|L(qXhlMLDZoW5rx*)IeoUNhIlm#^!}Q5TuA~l
zB*dmO=QIfOjeYu)Ka|}m6S5?*JIrD2xY}wVBP^*ImmzhZ8lAe}^mD=|->j#0a%QAQ
zxVzNihIr3P1k*I?VeTDjrCNg`bP0vqQ=W-}W}>w#ZcQG3Gu|UrRj78eTbk^fk+XWz
z^znzE7TSEQqsLv?%R#=X?R;vJ1zUJ5;iOtN)G=Hs>o++oUj7w!h_wY8G5RkmpDt{@
zF$sNR@2e_d+H@(omdR@}H52Jr)biScSTu?fdjh%o9Xsx)*Mn{>4DE%ey*4LYGMF1q
zu7%Y#hpcog@8&q>%WV?ppO;hiiiw+7Nms<$Q6h@`^kK?Ya7G?%v+=Z~t%^vq4+2}o
z<(OM{#XZb)MAC{qDr&b}K||xRmn>*w)Mfk7Ewr6dms?28^093FX{s$&$9o&6Yi8Yi
z$EU?3EE_)_bZL9~&)5CX7JFfIh$o|jC_V$%`aZ8OP4Hb~&hJB*)wW}~qQK|WiKV7j
zUF7UUD@hpcq?d`C84gsoWMMjo=U?+AuYfc9Scx1MtWDRtgo^8S$KA!#<7s;d?0;
zM5`sC(71N{%QX2|(~C{noD^!r3iU-q5*bZKkScN{&av-)<|%=rbi<|2VNfQcPS#9f
z;?trO5#5Uc-q|dNZrQQdvst!3j<>aIbX4p&2A&oPjQ4UEy-$d-@e2B4EwJWhKQLeM
zY&SHj;9an)u*@+RS6BF%&WEy#x9?XWI$xH(vgBJ;J_~IRmGEHLDt+;hZZymn_sgVR
z)l(GKGp}+W(CPiSJWuRM&HgabTAa4MknQlGRj81q=+`bSOyIMOm{Yfjf&NqRg7NwH
z3KS<9b5c$@?Nqf!QaZyYB^>+RFsjiKs<9`tr`;g7lM?Ar=cwIM8W-f_lJqY+9HDQE
zWM*KZl$~_@o|^5oTZ~^SB!4L*<>l<Tjt=*%>9P9r
zxlZ1j9|8V@0_yD%-LhV=C0SHO-T`(QeEvzB^%9bgAK5jrLg`$evs
z%p%;~@nH0IX4?kQNwHy3vyId@S2GFuwdj@M32iPAlH+p}$C<%g2c*hRFLXVawbiV~
z>NzUENb(Ih_dTk|b3OHJeCk~tZhH*pH1a@$9cE2x&KC7C7|q=`*G@%nI
zPpzVpO?e&{&_xzV`8{Sz-f;7iO|dnxw{N{+u1G+~Q&9jCqbdYgK?5I|1eg@cne|uN#%~`x}{Bisk5A
zU?m8Hkp~A(_sf;@SEs|cn%fl#kJqa-W|5R`6OOT6YnJR(C0
z2Bref|C)9)T;~(5%qk;}C}1*CG$0{7`;|bflxqqpYE9hIM~Ej{Evu%1#HE)&>K!jz1R#o?{3x0CQ3t-nf5f
z%OPgvH3YTW$9D#{*_tHCO~SMK;6`
zyQDjiSnkZXsYlXgw+P`Jh8&-Ro3es)X;MNX8CGM5SH87w(B>j*1X=kg=Y07TQ3FowNJm9LzT1;Q&Y61lcViADsQt{ct9UTTfI=$o670f
zlw%?D_L<|v$#S&lwi&KAR`-=AXa4!Zs?l$dRLk=V&%6@0(W=6qT*zL$_{crfiozOa
z#c}NsWp!Sk5;iD#k~jmukUJQ_F|IcmN`E-nb*RvA+sCD?zYiHHyXu~-+~F$puzgh}
zi6^g)2fliVxlAspFDW4K*)2P4i#F84-LTAugo8p32BYlx#rr-Tz>gh{UC=Pa@?wx
zwjAiMp~#undj~3J)*!2&6OX7^5BiXBU2YUEo1#l@xi}m1&?r3i7eRBV9n1iQ3GI|q
z;m0Qw`fHXjWU06HBqq1Dq0Fz}?UH@ED1#TFRtesLUYFdQNc&kZt8||Ns{#ue
zZpJ+gDe>jCLezHOG8^74&(9_Gm8AA*sJT>trLki|Idro;+Zx9&V;T%_@haA}=(s!Vo=%I59{GH5J=uWnS
zMjPL5AK$b$3%t87dyJKsJK`|gY|b6WP+IV(a~)7IRz&C*`VPOzycH$*eioVNXW?p4
zFO+vFr(IOfsHI@&>{v~KeL(wGw|b0@J-0v8#)X2=4v$>Y($4v+N*fKy95LE=_{&>*
z>8ha{S5Ekqn5mH^clytiZ}iMKtIMqErNhA#_c?KW`XNoD()%{}gT=rh1;$uoTiQnl{y-XV0vhF0uA
z(Y^qB2?~Dw=1se2T(+x5#AcvOGoWj_)Ow9QXdTy@-~NbO{Q80-WS`0K+@c|78x`0K
z@=Qu_POwdzi$t2S)|qpJjjRcT2ipb-_Gig?&ix0^}7;d5#W=hmVFvY13awT
zbE4Zss%-zlf$VBhubE(SO|_6{y>X|-!bb^>ki`9(>Djr3kCtEGI>3|nxNIE9xUf`a
zNkCPW1P1KE7UoG_%M$V=Q3W`R<#x{FCqUV3W#`iV8rTE=_;I4;Cw1+E5_21Xk@ZinlOajxZ`LZ7JA>|hp_9~9_388mmN|24Am`^^npII^5|F^f<`xYEj*9
zf4F|?2Gt0%k07&@(S0Z+e6=)R<>S({_SP1{hSjMtA&hSor-A~r2$a9Ru^xmj`B}5{
zOnosl$_T$5=C57cKJZ(hN-GU1YMYmGaoTs&j(r|6@TQ<;XR*vrSlO|rGn>6IhPWxBF{noN>c07Ug$iAaBVQGCuBai7$x{
z>kg`z(oUl2%8Xix=|cdl&=&?L#PonDReus)HkP^hdZ^6NV9`>l32v+fE>Iyld8mlG-ZiJ8m51@N^X>9~A2y?J3?G9|{-Tb{QOE
zDE%_N0|_y&LC^JHq7_%UDiv?ppHDzb(Cdj))r?W;Z-H^BnZYLJ~8
z7rc_Q#kC8KIDwurcC#bM*3<1>Sg`FFK81vw0efzBkdSBBtQQ9X@HyOHmU@I=PIyN$
zAf9$@DK{ZF*C(Cc84JWgWGQBQ(7n;H5Nu%M?r~5fQd*1Hn?etWCZIjWO~z{pG%@n*
z5|(qe1J9w3-5Hv*J=)=m3`?A6I|c(pAc8(T{`IIR{Ma30DKWquw9wD+$r5S4`l_Oz
z!9#BRN~|1cLR(^#1h!P)d6D%ey7Wu!ZcMA&JS@A}ml}VmDi*d3389uOcLCj4=0`3%
z`WmCHt1^Bsj@dDK^YPQG!rts35g#P+tt|9D5?EqcD`c`Sb~)WJRo3ergiH^W=47#I
zs^H9|C~^-r#sF!;(V<&K!*BLO1n{9}1gmP3?>;C`bDad|{q5sfn;ot4)~l1VVlfT7QGn2@HE
z>oa(^CO?bjGD<|x^%A_6pJFM2NW)S&jJ*e!RJ53tl0|d)n%`N<^jrbuGZ>{8p@&lq
zp*6cV=lQGzLZ~GJ8W?o=LxHo|l3fAZs^1lIo|Pq(hcrYMwbFv?a7}Q(GA<76-kvQ(
z&shA92{YiS|f-}eTT6U@{$m{QWObBe9ba77EZ&E(#_wZgk@
z2~n=Chn*}1(IyEpC7U(-HrQs?C1<+wZ@*@3|47IUsgA-s!w<~ct|2mH
zA+jO2oG~lyR&s4l2;dRjl$uzN8BR$U7Pak6k!ARB$)$P4+Pg-N^OUT3{sb%K7lXQl
z{UPcqBUeS1SPg%w_YjNHQza)A9)?{3=O?jU4kjoa5oN4yk31&|p;(3rmJy9Kp`^y-
z*6D9A2t#ORP=m5es>B_ZO~(vm@MMr&4JO*kI6SE$<(yQ#zE@69cEZJFDvhHxyTbBx
zbS-%ZtZwx}1JgW@K6@KKsUd0WqrJ=$DRYPR)u`H{%teRU=*qh~s
zv_N6(((xztgkwr%1P&J;qMB@cf?6#QZQL
zxPRK8q83ibVt0R=c7CCEl{nkDEX`p3%mN0GpwNH}1@xb2d@lg5p+dEMA-ug*J
zST<4Sb_~*uLhHrNI$P!BkB?2=wN{>5R{bs9>7tjDa7fHE_&D>8V(Y1&N2K$Px?jIU
z3%}i|FC54WsMp&j#~E>xTUhqKNEKK8e!q6qZr|ZtjNBv@nBR&$^r_GVt6k2CwM;xj
zJ8Ws}<~PXlTo4utp&Wg7bO(YQ`YmWwDx!cQt$eGLt>4>2zTF;CTAXD$QI&bn<7b+O$2dpr1d@uxt*xpi5w8=tm7^(lJ{MqjCCJTHfxL
zTyom+rh@`MODpu?hSpZiVF03&W`74#--qerjxG
zChitD`3}_gYrSTKI0-j6wKcTLR)|nN#f>HSkiOta_DOxp!`JN|roOaxnvUk}rer<^
zc3si#_A9r-S;ZO4gNOY#iCq>=a}Nq45z+c
{h9x9e7t~`j9T(mlJ}i
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