Stadionzeitung: Galerie-Seite als Bento-Raster

Optionale Live-Seite hinter dem Ergebnis-Rückblick mit Fotos vom letzten
Spiel. bin/stadionzeitung-add.php liest sie aus <ordner>/galerie/, Kopf und
Alt-Texte sind Chat-Pflege und hängen an source. Preflight meldet fehlende
Galerie-Dateien.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCMNXcfkM9RcaVjqoJjRsz
This commit is contained in:
2026-10-02 11:10:57 +02:00
parent 7aae3d5ca9
commit fbb23a8732
6 changed files with 235 additions and 2 deletions

View File

@@ -386,6 +386,24 @@ if (is_file($szFile)) {
}
}
// --- Stadionzeitung: Galerie-Fotos vorhanden? ---
// Die Galerie-Bilder liegen im Ausgaben-Ordner, den bin/stadionzeitung-add.php bei
// jedem Lauf leert und neu füllt. Fehlt eine Datei (Lauf ohne galerie/ im Ordner,
// Bild nicht mitcommittet), zeigt die Seite ein kaputtes Bild.
if (is_file($szFile)) {
$szData = $szData ?? (json_decode((string) file_get_contents($szFile), true) ?: []);
foreach ($szData['issues'] ?? [] as $ausgabe) {
foreach ($ausgabe['galerie']['bilder'] ?? [] as $bild) {
foreach ($bild['widths'] ?? [] as $w) {
$datei = PUBLIC_PATH . '/assets/' . ($bild['base'] ?? '') . "-{$w}.jpg";
if (!is_file($datei)) {
$errors[] = "Stadionzeitung '{$ausgabe['slug']}': Galerie-Foto fehlt (" . ($bild['base'] ?? '?') . "-{$w}.jpg).";
}
}
}
}
}
// --- 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

View File

