Files
tsv08kulmbach-website/app/components/stadionzeitung-cover.php

148 lines
6.8 KiB
PHP
Raw Normal View History

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
2026-07-31 14:55:27 +02:00
<?php
declare(strict_types=1);
/**
* Automatisch generiertes Stadionzeitung-Cover (Phase 3) ersetzt das bisherige
* Canva-Titelbild. Einzige fotografische Stadionzeitungs-Seite: Vereins-Hero-Bild
* als Hintergrund (dieselbe Quelle wie die Startseite, data/home.json nie
* duplizieren) mit Abdunklungs-Verlauf, das freigestellte Spielerfoto ist der
* Star (sehr großformatig, rechts randabfallend). Kopf wie ein echtes
* Zeitschriften-Titelblatt: kleine rote Kicker-Zeile (Saison/Datum), riesiger
* Masthead mit Logo auf gleicher Höhe, dezente Tagline darunter. Unten links
* gestapelt: Begegnung als große freie Typografie (kein Kasten) über einer
* roten Trennlinie und Slogan + QR-Plakette. Dünne Passepartout-Rahmenlinie
* gibt den "druckfertig"-Effekt.
*
* Selbst-Containment: alle Maße hier laufen über CSS Container Queries (cqi,
* relativ zur eigenen Breite von .stadionzeitung-cover), NICHT über die
* Website-Schriftgrößen-Tokens aus tokens.css das Cover sieht dadurch in
* jedem Kontext (Karten-Vorschau, Swipe-Viewer, Druck) proportional exakt
* gleich aus, unabhängig von Viewport/Root-Schriftgröße. Einzige Ausnahme
* ganz bewusst: die crest-Komponente (geteiltes Bauteil, bekommt ihre Größe
* über --crest-size lokal in cqi mitgegeben, keine eigene Kopie nötig).
*
* Props: $issue (Eintrag aus data/stadionzeitung.json: opponent, date, team_key,
* player_image?, qr_image?)
*
* Gegner-Wappen wird zur Laufzeit über den Anstoß-Tag in data/matchcenter.json
* gesucht (next_matches/last_results des Teams) nie im JSON dupliziert. Kein
* Treffer (z. B. Freundschaftsspiel gegen eine Spielgemeinschaft, die der BFV
* nicht führt) das eigene Wappen kommt trotzdem aus dem lokalen Vereinslogo,
* der Gegner läuft über den Initialen-Fallback der crest-Komponente.
*/
$issue = $issue ?? [];
$opponent = (string) ($issue['opponent'] ?? '');
$date = (string) ($issue['date'] ?? '');
$teamKey = (string) ($issue['team_key'] ?? '');
$playerImage = $issue['player_image'] ?? null;
$qrImage = $issue['qr_image'] ?? null;
$club = json_load('club');
$heroImage = json_load('home')['hero']['image'] ?? null;
$team = null;
foreach (json_load('matchcenter')['teams'] ?? [] as $t) {
if (($t['key'] ?? '') === $teamKey) {
$team = $t;
break;
}
}
$teamName = (string) ($team['name'] ?? '');
$league = (string) ($team['league'] ?? '');
$season = (string) ($team['season'] ?? '');
$match = null;
foreach (array_merge($team['next_matches'] ?? [], $team['last_results'] ?? []) as $candidate) {
if (str_starts_with((string) ($candidate['kickoff'] ?? ''), $date)) {
$match = $candidate;
break;
}
}
$ownCrest = 'img/logo.svg';
$opponentCrest = null;
if ($match !== null) {
$ownCrest = $match['is_home'] ? $match['home_crest'] : $match['away_crest'];
$opponentCrest = $match['is_home'] ? $match['away_crest'] : $match['home_crest'];
}
// Gedruckte Zeile neben dem QR-Code — immer die echte Domain (club_domain()).
$linkText = club_domain();
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
2026-07-31 14:55:27 +02:00
$kicker = trim($season . ($date !== '' ? ' · ' . german_date($date) : ''));
// 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'] ?? ''));
?>
<div class="stadionzeitung-cover">
<?php if ($heroImage): ?>
<?php component('img', ['image' => $heroImage, 'sizes' => '480px', 'class' => 'stadionzeitung-cover__bg']); ?>
<?php endif; ?>
<?php if (!empty($playerImage)): ?>
Cover-Spielerfoto: Varianten statt 12-MB-Original (iOS Safari) Das Cover blieb am iPhone leer, während Chrome am Desktop es zeigte. Ursache war kein fehlendes Bild: das freigestellte Spielerfoto hatte 2584x5093 Pixel und 12,3 MB. iOS Safari verwirft Bilder, deren dekodierte Größe sein Budget übersteigt — hier gut 53 MB, und auf einer Ausgabe liegen 86 Bilder. Dargestellt wird das Foto mit 98cqi Höhe, bei 480px Seitenbreite also etwa 238x470 CSS-Pixel. Es gibt jetzt 320/640/1000 als PNG mit erhaltener Transparenz (0,2 / 0,8 / 1,9 MB); sizes nennt die echte Breite, sonst zöge ein Handy mit DPR 3 die größte Variante. Das Original ist gelöscht. Dafür kann resize_image_variants() nun Alpha erhalten ($alpha = true schreibt PNG statt JPG) und die img-Komponente eine Endung ($image['ext']). Der Default bleibt JPG, weil bestehende Aufrufer PNG-Quellen absichtlich zu JPG verkleinern. bin/stadionzeitung-add.php erzeugt die Varianten künftig selbst. Dort stand "unverändert kopieren, Felix liefert bereits ein web-taugliches Foto" — genau diese Annahme ist gerissen. Außerdem: smtp.password stand in der Produktiv-Config noch auf dem Platzhalter POSTFACH_PASSWORT (von preflight auf dem Server gefunden, einziger harter Fehler). Übernommen wurde das Passwort aus der Dev-Config, mit dem der Versand am 27.07.2026 verifiziert wurde; Host, Postfach und Empfänger sind identisch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
2026-07-31 16:58:43 +02:00
<?php
/* Varianten-Set (base/widths/ext=png) statt der Originaldatei. Das Original ist
ein Freisteller mit 2584x5093 Pixeln und 12 MB: iOS Safari verwirft Bilder,
deren dekodierte Größe sein Budget übersteigt (hier gut 53 MB), und auf einer
Ausgabe liegen rund 86 Bilder das Cover blieb am iPhone leer, während Chrome
am Desktop es zeigte (31.07.2026). Dargestellt wird es mit 98cqi Höhe, bei
480px Seitenbreite also etwa 238x470 CSS-Pixel; die 1000er Variante deckt
damit auch Druck und dreifache Pixeldichte ab.
sizes nennt die echte Breite, sonst zöge ein Handy mit DPR 3 die größte. */
$playerBild = is_array($playerImage)
? $playerImage
: ['src' => $playerImage, 'width' => 0, 'height' => 0, 'alt' => ''];
?>
<?php component('img', [
'image' => $playerBild,
'class' => 'stadionzeitung-cover__player',
'sizes' => '(max-width: 600px) 50vw, 240px',
]); ?>
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
2026-07-31 14:55:27 +02:00
<?php endif; ?>
<div class="stadionzeitung-cover__inner">
<header class="stadionzeitung-cover__masthead">
<?php if ($kicker !== ''): ?>
<p class="stadionzeitung-cover__kicker"><?= e($kicker) ?></p>
<?php endif; ?>
<div class="stadionzeitung-cover__masthead-row">
<img class="stadionzeitung-cover__logo" src="<?= e(asset('img/logo.svg')) ?>" alt="" width="75" height="97">
<p class="stadionzeitung-cover__wordmark">Stadionzeitung</p>
</div>
<p class="stadionzeitung-cover__tagline">Rund ums Heimspiel im Katzbachtal</p>
</header>
<div class="stadionzeitung-cover__corner">
<div class="stadionzeitung-cover__headline">
<?php /* Bewusst ohne Trennzeichen zwischen den Wappen (Felix, 29.07.2026:
ruhiger) die beiden Logos sprechen für sich. */ ?>
<div class="stadionzeitung-cover__crests">
<?php component('crest', ['src' => $ownCrest, 'name' => $club['name'] ?? '']); ?>
<?php component('crest', ['src' => $opponentCrest, 'name' => $opponent]); ?>
</div>
<p class="stadionzeitung-cover__matchup">Heimspiel gegen <?= e($opponent) ?></p>
<?php if ($teamName !== '' || $league !== ''): ?>
<p class="stadionzeitung-cover__meta"><?= e(trim($teamName . ($league !== '' ? ' · ' . $league : ''))) ?></p>
<?php endif; ?>
</div>
<?php if ($teaser !== ''): ?>
<p class="stadionzeitung-cover__teaser">
<span class="stadionzeitung-cover__teaser-label">Im Heft</span>
<?= e($teaser) ?>
</p>
<?php endif; ?>
<?php /* Bewusst ohne Vereins-Slogan (Felix, 29.07.2026: ruhiger) der
Slogan bleibt Footer/JSON-LD der Website vorbehalten. */ ?>
<footer class="stadionzeitung-cover__footer">
<?php /* QR bewusst minimal (Felix, 29.07.2026): Code + eine Zeile,
keine Plakette/kein Rahmen mehr. */ ?>
<?php if (!empty($qrImage)): ?>
<div class="stadionzeitung-cover__digital">
<img class="stadionzeitung-cover__qr" src="<?= e(asset($qrImage)) ?>" alt="" width="60" height="60" loading="lazy">
<span class="stadionzeitung-cover__link">Digital lesen · <?= e($linkText) ?></span>
</div>
<?php endif; ?>
</footer>
</div>
</div>
</div>