Stadionzeitung Ausgabe 2 (TSV Ködnitz, 15.08.): Entwurf + neue Aufruf-Seite

- Neue generische Live-Seite `aufruf` (erste Nutzung: Sportfest-Helferaufruf):
  Komponente, Dispatcher, Skript-Einbindung (Planung im Aufschlag, QR aus
  aufruf.link, Feld-Carryover), CSS, optionales kleines Poster rechts.
- Ausgabe 2026-08-15-tsv-koednitz als ENTWURF: Vorwort (Benedikt, Revanche
  dezent), Interview (Ali, 3 Fragen), Sportfest-Helferseite mit Kontakten aus
  ansprechpartner.json; Rückseite/Cover wie Hutschdorf wiederverwendet.
- Interview-Frage größer/prominenter gesetzt.
- Sportfest-Plakat-Anzeige deaktiviert (redundant zur Helferseite) -> 40 Seiten.

Sync-Dateien (instagram/matchcenter) bleiben gitignored, Snapshot wird beim
Veröffentlichen auf dem Server eingefroren.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFnFYLm2MoBskyk9mc84ZW
This commit is contained in:
2026-08-13 17:27:41 +02:00
parent 1839632b66
commit c688d155cc
17 changed files with 771 additions and 9 deletions

View File

@@ -547,7 +547,7 @@ 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` | `tabelle` | `termine` | `news` | `ergebnisse` | `vorschau` | `kontakt` | `mitglied` | `historie` | `sportheim` | `partner` | `momente` | `impressum`, kein Bild;
`vorwort` | `interview` | `aufruf` | `tabelle` | `termine` | `news` | `ergebnisse` | `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
wird jede Seite (Bild wie Live) über `app/components/stadionzeitung-seite.php`** — die eine

View File

