Compare commits
7 Commits
70831b9bea
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b59a478c2 | |||
| fd305673aa | |||
| bd8f1dd129 | |||
| 33b73087cf | |||
| 5a107a955a | |||
| 5be1a25c07 | |||
| 2ae5ddb1ac |
28
CLAUDE.md
@@ -54,7 +54,7 @@ selbst gehosteter Stack ohne Abhängigkeiten von Drittanbietern.
|
|||||||
| Partner/Sponsoren | `data/partners.json` | — |
|
| Partner/Sponsoren | `data/partners.json` | — |
|
||||||
| Instagram-Beiträge | `data/instagram.json` + `public/assets/img/instagram/` | Aufgenommen ausschließlich mit **`php bin/instagram-add.php <post-url>`** (holt Bild, Text und Datum) — Einträge nie von Hand tippen, Bilder nie von Hand skalieren. **Kein Cron**, chat-gepflegt wie `events.json`. Eintrag und Bild müssen immer zusammen existieren, `bin/preflight.php` prüft beide Richtungen |
|
| Instagram-Beiträge | `data/instagram.json` + `public/assets/img/instagram/` | Aufgenommen ausschließlich mit **`php bin/instagram-add.php <post-url>`** (holt Bild, Text und Datum) — Einträge nie von Hand tippen, Bilder nie von Hand skalieren. **Kein Cron**, chat-gepflegt wie `events.json`. Eintrag und Bild müssen immer zusammen existieren, `bin/preflight.php` prüft beide Richtungen |
|
||||||
| Matchcenter (Spiele/Ergebnisse/Tabellen) | `data/matchcenter.json` + `public/assets/img/crests/` | **maschinenverwaltet von `bin/matchcenter-sync.php` — nie von Hand editieren**. Team-/Wettbewerbs-IDs in `config.php` (`matchcenter.teams`) |
|
| Matchcenter (Spiele/Ergebnisse/Tabellen) | `data/matchcenter.json` + `public/assets/img/crests/` | **maschinenverwaltet von `bin/matchcenter-sync.php` — nie von Hand editieren**. Team-/Wettbewerbs-IDs in `config.php` (`matchcenter.teams`) |
|
||||||
| Stadionzeitung (Ausgaben-Archiv) | `data/stadionzeitung.json` + `public/assets/img/stadionzeitung/` | **maschinenverwaltet von `bin/stadionzeitung-add.php` + `bin/stadionzeitung-publish.php` (Status/Snapshot) — nie von Hand editieren; einzige Chat-Pflege-Ausnahmen sind `vorwort`, `interview`, `aufruf`, `veranstaltung` (Slug-Verweis auf `veranstaltungen.json`), `cover` (`teaser` einer Sonderausgabe), `opponent_short` (gedruckter Anzeigename des Gegners) und `gegner_teams` (weitere Mannschaften mit Gegnerseite).** Der Ausgaben-Ordner wird bei jedem Lauf komplett geleert und neu gefüllt |
|
| Stadionzeitung (Ausgaben-Archiv) | `data/stadionzeitung.json` + `public/assets/img/stadionzeitung/` | **maschinenverwaltet von `bin/stadionzeitung-add.php` + `bin/stadionzeitung-publish.php` (Status/Snapshot) — nie von Hand editieren; einzige Chat-Pflege-Ausnahmen sind `vorwort`, `interview`, `aufruf`, `beitrag` (Text/Quelle/Bildnachweis; `image` bleibt script-verwaltet), `rueckblick` (Text/Bildnachweis; `image` bleibt script-verwaltet), `veranstaltung` (Slug-Verweis auf `veranstaltungen.json`), `cover` (`teaser` einer Sonderausgabe), `opponent_short` (gedruckter Anzeigename des Gegners) und `gegner_teams` (weitere Mannschaften mit Gegnerseite).** Der Ausgaben-Ordner wird bei jedem Lauf komplett geleert und neu gefüllt |
|
||||||
| Sponsoren-Anzeigen (Stadionzeitung) | `data/anzeigen.json` + `public/assets/img/anzeigen/` | Dauerhafter Bestand, **nicht** pro Ausgabe: Motive ändern sich selten, das Heft erscheint oft. `sponsor`, `alt` (optional), `aktiv`, `bis` (optional, Enddatum gegen das Ausgabedatum), `platzierung` (optional, fester Platz auf einer Live-Seite statt im Streu-Pool) und die **Reihenfolge** (= Reihenfolge im Heft) im Chat gepflegt; `base`/`widths` schreibt ausschließlich `bin/anzeige-add.php`. Anzeigen nie in den Ausgaben-Ordner legen |
|
| Sponsoren-Anzeigen (Stadionzeitung) | `data/anzeigen.json` + `public/assets/img/anzeigen/` | Dauerhafter Bestand, **nicht** pro Ausgabe: Motive ändern sich selten, das Heft erscheint oft. `sponsor`, `alt` (optional), `aktiv`, `bis` (optional, Enddatum gegen das Ausgabedatum), `platzierung` (optional, fester Platz auf einer Live-Seite statt im Streu-Pool) und die **Reihenfolge** (= Reihenfolge im Heft) im Chat gepflegt; `base`/`widths` schreibt ausschließlich `bin/anzeige-add.php`. Anzeigen nie in den Ausgaben-Ordner legen |
|
||||||
| Termine/Events (`/termine`) | `data/events.json` (Vereins-Termine **ohne** eigene Detailseite) — Spieltermine kommen weiterhin nur aus `data/matchcenter.json`, Feste mit eigener Seite nur aus `data/veranstaltungen.json`; die Termine-Seite mergt alle drei | Von Claude im Chat gepflegt wie `teams.json`/`partners.json` — **keine** Automatisierung, kein Bin-Skript. Spieltermine nie hier duplizieren. Ein Fest, das eine Detailseite hat, steht **nur** in `veranstaltungen.json` (`bin/preflight.php` prüft das) |
|
| Termine/Events (`/termine`) | `data/events.json` (Vereins-Termine **ohne** eigene Detailseite) — Spieltermine kommen weiterhin nur aus `data/matchcenter.json`, Feste mit eigener Seite nur aus `data/veranstaltungen.json`; die Termine-Seite mergt alle drei | Von Claude im Chat gepflegt wie `teams.json`/`partners.json` — **keine** Automatisierung, kein Bin-Skript. Spieltermine nie hier duplizieren. Ein Fest, das eine Detailseite hat, steht **nur** in `veranstaltungen.json` (`bin/preflight.php` prüft das) |
|
||||||
| Veranstaltungen (`/veranstaltungen` + Detailseiten) | `data/veranstaltungen.json` + `public/assets/img/veranstaltungen/` | Termin, Texte, Programm, Turnierpläne (`turniere[]`, nur fürs Heft), Abschnitte von Claude im Chat gepflegt — **außer `plakate` und `galerie`: die schreibt ausschließlich `bin/veranstaltung-bilder.php`**. Adresse nie in die Texte schreiben (steht in `club.json`) |
|
| Veranstaltungen (`/veranstaltungen` + Detailseiten) | `data/veranstaltungen.json` + `public/assets/img/veranstaltungen/` | Termin, Texte, Programm, Turnierpläne (`turniere[]`, nur fürs Heft), Abschnitte von Claude im Chat gepflegt — **außer `plakate` und `galerie`: die schreibt ausschließlich `bin/veranstaltung-bilder.php`**. Adresse nie in die Texte schreiben (steht in `club.json`) |
|
||||||
@@ -521,6 +521,19 @@ Besucher laden alle Matchcenter-Inhalte ausschließlich von unserer Domain.
|
|||||||
|
|
||||||
## Stadionzeitung (Ausgaben-Archiv)
|
## Stadionzeitung (Ausgaben-Archiv)
|
||||||
|
|
||||||
|
**CLI-Abrufe bei lokalem DNS-Ausfall:** `TSV_HTTP_DOH_URL=https://1.1.1.1/dns-query`
|
||||||
|
kann für `bin/matchcenter-sync.php` und `bin/instagram-add.php` gesetzt werden.
|
||||||
|
Optional, keine Änderung am System-DNS; Cron/Web ignoriert den Override.
|
||||||
|
|
||||||
|
**Redaktioneller Lesetipp (24.10.2026):** optionales chat-gepflegtes `beitrag`-Feld
|
||||||
|
(`kicker`, `heading`, `paragraphs[]`, `source`, `source_url` als HTTPS-Adresse,
|
||||||
|
`cta_label`, `image_credit`, `image.alt`). `bin/stadionzeitung-add.php` übernimmt
|
||||||
|
das Feld über erneute Läufe, schreibt `image` aus `<ordner>/beitrag.*` und erzeugt
|
||||||
|
`qr-beitrag.png` direkt zur externen Quelle. Die Seite steht im anzeigenfreien
|
||||||
|
Aufschlag hinter Vorwort/Interview; `cover.teaser` kündigt sie unabhängig von
|
||||||
|
einem Interview an. Rendering gemeinsam für Viewer/Druck über
|
||||||
|
`stadionzeitung-beitrag.php`; Preflight prüft Quelle, Foto und QR.
|
||||||
|
|
||||||
Digitale Fassung der Stadionzeitung zu jedem Heimspiel — Archiv-Übersicht (`/stadionzeitung`) als
|
Digitale Fassung der Stadionzeitung zu jedem Heimspiel — Archiv-Übersicht (`/stadionzeitung`) als
|
||||||
**Liste über die volle Breite, Cover links und die Angaben rechts daneben** (dasselbe Muster wie
|
**Liste über die volle Breite, Cover links und die Angaben rechts daneben** (dasselbe Muster wie
|
||||||
`.veranstaltung-card`, und aus demselben Grund: der Bestand ist klein). Vorher war das ein
|
`.veranstaltung-card`, und aus demselben Grund: der Bestand ist klein). Vorher war das ein
|
||||||
@@ -834,6 +847,19 @@ Raster: 6×4-Gitter, das erste Foto 4×2, die Anordnung je Anzahl (1 bis 7) als
|
|||||||
`__bento--nN` mit `grid-template-areas`. Entstanden als Füllseite, die eine 43-seitige Ausgabe
|
`__bento--nN` mit `grid-template-areas`. Entstanden als Füllseite, die eine 43-seitige Ausgabe
|
||||||
auf 44 brachte.
|
auf 44 brachte.
|
||||||
|
|
||||||
|
**Redaktioneller Spielrückblick (16.10.2026).** Optionales Feld `rueckblick` einer Ausgabe:
|
||||||
|
`kicker`, `heading`, `meta`, `result`, `paragraphs[]`, `credit`, `source_url`
|
||||||
|
sind Chat-Pflege; `image` (inklusive chat-gepflegtem Alt-Text) verwaltet das Add-Skript aus
|
||||||
|
`<ordner>/rueckblick.*`. Die Live-Seite steht nach den Ergebnissen, vor der Galerie. Das Foto
|
||||||
|
liegt dauerhaft im Ausgaben-Ordner. Bei erneutem Erzeugen muss die Quelle wieder mitgeliefert
|
||||||
|
werden; fehlt sie, bricht das Skript vor dem Leeren des Zielordners ab. Preflight prüft Foto
|
||||||
|
und Bildnachweis. Der gemeinsame Bild-/Textnachweis steht einmal direkt unter dem Foto
|
||||||
|
(im Web zur Quelle verlinkt, auch im Druck lesbar). Kein Bild hochskalieren oder fremd einbinden.
|
||||||
|
Für die Untersteinach-Ausgabe: öffentliches Derby-Vorschaubild
|
||||||
|
`https://www.anpfiff.info/upload/images/Teaser8/843447.jpg` (560 × 270), Bildquelle anpfiff.info.
|
||||||
|
Felix hat die Nutzungsgenehmigung im Chat am 16.10.2026 bestätigt. Ein individueller Fotograf
|
||||||
|
ist am frei sichtbaren Bild nicht ausgewiesen; deshalb keinen Namen vermuten.
|
||||||
|
|
||||||
**Gegner- und Ergebnisseite neu gestaltet (19.09.2026).** Beide Seiten waren inhaltlich richtig und
|
**Gegner- und Ergebnisseite neu gestaltet (19.09.2026).** Beide Seiten waren inhaltlich richtig und
|
||||||
gestalterisch leer, der Umbau lief über mehrere verworfene Entwürfe. Was davon bleibt:
|
gestalterisch leer, der Umbau lief über mehrere verworfene Entwürfe. Was davon bleibt:
|
||||||
- **Ergebnisse als Anzeigetafel** (Vorbild kicker/Sofascore): Wappen und Name je Seite, das Ergebnis
|
- **Ergebnisse als Anzeigetafel** (Vorbild kicker/Sofascore): Wappen und Name je Seite, das Ergebnis
|
||||||
|
|||||||
44
app/components/stadionzeitung-beitrag.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/** Redaktioneller Lesetipp. Text/Quelle: Chat-Pflege; Foto/QR: add-Skript. */
|
||||||
|
$beitrag = $issue['beitrag'] ?? null;
|
||||||
|
if (!is_array($beitrag)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$sourceUrl = (string) ($beitrag['source_url'] ?? '');
|
||||||
|
$validUrl = filter_var($sourceUrl, FILTER_VALIDATE_URL)
|
||||||
|
&& parse_url($sourceUrl, PHP_URL_SCHEME) === 'https';
|
||||||
|
$qrPath = 'img/stadionzeitung/' . (string) ($issue['slug'] ?? '') . '/qr-beitrag.png';
|
||||||
|
?>
|
||||||
|
<div class="stadionzeitung-beitrag">
|
||||||
|
<header class="stadionzeitung-page__head">
|
||||||
|
<p class="stadionzeitung-page__kicker"><?= e((string) ($beitrag['kicker'] ?? 'Lesetipp')) ?></p>
|
||||||
|
<h2 class="stadionzeitung-page__heading"><?= e((string) ($beitrag['heading'] ?? '')) ?></h2>
|
||||||
|
</header>
|
||||||
|
<span class="stadionzeitung-page__rule" aria-hidden="true"></span>
|
||||||
|
<figure class="stadionzeitung-beitrag__figure">
|
||||||
|
<?php component('img', ['image' => $beitrag['image'] ?? [], 'sizes' => '(min-width: 520px) 430px, 90vw', 'class' => 'stadionzeitung-beitrag__foto', 'eager' => $eager ?? false]); ?>
|
||||||
|
<figcaption class="stadionzeitung-rueckblick__credit"><?= e((string) ($beitrag['image_credit'] ?? '')) ?></figcaption>
|
||||||
|
</figure>
|
||||||
|
<?php foreach ($beitrag['paragraphs'] ?? [] as $paragraph): ?>
|
||||||
|
<p class="stadionzeitung-vorwort__text"><?= e((string) $paragraph) ?></p>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<p class="stadionzeitung-beitrag__source"><?= e((string) ($beitrag['source'] ?? '')) ?></p>
|
||||||
|
<?php if ($validUrl): ?>
|
||||||
|
<a class="stadionzeitung-page__cta" href="<?= e($sourceUrl) ?>" target="_blank" rel="noopener noreferrer">
|
||||||
|
<div class="stadionzeitung-page__cta-text">
|
||||||
|
<p class="stadionzeitung-page__cta-label"><?= e((string) ($beitrag['cta_label'] ?? 'Lesetipp')) ?></p>
|
||||||
|
<p class="stadionzeitung-page__cta-title">Den vollständigen Beitrag lesen</p>
|
||||||
|
<p class="stadionzeitung-page__cta-link"><?= e((string) parse_url($sourceUrl, PHP_URL_HOST)) ?></p>
|
||||||
|
</div>
|
||||||
|
<?php if (is_file(PUBLIC_PATH . '/assets/' . $qrPath)): ?>
|
||||||
|
<div class="stadionzeitung-page__cta-abriss">
|
||||||
|
<img class="stadionzeitung-page__cta-qr" src="<?= e(asset($qrPath)) ?>" alt="QR-Code zum Originalartikel" width="100" height="100" loading="lazy" decoding="async">
|
||||||
|
<span class="stadionzeitung-page__cta-scan">Zum Artikel</span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
41
app/components/stadionzeitung-rueckblick.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/** Redaktioneller Spielrückblick. Text/Quelle: Chat-Pflege; image: add-Skript. */
|
||||||
|
$rueckblick = $issue['rueckblick'] ?? [];
|
||||||
|
$sourceUrl = (string) ($rueckblick['source_url'] ?? '');
|
||||||
|
$sourceLink = filter_var($sourceUrl, FILTER_VALIDATE_URL)
|
||||||
|
&& in_array(parse_url($sourceUrl, PHP_URL_SCHEME), ['https', 'http'], true);
|
||||||
|
?>
|
||||||
|
<div class="stadionzeitung-rueckblick">
|
||||||
|
<header class="stadionzeitung-page__head">
|
||||||
|
<p class="stadionzeitung-page__kicker"><?= e((string) ($rueckblick['kicker'] ?? 'Rückblick')) ?></p>
|
||||||
|
<h2 class="stadionzeitung-page__heading"><?= e((string) ($rueckblick['heading'] ?? '')) ?></h2>
|
||||||
|
</header>
|
||||||
|
<span class="stadionzeitung-page__rule" aria-hidden="true"></span>
|
||||||
|
<div class="stadionzeitung-rueckblick__scoreline">
|
||||||
|
<p class="stadionzeitung-ergebnisse__result"><?= e((string) ($rueckblick['result'] ?? '')) ?></p>
|
||||||
|
<p class="stadionzeitung-page__sub"><?= e((string) ($rueckblick['meta'] ?? '')) ?></p>
|
||||||
|
</div>
|
||||||
|
<figure class="stadionzeitung-rueckblick__figure">
|
||||||
|
<?php component('img', [
|
||||||
|
'image' => $rueckblick['image'],
|
||||||
|
'sizes' => '(min-width: 520px) 430px, 90vw',
|
||||||
|
'class' => 'stadionzeitung-rueckblick__foto',
|
||||||
|
'eager' => $eager ?? false,
|
||||||
|
]); ?>
|
||||||
|
<figcaption class="stadionzeitung-rueckblick__credit">
|
||||||
|
<?php if ($sourceLink): ?>
|
||||||
|
<a href="<?= e($sourceUrl) ?>" target="_blank" rel="noopener noreferrer"><?= e((string) ($rueckblick['credit'] ?? '')) ?></a>
|
||||||
|
<?php else: ?>
|
||||||
|
<?= e((string) ($rueckblick['credit'] ?? '')) ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
<div class="stadionzeitung-rueckblick__text">
|
||||||
|
<?php foreach ($rueckblick['paragraphs'] ?? [] as $paragraph): ?>
|
||||||
|
<p class="stadionzeitung-vorwort__text"><?= e((string) $paragraph) ?></p>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -50,6 +50,9 @@ $type = $entry['type'] ?? 'image';
|
|||||||
<?php case 'interview': ?>
|
<?php case 'interview': ?>
|
||||||
<?php component('stadionzeitung-interview', ['issue' => $issue]); ?>
|
<?php component('stadionzeitung-interview', ['issue' => $issue]); ?>
|
||||||
<?php break; ?>
|
<?php break; ?>
|
||||||
|
<?php case 'beitrag': ?>
|
||||||
|
<?php component('stadionzeitung-beitrag', ['issue' => $issue, 'eager' => $eager]); ?>
|
||||||
|
<?php break; ?>
|
||||||
<?php case 'aufruf': ?>
|
<?php case 'aufruf': ?>
|
||||||
<?php component('stadionzeitung-aufruf', ['issue' => $issue]); ?>
|
<?php component('stadionzeitung-aufruf', ['issue' => $issue]); ?>
|
||||||
<?php break; ?>
|
<?php break; ?>
|
||||||
@@ -100,6 +103,9 @@ $type = $entry['type'] ?? 'image';
|
|||||||
<?php case 'galerie': ?>
|
<?php case 'galerie': ?>
|
||||||
<?php component('stadionzeitung-galerie', ['issue' => $issue]); ?>
|
<?php component('stadionzeitung-galerie', ['issue' => $issue]); ?>
|
||||||
<?php break; ?>
|
<?php break; ?>
|
||||||
|
<?php case 'rueckblick': ?>
|
||||||
|
<?php component('stadionzeitung-rueckblick', ['issue' => $issue, 'eager' => $eager]); ?>
|
||||||
|
<?php break; ?>
|
||||||
<?php case 'momente': ?>
|
<?php case 'momente': ?>
|
||||||
<?php component('stadionzeitung-momente', ['items' => $data['momente'] ?? []]); ?>
|
<?php component('stadionzeitung-momente', ['items' => $data['momente'] ?? []]); ?>
|
||||||
<?php break; ?>
|
<?php break; ?>
|
||||||
|
|||||||
@@ -149,6 +149,11 @@ if (!$lock || !flock($lock, LOCK_EX | LOCK_NB)) {
|
|||||||
// --- HTTP-Helfer, unverändert aus bin/instagram-sync.php übernommen ---
|
// --- HTTP-Helfer, unverändert aus bin/instagram-sync.php übernommen ---
|
||||||
$fetch = static function (string $url, array $headers = []): string|false {
|
$fetch = static function (string $url, array $headers = []): string|false {
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
|
// Optional wie im Matchcenter-Sync, wenn der lokale DNS-Dienst ausfällt.
|
||||||
|
$dohUrl = getenv('TSV_HTTP_DOH_URL') ?: '';
|
||||||
|
if ($dohUrl !== '') {
|
||||||
|
curl_setopt($ch, CURLOPT_DOH_URL, $dohUrl);
|
||||||
|
}
|
||||||
curl_setopt_array($ch, [
|
curl_setopt_array($ch, [
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
CURLOPT_FOLLOWLOCATION => true,
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
|||||||
@@ -94,6 +94,11 @@ if (!$lock || !flock($lock, LOCK_EX | LOCK_NB)) {
|
|||||||
// --- HTTP-Helfer mit Browser-typischen Headern (gegen HTTP 418) ---
|
// --- HTTP-Helfer mit Browser-typischen Headern (gegen HTTP 418) ---
|
||||||
$fetch = static function (string $url, bool $json = true) use ($referer): string|false {
|
$fetch = static function (string $url, bool $json = true) use ($referer): string|false {
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
|
// Optionaler CLI-Ausweg bei defektem lokalem DNS; keine Systemänderung.
|
||||||
|
$dohUrl = PHP_SAPI === 'cli' ? (getenv('TSV_HTTP_DOH_URL') ?: '') : '';
|
||||||
|
if ($dohUrl !== '') {
|
||||||
|
curl_setopt($ch, CURLOPT_DOH_URL, $dohUrl);
|
||||||
|
}
|
||||||
curl_setopt_array($ch, [
|
curl_setopt_array($ch, [
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
CURLOPT_FOLLOWLOCATION => true,
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
|||||||
@@ -393,6 +393,45 @@ if (is_file($szFile)) {
|
|||||||
if (is_file($szFile)) {
|
if (is_file($szFile)) {
|
||||||
$szData = $szData ?? (json_decode((string) file_get_contents($szFile), true) ?: []);
|
$szData = $szData ?? (json_decode((string) file_get_contents($szFile), true) ?: []);
|
||||||
foreach ($szData['issues'] ?? [] as $ausgabe) {
|
foreach ($szData['issues'] ?? [] as $ausgabe) {
|
||||||
|
if (is_array($ausgabe['beitrag'] ?? null)) {
|
||||||
|
$beitrag = $ausgabe['beitrag'];
|
||||||
|
foreach (['heading', 'source', 'source_url', 'image_credit'] as $feld) {
|
||||||
|
if (trim((string) ($beitrag[$feld] ?? '')) === '') {
|
||||||
|
$errors[] = "Stadionzeitung '{$ausgabe['slug']}': Beitrag ohne {$feld}.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$quelle = (string) ($beitrag['source_url'] ?? '');
|
||||||
|
if (!filter_var($quelle, FILTER_VALIDATE_URL) || parse_url($quelle, PHP_URL_SCHEME) !== 'https') {
|
||||||
|
$errors[] = "Stadionzeitung '{$ausgabe['slug']}': Beitrag mit ungültiger Quellen-URL.";
|
||||||
|
}
|
||||||
|
$foto = $beitrag['image'] ?? [];
|
||||||
|
if (empty($foto['base']) || empty($foto['widths']) || empty($foto['alt'])) {
|
||||||
|
$errors[] = "Stadionzeitung '{$ausgabe['slug']}': Beitragsfoto oder Alt-Text fehlt.";
|
||||||
|
}
|
||||||
|
foreach ($foto['widths'] ?? [] as $w) {
|
||||||
|
if (!is_file(PUBLIC_PATH . '/assets/' . ($foto['base'] ?? '') . "-{$w}.jpg")) {
|
||||||
|
$errors[] = "Stadionzeitung '{$ausgabe['slug']}': Beitragsfoto fehlt ({$w}px).";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!is_file(PUBLIC_PATH . '/assets/img/stadionzeitung/' . $ausgabe['slug'] . '/qr-beitrag.png')) {
|
||||||
|
$errors[] = "Stadionzeitung '{$ausgabe['slug']}': Beitrags-QR fehlt.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_array($ausgabe['rueckblick'] ?? null)) {
|
||||||
|
$rueckblick = $ausgabe['rueckblick'];
|
||||||
|
$foto = $rueckblick['image'] ?? [];
|
||||||
|
if (empty($foto['base']) || empty($foto['widths'])) {
|
||||||
|
$errors[] = "Stadionzeitung '{$ausgabe['slug']}': Foto zum Spielrückblick fehlt.";
|
||||||
|
}
|
||||||
|
foreach ($foto['widths'] ?? [] as $w) {
|
||||||
|
if (!is_file(PUBLIC_PATH . '/assets/' . ($foto['base'] ?? '') . "-{$w}.jpg")) {
|
||||||
|
$errors[] = "Stadionzeitung '{$ausgabe['slug']}': Rückblick-Foto fehlt ({$w}px).";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (trim((string) ($rueckblick['credit'] ?? '')) === '') {
|
||||||
|
$errors[] = "Stadionzeitung '{$ausgabe['slug']}': Bildnachweis zum Spielrückblick fehlt.";
|
||||||
|
}
|
||||||
|
}
|
||||||
foreach ($ausgabe['galerie']['bilder'] ?? [] as $bild) {
|
foreach ($ausgabe['galerie']['bilder'] ?? [] as $bild) {
|
||||||
foreach ($bild['widths'] ?? [] as $w) {
|
foreach ($bild['widths'] ?? [] as $w) {
|
||||||
$datei = PUBLIC_PATH . '/assets/' . ($bild['base'] ?? '') . "-{$w}.jpg";
|
$datei = PUBLIC_PATH . '/assets/' . ($bild['base'] ?? '') . "-{$w}.jpg";
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ $vorwort = $findOne('vorwort');
|
|||||||
$rueckseite = $findOne('rueckseite');
|
$rueckseite = $findOne('rueckseite');
|
||||||
$spieler = $findOne('spieler');
|
$spieler = $findOne('spieler');
|
||||||
$verfasser = $findOne('verfasser');
|
$verfasser = $findOne('verfasser');
|
||||||
|
$rueckblickFoto = $findOne('rueckblick');
|
||||||
|
$beitragFoto = $findOne('beitrag');
|
||||||
$galerieQuellen = $findAll('galerie');
|
$galerieQuellen = $findAll('galerie');
|
||||||
// Anzeigen kommen NICHT mehr aus dem Ablieferordner, sondern aus dem dauerhaften
|
// Anzeigen kommen NICHT mehr aus dem Ablieferordner, sondern aus dem dauerhaften
|
||||||
// Bestand (data/anzeigen.json, gepflegt über bin/anzeige-add.php). Ein noch
|
// Bestand (data/anzeigen.json, gepflegt über bin/anzeige-add.php). Ein noch
|
||||||
@@ -200,6 +202,16 @@ foreach ($existing['issues'] ?? [] as $entry) {
|
|||||||
$oldVorwort = $oldIssue['vorwort'] ?? null;
|
$oldVorwort = $oldIssue['vorwort'] ?? null;
|
||||||
// Interview wie das Vorwort chat-gepflegt — überlebt erneute Läufe ebenso.
|
// Interview wie das Vorwort chat-gepflegt — überlebt erneute Läufe ebenso.
|
||||||
$oldInterview = $oldIssue['interview'] ?? null;
|
$oldInterview = $oldIssue['interview'] ?? null;
|
||||||
|
$beitrag = $oldIssue['beitrag'] ?? null;
|
||||||
|
if (is_array($beitrag) && $beitragFoto === null) {
|
||||||
|
$fail('Redaktioneller Beitrag vorhanden, aber beitrag.* fehlt im Ablieferordner.');
|
||||||
|
}
|
||||||
|
if (is_array($beitrag)) {
|
||||||
|
$beitragLink = trim((string) ($beitrag['source_url'] ?? ''));
|
||||||
|
if (!filter_var($beitragLink, FILTER_VALIDATE_URL) || parse_url($beitragLink, PHP_URL_SCHEME) !== 'https') {
|
||||||
|
$fail('Beitrag: source_url muss eine gültige HTTPS-Adresse sein.');
|
||||||
|
}
|
||||||
|
}
|
||||||
// Aufruf-Seite (z. B. Sportfest-Helferaufruf) ebenso chat-gepflegt.
|
// Aufruf-Seite (z. B. Sportfest-Helferaufruf) ebenso chat-gepflegt.
|
||||||
$oldAufruf = $oldIssue['aufruf'] ?? null;
|
$oldAufruf = $oldIssue['aufruf'] ?? null;
|
||||||
// Sportfest-Ausgabe: Slug-Verweis auf data/veranstaltungen.json (Programm,
|
// Sportfest-Ausgabe: Slug-Verweis auf data/veranstaltungen.json (Programm,
|
||||||
@@ -213,6 +225,10 @@ $oldOpponentShort = $oldIssue['opponent_short'] ?? null;
|
|||||||
// Galerie-Seite: Kopf (kicker/heading/text) und Alt-Texte sind Chat-Pflege,
|
// Galerie-Seite: Kopf (kicker/heading/text) und Alt-Texte sind Chat-Pflege,
|
||||||
// die Bilder schreibt dieser Lauf aus <ordner>/galerie/ (siehe unten).
|
// die Bilder schreibt dieser Lauf aus <ordner>/galerie/ (siehe unten).
|
||||||
$oldGalerie = is_array($oldIssue['galerie'] ?? null) ? $oldIssue['galerie'] : null;
|
$oldGalerie = is_array($oldIssue['galerie'] ?? null) ? $oldIssue['galerie'] : null;
|
||||||
|
$rueckblick = is_array($oldIssue['rueckblick'] ?? null) ? $oldIssue['rueckblick'] : null;
|
||||||
|
if ($rueckblick !== null && $rueckblickFoto === null) {
|
||||||
|
$fail('Spielrückblick vorhanden, aber rueckblick.* fehlt im Ablieferordner.');
|
||||||
|
}
|
||||||
$oldGegnerTeams = is_array($oldIssue['gegner_teams'] ?? null) ? array_values($oldIssue['gegner_teams']) : null;
|
$oldGegnerTeams = is_array($oldIssue['gegner_teams'] ?? null) ? array_values($oldIssue['gegner_teams']) : null;
|
||||||
$veranstaltung = null;
|
$veranstaltung = null;
|
||||||
if (is_string($oldVeranstaltung) && $oldVeranstaltung !== '') {
|
if (is_string($oldVeranstaltung) && $oldVeranstaltung !== '') {
|
||||||
@@ -412,6 +428,10 @@ try {
|
|||||||
if ($oldInterview !== null) {
|
if ($oldInterview !== null) {
|
||||||
$aufschlag[] = $live('interview');
|
$aufschlag[] = $live('interview');
|
||||||
}
|
}
|
||||||
|
if (is_array($beitrag)) {
|
||||||
|
$beitrag['image'] = $bild($beitragFoto, 'beitrag', (string) ($beitrag['image']['alt'] ?? $beitrag['heading'] ?? 'Vereinsbeitrag'));
|
||||||
|
$aufschlag[] = $live('beitrag');
|
||||||
|
}
|
||||||
// Aufruf-Seite (Helferaufruf o. Ä.) reiht sich in denselben anzeigenfreien
|
// Aufruf-Seite (Helferaufruf o. Ä.) reiht sich in denselben anzeigenfreien
|
||||||
// Aufschlag ein, direkt hinter Vorwort/Interview — ein redaktioneller
|
// Aufschlag ein, direkt hinter Vorwort/Interview — ein redaktioneller
|
||||||
// Einstieg ohne Werbung dazwischen (chat-gepflegtes `aufruf`-Feld).
|
// Einstieg ohne Werbung dazwischen (chat-gepflegtes `aufruf`-Feld).
|
||||||
@@ -437,6 +457,13 @@ try {
|
|||||||
|
|
||||||
$bloecke[] = ['name' => 'ergebnisse', 'seiten' => [$live('ergebnisse')], 'anzeigen_danach' => true];
|
$bloecke[] = ['name' => 'ergebnisse', 'seiten' => [$live('ergebnisse')], 'anzeigen_danach' => true];
|
||||||
|
|
||||||
|
// Redaktioneller Spielrückblick: Text und Bildnachweis sind Chat-Pflege,
|
||||||
|
// das Foto kommt aus dem Ablieferordner und bleibt in dieser Ausgabe.
|
||||||
|
if ($rueckblick !== null) {
|
||||||
|
$rueckblick['image'] = $bild($rueckblickFoto, 'rueckblick', (string) ($rueckblick['image']['alt'] ?? $rueckblick['heading'] ?? 'Spielrückblick'));
|
||||||
|
$bloecke[] = ['name' => 'rueckblick', 'seiten' => [$live('rueckblick')], 'anzeigen_danach' => true];
|
||||||
|
}
|
||||||
|
|
||||||
// Galerie direkt hinter dem Ergebnis-Rückblick: erst das Ergebnis, dann die
|
// Galerie direkt hinter dem Ergebnis-Rückblick: erst das Ergebnis, dann die
|
||||||
// Bilder dazu. Eigener Block, eine Anzeige dazwischen ist unbedenklich.
|
// Bilder dazu. Eigener Block, eine Anzeige dazwischen ist unbedenklich.
|
||||||
if (!empty($galerie['bilder'])) {
|
if (!empty($galerie['bilder'])) {
|
||||||
@@ -749,6 +776,12 @@ foreach (['mitmachen', 'news', 'termine', 'matchcenter', 'mitglied-werden', 'his
|
|||||||
// (qr-aufruf.png) — der wird bei jedem Lauf ohnehin neu befüllt. club_url()
|
// (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.
|
// wie überall, damit der gedruckte Code auf die echte Domain zeigt.
|
||||||
$aufrufLink = trim((string) ($oldAufruf['link'] ?? ''));
|
$aufrufLink = trim((string) ($oldAufruf['link'] ?? ''));
|
||||||
|
$beitragLink = trim((string) ($beitrag['source_url'] ?? ''));
|
||||||
|
if ($beitragLink !== '') {
|
||||||
|
if (!generate_qr_png($beitragLink, "{$destDir}/qr-beitrag.png")) {
|
||||||
|
$fail('QR-Code für den redaktionellen Beitrag konnte nicht erzeugt werden.');
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($aufrufLink !== '') {
|
if ($aufrufLink !== '') {
|
||||||
if (generate_qr_png(club_url($aufrufLink), "{$destDir}/qr-aufruf.png")) {
|
if (generate_qr_png(club_url($aufrufLink), "{$destDir}/qr-aufruf.png")) {
|
||||||
$log("QR-Code für die Aufruf-Seite (/{$aufrufLink}) erzeugt.");
|
$log("QR-Code für die Aufruf-Seite (/{$aufrufLink}) erzeugt.");
|
||||||
@@ -826,10 +859,12 @@ $issues[] = [
|
|||||||
'qr_image' => $qrImage,
|
'qr_image' => $qrImage,
|
||||||
'vorwort' => $oldVorwort,
|
'vorwort' => $oldVorwort,
|
||||||
'interview' => $oldInterview,
|
'interview' => $oldInterview,
|
||||||
|
'beitrag' => $beitrag,
|
||||||
'aufruf' => $oldAufruf,
|
'aufruf' => $oldAufruf,
|
||||||
'veranstaltung' => $oldVeranstaltung,
|
'veranstaltung' => $oldVeranstaltung,
|
||||||
'cover' => $oldCover,
|
'cover' => $oldCover,
|
||||||
'galerie' => $galerie,
|
'galerie' => $galerie,
|
||||||
|
'rueckblick' => $rueckblick,
|
||||||
'verfasser_photo' => $verfasserPhoto,
|
'verfasser_photo' => $verfasserPhoto,
|
||||||
'termine_snapshot' => $termineSnapshot,
|
'termine_snapshot' => $termineSnapshot,
|
||||||
'images' => $images,
|
'images' => $images,
|
||||||
|
|||||||
@@ -1,7 +1,106 @@
|
|||||||
{
|
{
|
||||||
"fetched_at": 1789636733,
|
"fetched_at": 1792834512,
|
||||||
"username": "tsv08kulmbach",
|
"username": "tsv08kulmbach",
|
||||||
"posts": [
|
"posts": [
|
||||||
|
{
|
||||||
|
"shortcode": "De0vwrajLXG",
|
||||||
|
"caption": "🔴⚫️ Vorschau",
|
||||||
|
"alt": "🔴⚫️ Vorschau",
|
||||||
|
"taken_at": 1792731262,
|
||||||
|
"is_video": false,
|
||||||
|
"image": "img/instagram/De0vwrajLXG.jpg",
|
||||||
|
"url": "https://www.instagram.com/p/De0vwrajLXG/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shortcode": "Derh4uhDLDh",
|
||||||
|
"caption": "🔴⚫️ Ergebnisse \nKann sich sehen lassen 🙌",
|
||||||
|
"alt": "🔴⚫️ Ergebnisse",
|
||||||
|
"taken_at": 1792421998,
|
||||||
|
"is_video": false,
|
||||||
|
"image": "img/instagram/Derh4uhDLDh.jpg",
|
||||||
|
"url": "https://www.instagram.com/p/Derh4uhDLDh/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shortcode": "DehQgDrDB0Z",
|
||||||
|
"caption": "🔴⚫️ Vorschau",
|
||||||
|
"alt": "🔴⚫️ Vorschau",
|
||||||
|
"taken_at": 1792077339,
|
||||||
|
"is_video": false,
|
||||||
|
"image": "img/instagram/DehQgDrDB0Z.jpg",
|
||||||
|
"url": "https://www.instagram.com/p/DehQgDrDB0Z/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shortcode": "DeeldfjMZEm",
|
||||||
|
"caption": "Neue Trikots für unsere E-Jugend! ⚽️😍\n„Euer Ort, euer Verein, euer Trikot“ – unter diesem Motto sorgt die bundesweite TUI-Aktion auch beim TSV 08 Kulmbach für strahlende Gesichter! 👕😊\nDas ReiseCenter Schaffranek sicherte sich unter mehr als 500 Bewerbern einen von 200 Trikotsätzen. Wer sich darüber freuen durfte, entschieden anschließend die Verantwortlichen im ReiseCenter per Los: Sie befüllten die Lostrommel mit den Namen aller Vereine im Kulmbacher Stadtgebiet, die Jugendarbeit bis zur E-Jugend anbieten, und führten die Ziehung durch. Das gezogene Los: TSV 08 Kulmbach! 🍀🙌\nMelanie Schaffranek überreichte die neuen Trikots bei unserem E-Jugendfestival und konnte unsere Nachwuchskicker direkt darin in Aktion erleben. ⚽\nEin herzliches Dankeschön an TUI sowie an Melanie Schaffranek und das gesamte Team des ReiseCenter Schaffranek für diese tolle Unterstützung unserer Jugendarbeit! ❤️🖤\nUnsere E-Jugend trägt das TUI-Lächeln ab sofort auf dem Trikot – und die Freude darüber im Gesicht! 😃\n\n#tsv08kulmbach #jugendfussball #reisetui \nDanke @reisebuero_schaffranek",
|
||||||
|
"alt": "Neue Trikots für unsere E-Jugend! ⚽️😍",
|
||||||
|
"taken_at": 1791987664,
|
||||||
|
"is_video": false,
|
||||||
|
"image": "img/instagram/DeeldfjMZEm.jpg",
|
||||||
|
"url": "https://www.instagram.com/p/DeeldfjMZEm/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shortcode": "DePJUbkjKPc",
|
||||||
|
"caption": "🔴⚫️ Vorschau",
|
||||||
|
"alt": "🔴⚫️ Vorschau",
|
||||||
|
"taken_at": 1791469594,
|
||||||
|
"is_video": false,
|
||||||
|
"image": "img/instagram/DePJUbkjKPc.jpg",
|
||||||
|
"url": "https://www.instagram.com/p/DePJUbkjKPc/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shortcode": "DeG8wTzjAVR",
|
||||||
|
"caption": "Ergebnisse vom Wochenende 🔴⚫️\n\nDerbysieg für unsere Erste 🤹🏻♂️ und unsere Frauen bleiben weiterhin ungeschlagen! 💪🏻",
|
||||||
|
"alt": "Ergebnisse vom Wochenende 🔴⚫️",
|
||||||
|
"taken_at": 1791194571,
|
||||||
|
"is_video": false,
|
||||||
|
"image": "img/instagram/DeG8wTzjAVR.jpg",
|
||||||
|
"url": "https://www.instagram.com/p/DeG8wTzjAVR/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shortcode": "Dd9tmXplrZx",
|
||||||
|
"caption": "🔴⚫️ Vorschau inkl. Stadtderby 🔥",
|
||||||
|
"alt": "🔴⚫️ Vorschau inkl. Stadtderby 🔥",
|
||||||
|
"taken_at": 1790884635,
|
||||||
|
"is_video": false,
|
||||||
|
"image": "img/instagram/Dd9tmXplrZx.jpg",
|
||||||
|
"url": "https://www.instagram.com/p/Dd9tmXplrZx/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shortcode": "Dd1S1UkDOfO",
|
||||||
|
"caption": "🔴⚫️ Ergebnisse",
|
||||||
|
"alt": "🔴⚫️ Ergebnisse",
|
||||||
|
"taken_at": 1790602166,
|
||||||
|
"is_video": false,
|
||||||
|
"image": "img/instagram/Dd1S1UkDOfO.jpg",
|
||||||
|
"url": "https://www.instagram.com/p/Dd1S1UkDOfO/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shortcode": "DdtJFZajIds",
|
||||||
|
"caption": "🔴⚫️-Vorschau",
|
||||||
|
"alt": "🔴⚫️-Vorschau",
|
||||||
|
"taken_at": 1790328620,
|
||||||
|
"is_video": false,
|
||||||
|
"image": "img/instagram/DdtJFZajIds.jpg",
|
||||||
|
"url": "https://www.instagram.com/p/DdtJFZajIds/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shortcode": "Ddjq90MjLvH",
|
||||||
|
"caption": "Maximale Punkteausbeute für uns am Wochenende!\n❤️🖤",
|
||||||
|
"alt": "Maximale Punkteausbeute für uns am Wochenende!",
|
||||||
|
"taken_at": 1790010839,
|
||||||
|
"is_video": false,
|
||||||
|
"image": "img/instagram/Ddjq90MjLvH.jpg",
|
||||||
|
"url": "https://www.instagram.com/p/Ddjq90MjLvH/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shortcode": "DdZ3vzhDAZS",
|
||||||
|
"caption": "🔴⚫️ Vorschau",
|
||||||
|
"alt": "🔴⚫️ Vorschau",
|
||||||
|
"taken_at": 1789681996,
|
||||||
|
"is_video": false,
|
||||||
|
"image": "img/instagram/DdZ3vzhDAZS.jpg",
|
||||||
|
"url": "https://www.instagram.com/p/DdZ3vzhDAZS/"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"shortcode": "DdROjQqjI1_",
|
"shortcode": "DdROjQqjI1_",
|
||||||
"caption": "Ergebnisse 🔴⚫️",
|
"caption": "Ergebnisse 🔴⚫️",
|
||||||
|
|||||||
@@ -132,8 +132,9 @@
|
|||||||
],
|
],
|
||||||
"recommendation": "Bei besonderem Potenzial für Wettkampf- oder Leistungsgruppen sprechen unsere Trainer eine Empfehlung aus.",
|
"recommendation": "Bei besonderem Potenzial für Wettkampf- oder Leistungsgruppen sprechen unsere Trainer eine Empfehlung aus.",
|
||||||
"sessions": [
|
"sessions": [
|
||||||
{ "label": "Mäuse I", "day": "Dienstag", "time": "17:30–18:30 Uhr", "trainers": ["Maxi Roßberg*", "Katrin und Marcus Braese"] },
|
{ "label": "Mäuse I", "day": "Dienstag", "time": "17:30–18:30 Uhr", "trainers": ["Maxi Roßberg*", "Katrin und Markus Braese", "Kristina Dorsch", "Katja Knappich"] },
|
||||||
{ "label": "Mäuse II", "day": "Donnerstag", "time": "16:30–17:30 Uhr", "trainers": ["Franziska Trotzke"] }
|
{ "label": "Mäuse II", "day": "Donnerstag", "time": "16:30–17:30 Uhr", "trainers": ["Franziska Trotzke", "Lisa Benker"] },
|
||||||
|
{ "label": "Mäuse III", "day": "Freitag", "time": "17:00–18:00 Uhr", "trainers": ["Annelie Frisque*", "Eva Wünsch*"] }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -211,9 +212,9 @@
|
|||||||
"contact_email": "tina.kornatz@gmx.de",
|
"contact_email": "tina.kornatz@gmx.de",
|
||||||
"contact_phone": "0163 6032410",
|
"contact_phone": "0163 6032410",
|
||||||
"footnote": "In den Schulferien findet kein Training statt.",
|
"footnote": "In den Schulferien findet kein Training statt.",
|
||||||
"notice_icon": "calendar-event",
|
"notice_tone": "stop",
|
||||||
"notice_title": "Schnuppern nach Absprache",
|
"notice_title": "Hinweis zur Warteliste",
|
||||||
"notice": "Schnuppertraining ist bei uns jederzeit nach vorheriger Absprache möglich. Melde dich davor einfach kurz bei Tina, dann finden wir einen passenden Termin.",
|
"notice": "Aktuell sind alle Plätze beim Trampolinturnen vergeben. Wir nehmen dich oder dein Kind aber gerne auf die Warteliste. Melde dich dafür einfach kurz bei Tina.",
|
||||||
"groups": [
|
"groups": [
|
||||||
{
|
{
|
||||||
"name": "Kinder und Erwachsene",
|
"name": "Kinder und Erwachsene",
|
||||||
|
|||||||
@@ -8628,6 +8628,85 @@ p.stadionzeitung-turnier__label:first-of-type {
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === stadionzeitung-beitrag (redaktioneller Lesetipp) === */
|
||||||
|
.stadionzeitung-beitrag {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
.stadionzeitung-beitrag__figure {
|
||||||
|
margin: 0 0 3cqi;
|
||||||
|
}
|
||||||
|
.stadionzeitung-beitrag .stadionzeitung-page__heading {
|
||||||
|
font-size: 7cqi;
|
||||||
|
}
|
||||||
|
.stadionzeitung-beitrag .stadionzeitung-vorwort__text {
|
||||||
|
font-size: 3.2cqi;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 2.4cqi;
|
||||||
|
}
|
||||||
|
.stadionzeitung-beitrag__foto {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 48cqi;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
.stadionzeitung-beitrag__source {
|
||||||
|
font-size: 2.5cqi;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--clr-text-muted);
|
||||||
|
margin: 0 0 4cqi;
|
||||||
|
}
|
||||||
|
.stadionzeitung-beitrag .stadionzeitung-page__cta {
|
||||||
|
margin-top: auto;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.stadionzeitung-beitrag .stadionzeitung-page__cta-qr {
|
||||||
|
width: 20cqi;
|
||||||
|
height: 20cqi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === stadionzeitung-rueckblick (redaktionelle Seite mit Bildnachweis) === */
|
||||||
|
.stadionzeitung-rueckblick {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Kopf/Trennlinie, Ergebnis und Fließtext verwenden die vorhandenen
|
||||||
|
Heft-Klassen. Hier nur die Anordnung von Bild und Ergebnis definieren. */
|
||||||
|
.stadionzeitung-rueckblick__scoreline {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4cqi;
|
||||||
|
margin-bottom: 4cqi;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stadionzeitung-rueckblick__scoreline .stadionzeitung-ergebnisse__result {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stadionzeitung-rueckblick__figure {
|
||||||
|
margin: 0 0 3cqi;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stadionzeitung-rueckblick__foto {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 65cqi;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stadionzeitung-rueckblick__credit {
|
||||||
|
font-size: 2.5cqi;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--clr-text-muted);
|
||||||
|
margin-top: 1cqi;
|
||||||
|
}
|
||||||
|
|
||||||
/* === stadionzeitung-galerie (Live-Seite, optional: Fotos vom letzten Spiel) === */
|
/* === stadionzeitung-galerie (Live-Seite, optional: Fotos vom letzten Spiel) === */
|
||||||
/* Bento-Raster auf einem 6×4-Gitter: das erste Foto ist der Aufmacher (4×2),
|
/* Bento-Raster auf einem 6×4-Gitter: das erste Foto ist der Aufmacher (4×2),
|
||||||
die übrigen legen sich je nach Anzahl darum. Die Fläche unter dem Kopf gehört
|
die übrigen legen sich je nach Anzahl darum. Die Fläche unter dem Kopf gehört
|
||||||
|
|||||||
BIN
public/assets/img/crests/01S7GFJ77S000000VS548984VTJ68QLL.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
public/assets/img/instagram/Dd1S1UkDOfO.jpg
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
public/assets/img/instagram/Dd9tmXplrZx.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
public/assets/img/instagram/DdZ3vzhDAZS.jpg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
public/assets/img/instagram/Ddjq90MjLvH.jpg
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
public/assets/img/instagram/DdtJFZajIds.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/assets/img/instagram/De0vwrajLXG.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/assets/img/instagram/DeG8wTzjAVR.jpg
Normal file
|
After Width: | Height: | Size: 191 KiB |
BIN
public/assets/img/instagram/DePJUbkjKPc.jpg
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
public/assets/img/instagram/DeeldfjMZEm.jpg
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
public/assets/img/instagram/DehQgDrDB0Z.jpg
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
public/assets/img/instagram/Derh4uhDLDh.jpg
Normal file
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 64 KiB |