@@ -19,6 +19,9 @@ declare(strict_types=1);
* <ordner>/spieler.* optional, freigestelltes Spielerfoto (PNG mit Transparenz)
* fürs Cover — wird unverändert kopiert, nie über GD neu kodiert
* <ordner>/verfasser.* optional, Porträtfoto für die Vorwort-Signatur
* <ordner>/galerie/* optional, Fotos vom letzten Spiel (natsort = Reihenfolge,
* das erste ist der Aufmacher) für die Bento-Galerie-
* Seite hinter dem Ergebnis-Rückblick, höchstens sieben
*
* KEINE Anzeigen im Ordner (seit 31.07.2026): die kommen aus dem dauerhaften
* Bestand data/anzeigen.json, gepflegt mit bin/anzeige-add.php. Sponsorenmotive
@@ -157,6 +160,7 @@ $vorwort = $findOne('vorwort');
$rueckseite = $findOne('rueckseite');
$spieler = $findOne('spieler');
$verfasser = $findOne('verfasser');
$galerieQuellen = $findAll('galerie');
// Anzeigen kommen NICHT mehr aus dem Ablieferordner, sondern aus dem dauerhaften
// Bestand (data/anzeigen.json, gepflegt über bin/anzeige-add.php). Ein noch
// vorhandener anzeigen/-Unterordner wäre also wirkungslos — dann besser sagen,
@@ -206,6 +210,9 @@ $oldCover = $oldIssue['cover'] ?? null;
// der BFV-Zeile) und die Liste der Mannschaften, die zusätzlich eine
// Gegnerseite bekommen — beides Chat-Pflege, überlebt erneute Läufe wie vorwort.
$oldOpponentShort = $oldIssue['opponent_short'] ?? null;
// Galerie-Seite: Kopf (kicker/heading/text) und Alt-Texte sind Chat-Pflege,
// die Bilder schreibt dieser Lauf aus <ordner>/galerie/ (siehe unten).
$oldGalerie = is_array($oldIssue['galerie'] ?? null) ? $oldIssue['galerie'] : null;
$oldGegnerTeams = is_array($oldIssue['gegner_teams'] ?? null) ? array_values($oldIssue['gegner_teams']) : null;
$veranstaltung = null;
if (is_string($oldVeranstaltung) && $oldVeranstaltung !== '') {
@@ -317,6 +324,61 @@ $bild = static function (string $src, string $name, string $alt) use ($destDir,
];
};
// --- Galerie (optional): Fotos vom letzten Spiel als Bento-Seite. Wie bei den
// Veranstaltungsbildern ist der Ordner immer der KOMPLETTE Bestand (der
// Ausgaben-Ordner wurde oben geleert), Alt-Texte hängen an `source`, dem
// Originaldateinamen, und folgen ihrem Bild auch bei neuer Reihenfolge. Zwei
// Breiten reichen: der Aufmacher steht mit gut zwei Dritteln der Seitenbreite. ---
$galerieMax = 7;
$galerie = $oldGalerie;
if ($galerieQuellen !== []) {
$altBySource = [];
foreach ($oldGalerie['bilder'] ?? [] as $b) {
if (is_array($b) && !empty($b['source'])) {
$altBySource[(string) $b['source']] = (string) ($b['alt'] ?? '');
}
}
$platzhalter = trim((string) ($oldGalerie['heading'] ?? '')) ?: 'Fotos vom letzten Spiel';
if (count($galerieQuellen) > $galerieMax) {
$log(sprintf('galerie/ enthält %d Fotos, das Raster fasst %d — die übrigen bleiben weg.', count($galerieQuellen), $galerieMax), 'WARN');
}
$galerieBilder = [];
$ohneAlt = [];
foreach (array_slice($galerieQuellen, 0, $galerieMax) as $n => $src) {
$source = basename($src);
$name = sprintf('galerie-%02d', $n + 1);
try {
$variants = resize_image_variants($src, "{$destDir}/{$name}", [640, 1200], 80);
} catch (RuntimeException $e) {
$fail("Galerie-Foto {$source} fehlgeschlagen: " . $e->getMessage());
}
$generated = array_values(array_unique(array_column($variants, 'width')));
sort($generated);
$alt = trim($altBySource[$source] ?? '');
if ($alt === '' || $alt === $platzhalter) {
$ohneAlt[] = $source;
$alt = $platzhalter;
}
$galerieBilder[] = [
'source' => $source,
'base' => "img/stadionzeitung/{$slug}/{$name}",
'widths' => $generated,
'alt' => $alt,
];
}
$galerie = ($oldGalerie ?? []) + ['kicker' => 'Rückblick', 'heading' => $platzhalter];
$galerie['bilder'] = $galerieBilder;
$log(count($galerieBilder) . ' Galerie-Fotos übernommen.');
if ($ohneAlt !== []) {
$log('Galerie: noch ohne eigenen Alt-Text (im Chat ergänzen): ' . implode(', ', $ohneAlt), 'WARN');
}
} elseif (!empty($oldGalerie['bilder'])) {
// Die Dateien sind mit dem Ausgaben-Ordner weg; ohne galerie/ im Ordner
// lieferte die Seite sonst kaputte Bilder.
$log('Die Ausgabe hatte eine Galerie, der Ordner liefert aber kein galerie/ mehr — die Galerie-Seite entfällt.', 'WARN');
$galerie['bilder'] = [];
}
/*
* Inhalt als Liste von BLÖCKEN, Anzeigen kommen anschließend in die Lücken
* dazwischen. Ein Block ist eine Folge von Seiten, die zusammenbleiben MUSS;
@@ -375,6 +437,12 @@ try {
$bloecke[] = ['name' => 'ergebnisse', 'seiten' => [$live('ergebnisse')], 'anzeigen_danach' => true];
// Galerie direkt hinter dem Ergebnis-Rückblick: erst das Ergebnis, dann die
// Bilder dazu. Eigener Block, eine Anzeige dazwischen ist unbedenklich.
if (!empty($galerie['bilder'])) {
$bloecke[] = ['name' => 'galerie', 'seiten' => [$live('galerie')], '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() —
@@ -761,6 +829,7 @@ $issues[] = [
'aufruf' => $oldAufruf,
'veranstaltung' => $oldVeranstaltung,
'cover' => $oldCover,
'galerie' => $galerie,
'verfasser_photo' => $verfasserPhoto,
'termine_snapshot' => $termineSnapshot,
'images' => $images,