@@ -0,0 +1,155 @@
<?php
declare(strict_types=1);
/**
* Aufruf-Seite der Stadionzeitung (Live-Seite) — eine redaktionelle Content-
* Seite für einen Handlungsaufruf, zuerst gebaut für den Helferaufruf zum
* Sportfest, bewusst GENERISCH gehalten (nicht `sportfest`), damit sie später
* auch für andere Feste (Weihnachtsfeier o. Ä.) taugt.
*
* Der Inhalt ist KEIN Skript-Input: er wird wie Vorwort/Interview im Chat
* gepflegt und lebt im `aufruf`-Feld der Ausgabe in data/stadionzeitung.json:
* kicker kleine Zeile über der Überschrift (z. B. „Sportfest 2026")
* heading Überschrift der Seite
* intro Vorspann (Lese-Serif, wie Vorwort/Interview) — String ODER
* Liste von Zeilen (mehrere Zeilen = ein Absatz mit <br>)
* tasks_title Überschrift über der Aufgaben-Liste (optional)
* tasks[] wobei Hilfe gebraucht wird
* contacts_intro Zeile über den Ansprechpartnern (optional)
* contacts[] NAMEN — Nummer/Foto zieht die Seite per Namensabgleich aus
* data/ansprechpartner.json (geteilte Quelle, nie duplizieren;
* wie die Kontakt-Seite bewusst live statt Snapshot: ein Fest
* betrifft ohnehin die Zukunft, alte Nummern nützen niemandem)
* link interner Slug fürs Ziel (z. B. „veranstaltungen/sportfest-2026"),
* liefert Link-Zeile + QR im CTA-Fuß
* poster optional, kleines Plakat rechts neben Intro + Aufgaben
* (Varianten-Set {base,widths,alt} oder {src,…}) — z. B. das
* Sportfest-Vorderplakat aus data/veranstaltungen.json
* bin/stadionzeitung-add.php trägt das Feld über erneute Läufe weiter (wie
* `vorwort`/`interview`), plant die Seite nur ein wenn das Feld existiert und
* erzeugt den QR (qr-aufruf.png im Ausgaben-Ordner) aus `link`.
*
* Props: $issue (Eintrag aus data/stadionzeitung.json)
*/
$issue = $issue ?? [];
$aufruf = $issue['aufruf'] ?? null;
if (!is_array($aufruf)) {
return;
}
$slug = (string) ($issue['slug'] ?? '');
$heading = trim((string) ($aufruf['heading'] ?? 'Mach mit'));
$kicker = trim((string) ($aufruf['kicker'] ?? 'Mitmachen'));
// intro darf ein String ODER eine Liste von Zeilen sein — mehrere Zeilen
// werden als EIN Absatz mit weichen Umbrüchen (<br>) gesetzt, damit ein kurzer
// Aufruf bewusst zeilenweise stehen kann statt als Fließblock.
$introLines = array_values(array_filter(
array_map(static fn ($l): string => trim((string) $l), (array) ($aufruf['intro'] ?? [])),
static fn (string $l): bool => $l !== ''
));
$tasksTitle = trim((string) ($aufruf['tasks_title'] ?? ''));
$tasks = array_values(array_filter(
array_map(static fn ($t): string => trim((string) $t), $aufruf['tasks'] ?? []),
static fn (string $t): bool => $t !== ''
));
$contactsIntro = trim((string) ($aufruf['contacts_intro'] ?? ''));
$link = trim((string) ($aufruf['link'] ?? ''));
$ctaLabel = trim((string) ($aufruf['cta_label'] ?? 'Alle Infos'));
$ctaTitle = trim((string) ($aufruf['cta_title'] ?? ''));
// Kontakte per Namensabgleich aus der geteilten Quelle auflösen (Nummer/Foto),
// die Reihenfolge folgt dem `contacts`-Feld. Ein unbekannter Name bleibt als
// reiner Name stehen (kein Fehler), damit ein Tippfehler die Seite nicht kippt.
$byName = [];
foreach (json_load('ansprechpartner')['persons'] ?? [] as $person) {
$name = (string) ($person['name'] ?? '');
if ($name !== '') {
$byName[$name] = $person;
}
}
$contacts = [];
foreach ($aufruf['contacts'] ?? [] as $name) {
$name = trim((string) $name);
if ($name === '') {
continue;
}
$contacts[] = $byName[$name] ?? ['name' => $name];
}
$qr = ($slug !== '' && is_file(PUBLIC_PATH . "/assets/img/stadionzeitung/{$slug}/qr-aufruf.png"))
? "img/stadionzeitung/{$slug}/qr-aufruf.png"
: null;
$linkLabel = $link !== '' ? club_domain($link) : '';
// Optionales kleines Plakat rechts neben Intro + Aufgaben (z. B. das Sportfest-
// Vorderplakat) — Varianten-Set {base,widths,alt} oder Einzeldatei {src,…}.
$poster = $aufruf['poster'] ?? null;
if (!is_array($poster) || (empty($poster['base']) && empty($poster['src']))) {
$poster = null;
}
?>
<div class="stadionzeitung-aufruf">
<header class="stadionzeitung-page__head">
<p class="stadionzeitung-page__kicker"><?= e($kicker) ?></p>
<h2 class="stadionzeitung-page__heading"><?= e($heading) ?></h2>
</header>
<span class="stadionzeitung-page__rule" aria-hidden="true"></span>
<div class="stadionzeitung-aufruf__cols<?= $poster ? ' stadionzeitung-aufruf__cols--poster' : '' ?>">
<div class="stadionzeitung-aufruf__col-main">
<?php if ($introLines !== []): ?>
<p class="stadionzeitung-aufruf__intro"><?= implode('<br>', array_map('e', $introLines)) ?></p>
<?php endif; ?>
<?php if ($tasks !== []): ?>
<div class="stadionzeitung-aufruf__tasks">
<?php if ($tasksTitle !== ''): ?>
<p class="stadionzeitung-aufruf__tasks-title"><?= e($tasksTitle) ?></p>
<?php endif; ?>
<ul class="stadionzeitung-aufruf__task-list">
<?php foreach ($tasks as $task): ?>
<li class="stadionzeitung-aufruf__task"><?= icon('check2', 'stadionzeitung-aufruf__task-icon') ?><span><?= e($task) ?></span></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</div>
<?php if ($poster): ?>
<?php component('img', ['image' => $poster, 'sizes' => '150px', 'class' => 'stadionzeitung-aufruf__poster']); ?>
<?php endif; ?>
</div>
<?php if ($contacts !== []): ?>
<div class="stadionzeitung-aufruf__contacts">
<?php if ($contactsIntro !== ''): ?>
<p class="stadionzeitung-aufruf__contacts-intro"><?= e($contactsIntro) ?></p>
<?php endif; ?>
<ul class="stadionzeitung-aufruf__contact-list">
<?php foreach ($contacts as $contact): ?>
<li class="stadionzeitung-aufruf__contact">
<span class="stadionzeitung-aufruf__contact-name"><?= e((string) ($contact['name'] ?? '')) ?></span>
<?php if (!empty($contact['phone'])): ?>
<span class="stadionzeitung-aufruf__contact-phone"><?= icon('telephone', 'stadionzeitung-aufruf__contact-icon') ?><?= e((string) $contact['phone']) ?></span>
<?php elseif (!empty($contact['email'])): ?>
<span class="stadionzeitung-aufruf__contact-phone"><?= icon('envelope', 'stadionzeitung-aufruf__contact-icon') ?><?= e((string) $contact['email']) ?></span>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php if ($linkLabel !== ''): ?>
<div class="stadionzeitung-page__cta">
<div class="stadionzeitung-page__cta-text">
<p class="stadionzeitung-page__cta-label"><?= e($ctaLabel) ?></p>
<?php if ($ctaTitle !== ''): ?>
<p class="stadionzeitung-page__cta-title"><?= e($ctaTitle) ?></p>
<?php endif; ?>
<p class="stadionzeitung-page__cta-link"><?= e($linkLabel) ?></p>
</div>
<?php if ($qr !== null): ?>
<div class="stadionzeitung-page__cta-abriss">
<img class="stadionzeitung-page__cta-qr" src="<?= e(asset($qr)) ?>" alt="" width="60" height="60" loading="lazy" decoding="async">
<span class="stadionzeitung-page__cta-scan">Scan mich</span>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>

