Stadionzeitung, Veranstaltungen, News-Artikel, Lightbox und Anzeigen-Verteilung
Stadionzeitung: Live-Seiten (Tabellen, Ergebnisse, Vorschau, Termine, News, Kontakt, Historie, Sportheim, Partner, Momente, Impressum), automatisches Cover, Swipe-Viewer mit Vollbildmodus und die Druckfassung als Falt-Heft. Anzeigen kommen jetzt aus dem Bestand und werden gleichmäßig verteilt, nie mehr als zwei hintereinander (vorher vier Blöcke à fünf). Die Doppelseiten-Regel rechnet das Skript selbst, statt sie von Hand zu prüfen. Veranstaltungen: Bereich /veranstaltungen mit Detailseite pro Fest, Lebenszyklus über das Datum (Ankündigung vor dem Fest, Rückblick danach), Galerie mit Lightbox. veranstaltungen.json ist dritte Termin-Quelle, damit ein Fest nie doppelt gepflegt wird. News-Artikel als dritte dynamische Prefix-Route, gemeinsamer detail-head. Behobene Fehler: - Wappen-Kontexte setzten Zeilen-Layout auf .crest statt auf die Zeile; das Wappen wurde zum Grid, Vereinsname und Tore rutschten darunter zusammen (Ergebnis-Rückblick, Vorschau, Cover). - --header-h (60px) unterschätzt die Kopfleiste um 32px (Logo 72px + 20px Versatz): neues abgeleitetes --header-space, sonst klebten Zurück-Links unter dem Logo. - base_url in der Produktions-Config ohne www, während .htaccess auf www umleitet; preflight prüft das jetzt. - .lightbox brauchte display:none mit (0,2,0), sonst lag das Overlay offen. Neue Werkzeuge: bin/anzeige-add.php (Anzeigen-Bestand), bin/veranstaltung-bilder.php (Plakate und Galerien, Alt-Texte folgen der Quelldatei), bin/qr.php (Druck-QR mit Warnung, wenn base_url nicht auf die echte Domain zeigt). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
This commit is contained in:
77
app/components/stadionzeitung-viewer.php
Normal file
77
app/components/stadionzeitung-viewer.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
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]}})
|
||||
*
|
||||
* Ohne JS: alle Seiten normal gestapelt (stadionzeitung-seite-Komponente) —
|
||||
* voll lesbar, sequentiell auch per Screenreader. Mit JS
|
||||
* (stadionzeitung-viewer.js) wird der Track zum horizontalen Scroll-Snap-
|
||||
* Swiper, Zähler + Pfeil-Buttons kommen dazu (vorher [hidden], da ohne JS
|
||||
* funktionslos).
|
||||
*
|
||||
* Das Rendern der einzelnen Seiten (Bild wie Live) liegt in
|
||||
* stadionzeitung-seite.php — geteilt mit der Druckfassung
|
||||
* (stadionzeitung-druck.php), damit Bildschirm und Druck nie
|
||||
* auseinanderlaufen. Die Daten der Live-Seiten kommen EINMAL aus
|
||||
* stadionzeitung_live_data() (app/helpers.php): Entwürfe rechnen live,
|
||||
* veröffentlichte Ausgaben lesen ihren eingefrorenen snapshot.
|
||||
*/
|
||||
$issue = $issue ?? [];
|
||||
$images = $issue['images'] ?? [];
|
||||
$total = count($images);
|
||||
if ($images === []) {
|
||||
return;
|
||||
}
|
||||
$opponent = (string) ($issue['opponent'] ?? '');
|
||||
|
||||
// Live-Daten nur laden, wenn die Ausgabe tatsächlich eine Live-Seite enthält.
|
||||
$hasLive = false;
|
||||
foreach ($images as $entry) {
|
||||
if (($entry['type'] ?? 'image') === 'live') {
|
||||
$hasLive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$liveData = $hasLive ? stadionzeitung_live_data($issue) : [];
|
||||
?>
|
||||
<section class="stadionzeitung-viewer" data-stadionzeitung-viewer aria-roledescription="Bildergalerie" aria-label="<?= e('Stadionzeitung gegen ' . $opponent . ', Seite für Seite') ?>">
|
||||
<ul class="stadionzeitung-viewer__track" data-viewer-track>
|
||||
<?php foreach ($images as $i => $entry): ?>
|
||||
<li class="stadionzeitung-viewer__page" data-viewer-page>
|
||||
<?php component('stadionzeitung-seite', [
|
||||
'entry' => $entry,
|
||||
'issue' => $issue,
|
||||
'data' => $liveData,
|
||||
'eager' => $i === 0,
|
||||
]); ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<div class="stadionzeitung-viewer__controls" data-viewer-controls hidden>
|
||||
<button class="btn btn--icon stadionzeitung-viewer__nav-btn" type="button" data-viewer-prev aria-label="Vorherige Seite">
|
||||
<?= icon('chevron-left') ?>
|
||||
</button>
|
||||
<p class="stadionzeitung-viewer__counter" data-viewer-counter aria-live="polite">
|
||||
Seite <span data-viewer-current>1</span> von <?= $total ?>
|
||||
</p>
|
||||
<button class="btn btn--icon stadionzeitung-viewer__nav-btn" type="button" data-viewer-next aria-label="Nächste Seite">
|
||||
<?= icon('chevron-right') ?>
|
||||
</button>
|
||||
<?php /* Vollbild-Lesemodus: am Handy fressen Kopfleiste, Navigation und Fußzeile
|
||||
mehr Platz als die Seite selbst. Beide Icons stehen im Markup, welches
|
||||
sichtbar ist entscheidet CSS am Zustand — so muss das JS kein Markup
|
||||
erzeugen. Zustand über aria-pressed, deshalb bleibt das aria-label
|
||||
unverändert. */ ?>
|
||||
<button class="btn btn--icon stadionzeitung-viewer__full-btn" type="button" data-viewer-full aria-pressed="false" aria-label="Vollbild">
|
||||
<span class="stadionzeitung-viewer__icon--open"><?= icon('arrows-fullscreen') ?></span>
|
||||
<span class="stadionzeitung-viewer__icon--close"><?= icon('x-lg') ?></span>
|
||||
</button>
|
||||
</div>
|
||||
<a class="btn btn--outline stadionzeitung-viewer__print" href="<?= e(url('stadionzeitung/' . ($issue['slug'] ?? '')) . '?druck=1') ?>">
|
||||
<?= icon('printer') ?> Druckfassung
|
||||
</a>
|
||||
</section>
|
||||
Reference in New Issue
Block a user