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

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