View File

@@ -9,7 +9,7 @@ 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|tabelle|termine|
* type 'live' (page: cover|vorwort|interview|aufruf|tabelle|termine|
* news|ergebnisse|vorschau|kontakt; tabelle optional
* mit team: Team-Key aus matchcenter.json),
* type 'leer' (weiße Füllseite, nur Druckfassung — hält die
@@ -46,6 +46,9 @@ $type = $entry['type'] ?? 'image';
<?php case 'interview': ?>
<?php component('stadionzeitung-interview', ['issue' => $issue]); ?>
<?php break; ?>
<?php case 'aufruf': ?>
<?php component('stadionzeitung-aufruf', ['issue' => $issue]); ?>
<?php break; ?>
<?php case 'termine': ?>
<?php component('stadionzeitung-termine', ['issue' => $issue]); ?>
<?php break; ?>

View File

@@ -51,6 +51,13 @@ declare(strict_types=1);
* die Ausgabe die Live-Seite app/components/stadionzeitung-vorwort.php statt des
* Fallback-Bilds `vorwort.*`.
*
* Interview (`interview`-Feld) und die generische Aufruf-Seite (`aufruf`-Feld,
* z. B. Sportfest-Helferaufruf) sind wie das Vorwort chat-gepflegt: das Skript
* trägt sie über erneute Läufe weiter und plant die Live-Seite nur ein, wenn
* das Feld existiert. Reihenfolge im anzeigenfreien Aufschlag: Vorwort →
* Interview → Aufruf. Für die Aufruf-Seite erzeugt das Skript zusätzlich den
* QR (qr-aufruf.png im Ausgaben-Ordner) aus `aufruf.link`.
*
* Erneuter Aufruf mit demselben Slug ersetzt die Ausgabe vollständig (alte
* Seiten-Bilder werden vorher gelöscht) — idempotent für Korrekturen.
*/
@@ -179,6 +186,8 @@ foreach ($existing['issues'] ?? [] as $entry) {
$oldVorwort = $oldIssue['vorwort'] ?? null;
// Interview wie das Vorwort chat-gepflegt — überlebt erneute Läufe ebenso.
$oldInterview = $oldIssue['interview'] ?? null;
// Aufruf-Seite (z. B. Sportfest-Helferaufruf) ebenso chat-gepflegt.
$oldAufruf = $oldIssue['aufruf'] ?? null;
// --- Ziel-Ordner vorbereiten: bei erneutem Lauf alte Seiten dieser Ausgabe wegräumen ---
$destDir = PUBLIC_PATH . "/assets/img/stadionzeitung/{$slug}";
@@ -289,6 +298,12 @@ try {
if ($oldInterview !== null) {
$aufschlag[] = $live('interview');
}
// Aufruf-Seite (Helferaufruf o. Ä.) reiht sich in denselben anzeigenfreien
// Aufschlag ein, direkt hinter Vorwort/Interview — ein redaktioneller
// Einstieg ohne Werbung dazwischen (chat-gepflegtes `aufruf`-Feld).
if ($oldAufruf !== null) {
$aufschlag[] = $live('aufruf');
}
if ($aufschlag !== []) {
$bloecke[] = ['name' => 'aufschlag', 'seiten' => $aufschlag, 'anzeigen_danach' => true];
}
@@ -553,6 +568,20 @@ foreach (['mitmachen', 'news', 'termine', 'matchcenter', 'mitglied-werden', 'his
}
}
// Aufruf-Seite: QR auf das interne Ziel (z. B. /veranstaltungen/sportfest-2026)
// aus dem chat-gepflegten `aufruf.link`-Feld. Anders als die geteilten CTA-QRs
// ist das Ziel ausgabenspezifisch, deshalb liegt der Code im Ausgaben-Ordner
// (qr-aufruf.png) — der wird bei jedem Lauf ohnehin neu befüllt. club_url()
// wie überall, damit der gedruckte Code auf die echte Domain zeigt.
$aufrufLink = trim((string) ($oldAufruf['link'] ?? ''));
if ($aufrufLink !== '') {
if (generate_qr_png(club_url($aufrufLink), "{$destDir}/qr-aufruf.png")) {
$log("QR-Code für die Aufruf-Seite (/{$aufrufLink}) erzeugt.");
} else {
$log('QR-Code für die Aufruf-Seite konnte nicht erzeugt werden — die Seite rendert ohne QR.', 'WARN');
}
}
// Momente-Seite: QR aufs Instagram-Profil (externe URL aus club.json, die
// einzige Nicht-Website-Adresse unter den Heft-QRs — ein gedruckter Code,
// keine Einbindung, Regel 1 bleibt unberührt).
@@ -620,6 +649,7 @@ $issues[] = [
'qr_image' => $qrImage,
'vorwort' => $oldVorwort,
'interview' => $oldInterview,
'aufruf' => $oldAufruf,
'verfasser_photo' => $verfasserPhoto,
'termine_snapshot' => $termineSnapshot,
'images' => $images,

View File

@@ -225,7 +225,7 @@
{
"slug": "sportfest-2026-plakat",
"sponsor": "Sportfest 2026",
"aktiv": true,
"aktiv": false,
"bis": "2026-09-06",
"alt": "Eigenanzeige: Sportfest 2026 vom 4. bis 6. September auf unserer Sportanlage",
"base": "img/anzeigen/sportfest-2026-plakat",

View File

@@ -1,5 +1,448 @@
{
"issues": [
{
"slug": "2026-08-15-tsv-koednitz",
"opponent": "TSV Ködnitz",
"date": "2026-08-15",
"team_key": "erste-mannschaft",
"status": "entwurf",
"pages": 40,
"player_image": {
"base": "img/stadionzeitung/2026-08-15-tsv-koednitz/spieler",
"widths": [
320,
640,
1000
],
"ext": "png",
"alt": ""
},
"qr_image": "img/stadionzeitung/2026-08-15-tsv-koednitz/qr.png",
"vorwort": {
"heading": "Auf ein Neues im Katzbachtal!",
"paragraphs": [
"Liebe Fußballfreunde,",
"schön, dass ihr heute wieder den Weg ins Katzbachtal gefunden habt. Ein herzlicher Gruß gilt dem Schiedsrichtergespann und unseren Gästen vom TSV Ködnitz.",
"Auch wenn die Urlaubszeit den einen oder anderen Ausfall mit sich bringt, zählt heute nur der Dreier. Mit Ködnitz haben wir zudem noch eine Rechnung aus der Vorsaison offen.",
"Packen wir es gemeinsam an. Wille, Leidenschaft, Heimsieg: los geht's!"
],
"closing": "Rot-schwarze Grüße",
"signee_name": "Benedikt Höfner",
"signee_role": "Sportlicher Leiter"
},
"interview": {
"teaser": "Unser Trainer Ali Kilic über das spielfreie Wochenende und die Aufgabe gegen Ködnitz",
"heading": "Drei Fragen an Ali Kilic",
"person_name": "Ali Kilic",
"person_role": "Trainer 1. Mannschaft",
"intro": null,
"fragen": [
{
"frage": "Ali, wie war es, durch die Absage von Wonsees ein Wochenende spielfrei zu sein?",
"antwort": "Natürlich will man immer spielen. Aber wir haben die Zeit genutzt und in einer intensiven Einheit weiter an unserer Fitness und unserer Spielidee gefeilt."
},
{
"frage": "Was erwartest du heute von Ködnitz?",
"antwort": "Grundsätzlich ein schwierig zu bespielender Gegner. Sie sind sich nicht zu schade, defensiv kompakt zu stehen und in Kontersituationen für Gefahr zu sorgen. Mit Riediger haben sie vorne einen schnellen und gefährlichen Abschlussspieler, und auch in der Abwehr sind sie mit körperlicher Präsenz da."
},
{
"frage": "Was können wir aus der Niederlage gegen Hutschdorf lernen?",
"antwort": "Die klare Lehre ist, dass wir unsere eigenen Fehler gerade im Ballbesitz kleinhalten müssen, damit wir den Gegner nicht zu einfachen Torchancen einladen. Gegen Hutschdorf sind die meisten ihrer Chancen nach Unaufmerksamkeiten und Fehlpässen im Aufbau gefallen. Fußball ist eben ein Fehlersport, ganz vermeiden lassen sie sich nie. Entscheidend ist, dass wir als Team besser damit umgehen: uns gegenseitig helfen und nach einem Ballverlust entweder sofort sauber ins Gegenpressing kommen oder uns kompakt als Team fallen lassen."
}
],
"photo": "img/persons/ali-kilic.jpg"
},
"aufruf": {
"kicker": "Sportfest 2026",
"heading": "Helfende Hände gesucht",
"intro": [
"Sportfest vom 4. bis 6. September.",
"Drei Tage Fußball, Essen und Feiern.",
"Dafür brauchen wir dich."
],
"tasks_title": "Wobei wir Hilfe brauchen",
"tasks": [
"Grillen",
"Kaffee, Kuchen und Getränke verkaufen",
"Pizza backen und verkaufen",
"Kuchen backen und mitbringen"
],
"contacts_intro": "Du willst mit anpacken? Melde dich bei einem von uns:",
"contacts": [
"Sam Kern",
"Kerstin Tauber",
"Fabian Früh"
],
"link": "veranstaltungen/sportfest-2026",
"cta_label": "Alle Infos",
"cta_title": "Alles zum Sportfest",
"poster": {
"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"
}
},
"verfasser_photo": {
"base": "img/stadionzeitung/2026-08-15-tsv-koednitz/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": [],
"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": []
}
}
],
"images": [
{
"type": "live",
"page": "cover"
},
{
"type": "live",
"page": "vorwort"
},
{
"type": "live",
"page": "interview"
},
{
"type": "live",
"page": "aufruf"
},
{
"type": "image",
"base": "img/anzeigen/eldorado",
"widths": [
480,
960,
1440
],
"alt": "Anzeige: eldorado Radsporttechnik Kulmbach"
},
{
"type": "image",
"base": "img/anzeigen/egu",
"widths": [
480,
960,
1440
],
"alt": "Anzeige: Erfrischungsgetränke-Union Kulmbacher Gruppe GmbH"
},
{
"type": "live",
"page": "ergebnisse"
},
{
"type": "image",
"base": "img/anzeigen/hofmann-graef",
"widths": [
480,
960,
1440
],
"alt": "Anzeige: Buchhandlung Hofmann, Kulmbach, und Automobilwerkstatt Gräf, Thurnau"
},
{
"type": "image",
"base": "img/anzeigen/muench-richterstegner",
"widths": [
480,
960,
1440
],
"alt": "Anzeige: Planungsbüro Münch und Richter & Stegner Steuerungstechnik"
},
{
"type": "live",
"page": "tabelle",
"team": "erste-mannschaft"
},
{
"type": "live",
"page": "tabelle",
"team": "zweite-mannschaft"
},
{
"type": "live",
"page": "tabelle",
"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": "vorschau"
},
{
"type": "image",
"base": "img/anzeigen/karl-geyer",
"widths": [
480,
960,
1440
],
"alt": "Anzeige: Geyer Farbe + Raum, Kulmbach, Starthilfe Boden"
},
{
"type": "image",
"base": "img/anzeigen/gsb",
"widths": [
480,
960,
1440
],
"alt": "Anzeige: GSB Security und GSB Gebäude Service, Bindlach"
},
{
"type": "live",
"page": "termine"
},
{
"type": "image",
"base": "img/anzeigen/physiofit-wohlfart",
"widths": [
480,
960,
1440
],
"alt": "Anzeige: Physio Fit Neudrossenfeld und Textilpflege Wohlfart, Kulmbach"
},
{
"type": "live",
"page": "news"
},
{
"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": "kontakt"
},
{
"type": "image",
"base": "img/anzeigen/emons-stuebinger",
"widths": [
480,
960,
1440
],
"alt": "Anzeige: Emons Spedition und AST Stübinger Hoch-, Tief- und Straßenbau, Kulmbach"
},
{
"type": "image",
"base": "img/anzeigen/mattis-ka2",
"widths": [
480,
960,
1440
],
"alt": "Anzeige: Karl Mattis Flaschnerei und KA2 Fitness, Kulmbach"
},
{
"type": "live",
"page": "mitglied"
},
{
"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": "historie"
},
{
"type": "image",
"base": "img/anzeigen/trapper-verkehrsakademie",
"widths": [
480,
960,
1440
],
"alt": "Anzeige: Trapper GmbH Entsorgungsfachbetrieb und Verkehrsakademie, Kulmbach"
},
{
"type": "live",
"page": "sportheim"
},
{
"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": "partner"
},
{
"type": "image",
"base": "img/anzeigen/elma-kleinheinz",
"widths": [
480,
960,
1440
],
"alt": "Anzeige: elma GmbH Bayreuth und Metzgerei Kleinheinz, Kulmbach"
},
{
"type": "image",
"base": "img/anzeigen/stadionzeitung-digital",
"widths": [
480,
960,
1440
],
"alt": "Eigenanzeige: Unsere Stadionzeitung gibt es jetzt auch online, mit QR-Code zum Scannen"
},
{
"type": "live",
"page": "momente"
},
{
"type": "image",
"base": "img/anzeigen/sportfest-2026-programm",
"widths": [
480,
960,
1440
],
"alt": "Eigenanzeige: Programm des Sportfests 2026 mit allen Spielen, Turnieren und der Kinderolympiade"
},
{
"type": "live",
"page": "impressum"
},
{
"type": "image",
"base": "img/stadionzeitung/2026-08-15-tsv-koednitz/rueckseite",
"widths": [
480,
960,
1410
],
"alt": "Rückseite der Stadionzeitung gegen TSV Ködnitz"
}
]
},
{
"slug": "2026-08-01-sv-hutschdorf",
"opponent": "SV Hutschdorf",

View File

@@ -5836,7 +5836,8 @@ a.match-row__link:hover {
.stadionzeitung-kontakt,
.stadionzeitung-partner,
.stadionzeitung-momente,
.stadionzeitung-historie {
.stadionzeitung-historie,
.stadionzeitung-aufruf {
display: flex;
flex-direction: column;
min-height: 100%;
@@ -5847,7 +5848,8 @@ a.match-row__link:hover {
.stadionzeitung-kontakt .stadionzeitung-page__cta,
.stadionzeitung-partner .stadionzeitung-page__cta,
.stadionzeitung-momente .stadionzeitung-page__cta,
.stadionzeitung-historie .stadionzeitung-page__cta {
.stadionzeitung-historie .stadionzeitung-page__cta,
.stadionzeitung-aufruf .stadionzeitung-page__cta {
margin-top: auto;
}
@@ -7189,12 +7191,16 @@ a.news-feature__link:hover .news-feature__more {
margin-bottom: 0;
}
/* Frage deutlich größer als die Antwort, damit sie klar als Frage vorne steht
(vorher gleich groß wie die Antwort, dadurch zu leise — Felix). Abel hat nur
einen fetten Schnitt, die Auszeichnung trägt also die GRÖSSE, nicht ein
stärkeres Bold. Rot scheidet als Textfarbe aus (Kontrast auf Weiß < 4,5:1). */
.stadionzeitung-interview__frage {
margin-bottom: 0.8cqi;
margin-bottom: 1.4cqi;
font-family: var(--ff-body);
font-weight: var(--fw-bold);
font-size: 3.4cqi;
line-height: 1.3;
font-size: 4.6cqi;
line-height: 1.2;
}
.stadionzeitung-interview__antwort {
@@ -7203,6 +7209,131 @@ a.news-feature__link:hover .news-feature__more {
line-height: var(--lh-body);
}
/* === stadionzeitung-aufruf (Live-Seite) === */
/* Redaktioneller Handlungsaufruf (zuerst der Sportfest-Helferaufruf): Kopf-
Muster + Trennlinie wie die Geschwister-Seiten, Vorspann in der Lese-Serif
(wie Vorwort/Interview), darunter die Aufgaben-Liste und die Ansprechpartner
(Namen + Nummern aus der geteilten Quelle), unten der geteilte CTA-Fuß mit
QR aufs Ziel. Inhalt chat-gepflegt (aufruf-Feld der Ausgabe). */
/* Intro + Aufgaben stehen normalerweise gestapelt (ohne Poster ist __cols ein
schlichter Block, keine Layout-Änderung). Trägt die Ausgabe ein Poster,
wird __cols zur Reihe: Text links, kleines Plakat rechts. */
.stadionzeitung-aufruf__cols--poster {
display: flex;
align-items: flex-start;
gap: 4.2cqi;
}
.stadionzeitung-aufruf__cols--poster .stadionzeitung-aufruf__col-main {
flex: 1;
min-width: 0;
}
/* Kleines Plakat rechts — Hochformat-Flyer, bewusst klein („weißt du was ich
meine"): ein visueller Anker fürs Fest neben dem Aufruf, keine zweite große
Anzeige. Feiner Rahmen wie ein aufgehängter Zettel, kein Schatten (druckt
zuverlässiger, siehe Cover-Kommentar). */
.stadionzeitung-aufruf__poster {
flex: none;
width: 30cqi;
height: auto;
border: 1px solid var(--clr-glass-border);
}
.stadionzeitung-aufruf__intro {
margin-bottom: 4.2cqi;
font-family: var(--ff-serif);
font-size: 3.6cqi;
line-height: var(--lh-body);
}
/* Im Zweispalter trägt die Aufgabenliste die Unterkante der linken Spalte —
kein Abstand mehr darunter, sonst steht das Plakat höher als der Text endet. */
.stadionzeitung-aufruf__cols--poster .stadionzeitung-aufruf__task-list {
margin-bottom: 0;
}
.stadionzeitung-aufruf__tasks-title,
.stadionzeitung-aufruf__contacts-intro {
margin-bottom: 1.6cqi;
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-aufruf__task-list {
margin: 0 0 4.2cqi;
padding: 0;
list-style: none;
}
.stadionzeitung-aufruf__task {
display: flex;
align-items: baseline;
gap: 2.2cqi;
padding-block: 1.9cqi;
font-size: 3.8cqi;
line-height: 1.3;
border-bottom: 1px solid var(--clr-glass-border);
}
.stadionzeitung-aufruf__task:last-child {
border-bottom: 0;
}
/* Icon in Akzentrot als Fläche/Glyph (nie roter Fließtext, Kontrastregel):
das Häkchen trägt Farbe, der Text bleibt schwarz. */
.stadionzeitung-aufruf__task-icon {
flex: none;
font-size: 3.4cqi;
color: var(--clr-accent);
transform: translateY(0.3cqi);
}
.stadionzeitung-aufruf__contact-list {
margin: 0;
padding: 0;
list-style: none;
}
.stadionzeitung-aufruf__contact {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 3.3cqi;
padding-block: 2.2cqi;
border-bottom: 1px solid var(--clr-glass-border);
}
.stadionzeitung-aufruf__contact:last-child {
border-bottom: 0;
}
.stadionzeitung-aufruf__contact-name {
font-family: var(--ff-body);
font-weight: var(--fw-bold);
font-size: 3.8cqi;
line-height: 1.25;
}
.stadionzeitung-aufruf__contact-phone {
display: inline-flex;
align-items: center;
gap: 1.4cqi;
flex: none;
font-size: 3.1cqi;
color: var(--clr-text-muted);
white-space: nowrap;
}
.stadionzeitung-aufruf__contact-icon {
font-size: 2.9cqi;
color: var(--clr-accent);
}
/* === 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 —

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB