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:
2026-07-31 14:55:27 +02:00
parent 3fb480dd78
commit a101b0c3ec
861 changed files with 75244 additions and 487 deletions

View File

@@ -17,6 +17,38 @@
font-display: swap;
}
/* Nur für Fließtext auf der Stadionzeitungs-Vorwort-Seite (--ff-serif,
tokens.css) — self-hosted wie Coolvetica/Abel, keine Google-Fonts-CDN.
Quelle: PT Serif (ParaType, OFL-Lizenz). */
@font-face {
font-family: 'PT Serif';
src: url('../fonts/pt-serif.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'PT Serif';
src: url('../fonts/pt-serif-italic.woff2') format('woff2');
font-weight: 400;
font-style: italic;
font-display: swap;
}
/* Nur für die Unterschrift des Vorwort-Unterzeichners in der Stadionzeitung
(--ff-signature, tokens.css) — handschriftliche Signatur-Anmutung wie in
der Canva-Vorlage. Self-hosted, keine Google-Fonts-CDN.
Quelle: Mr Dafoe (Sudtipos, OFL-Lizenz), Latin-Subset inkl. Umlaute —
von Felix aus sechs OFL-Kandidaten ausgewählt (29.07.2026). */
@font-face {
font-family: 'Mr Dafoe';
src: url('../fonts/mr-dafoe.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
body {
font-family: var(--ff-body);
font-size: var(--fs-600);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,112 @@
/* ============================================================
DATEIEN — Marken-Skin für die Dateiablage (Files Gallery, /dateien)
KEIN Teil des Seiten-Layouts und deshalb bewusst die 7. CSS-Datei: geladen wird
sie ausschließlich von storage/dateien/system/include/head.html, also in einem
fremden Dokument, das wir nicht rendern. Sie steht NACH der files.css der App und
gewinnt damit über die Variablen.
Zusammen mit tokens.css geladen (Farbwerte kommen von dort, nie als Hex von hier).
Die App ist durchgängig variablengesteuert — wir setzen deshalb ihre Variablen und
fassen ihre Selektoren fast nicht an. Das übersteht Updates.
============================================================ */
/* Die Schrift bleibt bewusst die der App (system-ui): ein Datei-Manager ist ein
Werkzeug, nicht eine Seite der Website — nah am Standard heißt hier vertraute
Bedienung, und die Systemschrift ist in kleinen Größen und langen Dateinamen
ohnehin robuster als unsere Abel. Die Marke tragen Wappen und Rot. */
:root {
/* Farbton der App auf das Markenrot ziehen. Die App leitet daraus Flächen,
Ränder und Ordnerfarben ab (hsl(var(--hue), 20%, …)) — alles bekommt einen
warmen Rotstich statt Blau, ohne dass wir einzelne Regeln überschreiben. */
--hue: var(--clr-accent-h);
/* Der Akzent selbst (Buttons, Links, Auswahl) exakt auf --clr-accent.
Die App wählt je Theme eine der drei Varianten. */
--hsl-primary-dark: var(--clr-accent-hsl);
--hsl-primary-light: var(--clr-accent-hsl);
--hsl-primary-contrast: var(--clr-accent-hsl);
/* --- Kontrast (gemessen, nicht geschätzt) ---
Die App leitet Flächen UND Ordnerfarben aus --hue ab. Mit dem Markenrot als
Farbton stand ein dunkelroter Ordner auf dunkelroter Fläche: 1,07:1, praktisch
unsichtbar. Deshalb hier drei Korrekturen. */
/* Flächen etwas tiefer als das Seiten-Grau (#222 ≈ 13 %): der Ordner ist ein
gefülltes Rot, und Rot ist von Natur aus dunkel — es braucht den Abstand nach
unten. Bei 13 % kommt das Rot nur auf 3,03:1, bei 10 % auf 3,30:1. */
--surface-base-lightness-dark: 10%;
/* Text heller als die Vorgabe der App (80 %): hebt Dateinamen auf 11,97:1 und
zieht die abgeleiteten Meta-Töne (Brotkrumen, Anzahl-Badge) mit nach oben. */
--text-base-lightness-dark: 88%;
/* Ordner im Markenrot. Vorderseite ist die große Fläche und trägt die Erkennbarkeit
(gemessen 3,91:1 — über den 3:1, die WCAG 1.4.11 für Grafik verlangt), Rückseite dunkler
als Kante. Erste Zeile ist der Fallback für Browser ohne color-mix(). */
--svg-folder-fg-dark: var(--clr-accent);
--svg-folder-fg-dark: color-mix(in srgb, var(--clr-accent) 80%, white);
--svg-folder-fg-large-dark: var(--clr-accent);
--svg-folder-fg-large-dark: color-mix(in srgb, var(--clr-accent) 80%, white);
--svg-folder-bg-dark: var(--clr-accent-dark);
--svg-folder-bg-large-dark: var(--clr-accent-dark);
}
/* --- Login-Seite ---
Die Login-Seite der App rendert nur „Einloggen" und ihr Formular; sie bindet
include/footer.html nicht ein (index.php:561 rendert eine eigene, knappe Seite) und
kennt keinen Platz für eigenes Markup. Damit man sieht, wessen Ablage das ist, kommt
das Vereinswappen als dekorative Fläche darüber — Bild statt CSS-Textinhalt, damit
nichts Sinntragendes im Stylesheet landet. */
.page-login-body .login-container::before {
content: '';
display: block;
width: 96px;
height: 124px;
margin: 0 auto 1.5rem;
background: url('../img/logo.svg') center / contain no-repeat;
}
/* --- Fußzeile (storage/dateien/system/include/footer.html) ---
.custom-footer-fixed ist der Hook der App (fest am unteren Rand, volle Breite).
Wir geben ihm nur Typografie und Abstand. */
.tsv-footer {
display: flex;
align-items: center;
justify-content: center;
padding: 0.375rem 1rem;
border-top: 2px solid hsl(var(--clr-accent-hsl));
line-height: 1.4;
}
.tsv-footer a {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: var(--text1);
text-decoration: none;
white-space: nowrap;
}
/* Wappen in der Fußzeile: echtes <img> mit Maßen (kein Layout-Sprung), dekorativ —
der Linktext trägt die Bedeutung. Die offizielle `logo`-Option der App fällt hier
aus: sie wird über _c.config in js/custom.js gesetzt, und dieser Hook braucht wie
css/custom.css eine öffentliche URL, die unser storage_path nicht hat. */
.tsv-footer a img {
height: 2.6em; /* darunter wird das Wappen zum Fleck */
width: auto;
}
.tsv-footer a:hover,
.tsv-footer a:focus-visible {
color: hsl(var(--clr-accent-hsl));
text-decoration: underline;
}
/* Die App reserviert keinen Platz für die feste Fußzeile — sonst verschwindet die
letzte Dateizeile bzw. der letzte Menüpunkt darunter. */
#files-container,
#sidebar-inner {
padding-bottom: 4rem;
}

View File

@@ -22,7 +22,7 @@
/* --- Header --- */
.site-header {
position: absolute;
inset: 20px 0 auto 0;
inset: var(--header-top) 0 auto 0;
z-index: 10;
}
@@ -76,6 +76,13 @@
margin-left: auto;
}
/* CTAs im Menü-Overlay: nur im Burger-Modus sichtbar (dort ersetzen sie die
Header-Buttons — Best Practice: mobil kein Dauer-Button neben dem Burger,
die Handlungsaufrufe stehen prominent am Ende des Menüs). */
.site-nav__ctas {
display: none;
}
.site-nav__toggle {
display: none;
}
@@ -262,12 +269,22 @@
justify-content: space-between;
}
/* Header-CTAs weichen in den Burger (site-nav__ctas) — kein Dauer-Button
neben dem Burger, der Header bleibt ruhig: Logo links, Burger rechts. */
.site-header__actions {
order: 2;
margin-left: auto;
/* CTAs bleiben über dem Menü-Overlay sichtbar */
position: relative;
z-index: 30;
display: none;
}
.site-nav__ctas {
display: flex;
flex-direction: column;
gap: var(--space-s);
margin-top: var(--space-2xl);
}
.site-nav__ctas .btn {
justify-content: center;
font-size: var(--btn-font-lg);
}
/* Logo/Brand ebenfalls über dem Overlay halten — Orientierung bei offenem Menü. */
@@ -337,6 +354,10 @@
inset: 0;
z-index: 20;
flex-direction: column;
/* Desktop-Leiste wrappt (horizontale Nav) — im Overlay MUSS das aus,
sonst bricht die Spalte bei Überlänge (offenes Untermenü + CTAs)
in eine zweite Spalte um, statt zu scrollen. */
flex-wrap: nowrap;
justify-content: flex-start;
align-items: stretch;
gap: 0;
@@ -492,15 +513,6 @@
.site-header__name {
display: none;
}
/* CTA(s) kompakter neben dem Burger. */
.site-header__actions a:not(:last-child) {
display: none;
}
.site-header__actions .btn {
font-size: var(--btn-font-sm);
}
}
/* --- Back-to-top --- */

View File

@@ -8,6 +8,9 @@
--clr-accent: #e20612; /* Marken-Rot (alte DB: primary_accent) */
--clr-accent-dark: #b00510; /* Hover/Active auf Rot */
--clr-accent-rgb: 226 6 18; /* für rgb(var(--clr-accent-rgb) / α) */
--clr-accent-hsl: 356.7 94.8% 45.5%; /* dasselbe Rot als H S L */
--clr-accent-h: 356.7; /* nur der Farbton — die Dateiablage (/dateien)
leitet daraus ihre Flächen ab, siehe dateien.css */
--clr-bg: #222222; /* Grund-Hintergrund (mit Textur) */
--clr-bg-deep: #161616; /* abgesetzte Sektionen */
--clr-bg-footer: #000000;
@@ -16,19 +19,29 @@
--clr-text-muted: #b3b3b3; /* 7.7:1 auf --clr-bg */
--clr-text-faint: #8c8c8c; /* 4.5:1 auf --clr-bg (AA) */
--clr-sepia-bg: #f3efe4; /* Timeline-Museumsrahmen (Foto-Mount) */
--clr-logo-card: #ffffff; /* Partner-Logowall: Farb-Logos brauchen neutralen weißen Grund */
--clr-photo-frame: #faf9f6; /* Polaroid-Rahmen (split--polaroid): fast reinweiß, minimal warm */
--clr-tape: rgb(255 250 235 / 0.4); /* Klebestreifen auf Polaroids: milchig, leicht vergilbt */
--clr-sepia-text: #3a372f; /* Timeline-Text auf hellem Sepia-Grund */
--clr-glass-bg: rgb(0 0 0 / 0.22); /* dunkles Glas (dunkler als der Grund) */
--clr-glass-border: rgb(255 255 255 / 0.14); /* sehr dezenter Card-Rand — Glow/Sheen tragen die Kontur */
--clr-glass-border-strong: rgb(255 255 255 / 0.45); /* Back-to-Top, position-/group-card etc.: ≥3:1 (WCAG 1.4.11) */
--clr-field-bg: rgb(255 255 255 / 0.07); /* Formularfelder: sanfte Füllfläche (etwas heller als das Glas) */
--clr-field-bg-solid: #313131; /* dieselbe Fläche, aber OPAK (--clr-field-bg über --clr-bg gerechnet) —
nötig für den Autofill-Fix (box-shadow inset kann keine Transparenz
über die native Browser-Füllung legen, siehe components.css) */
--clr-field-border: rgb(255 255 255 / 0.22); /* Feld-Rand weich — Fläche + Rand machen das Feld erkennbar */
--clr-field-border-hover: rgb(255 255 255 / 0.4); /* Feld-Hover: dezent */
--clr-lightbox-bg: rgb(0 0 0 / 0.93); /* Bild-Großansicht (lightbox): fast schwarz, damit das Foto
allein wirkt und der Seitenhintergrund nicht durchdrückt */
--clr-error: #ff8a8a; /* Formularfehler, lesbar auf dunkel */
--clr-success: #8ce99a;
/* --- Typografie --- */
--ff-heading: 'Coolvetica', 'Arial Narrow', system-ui, sans-serif;
--ff-body: 'Abel', 'Helvetica Neue', system-ui, sans-serif;
--ff-serif: 'PT Serif', Georgia, 'Times New Roman', serif; /* nur Stadionzeitung-Vorwort-Fließtext */
--ff-signature: 'Mr Dafoe', cursive; /* nur Unterschrift des Vorwort-Unterzeichners (Stadionzeitung) */
--fs-300: 0.8125rem; /* 13px Buttons klein */
--fs-400: 0.9375rem; /* 15px Footer, Meta */
--fs-500: 1.125rem; /* 18px Navigation */
@@ -111,6 +124,7 @@
/* --- Form & Tiefe --- */
--radius-btn: 8px;
--radius-card: 15px;
--radius-photo: 4px; /* Polaroid-/Fotorahmen (split--polaroid): fast eckig wie ein echter Abzug */
--shadow: 0 0 14px rgb(0 0 0 / 0.15);
--shadow-card: 0 14px 38px rgb(0 0 0 / 0.42), inset 0 1px 0 rgb(255 255 255 / 0.18); /* Hover-Lift + Top-Sheen */
@@ -122,8 +136,15 @@
--container-max: 1200px;
--container-pad: var(--space-s);
--space-section: var(--space-6xl);
--header-h: 60px;
--logo-h: 72px;
--header-h: 60px; /* Mindesthöhe der Kopfleiste (.site-header__inner) */
--logo-h: 72px; /* Logo — größer als --header-h, bestimmt also faktisch die Höhe */
--header-top: 20px; /* Versatz der Leiste von oben (.site-header) */
/* Was die Kopfleiste vom Seitenanfang wegnimmt. NICHT --header-h dafür
nehmen: die Leiste ist wegen des Logos 72px hoch, nicht 60px, und sitzt
zusätzlich 20px versetzt — wer mit --header-h rechnet, landet 32px zu
hoch und schiebt seinen Inhalt unter das Logo. Abgeleitet statt fest,
damit es nicht auseinanderläuft, wenn Logo oder Versatz sich ändern. */
--header-space: calc(var(--header-top) + var(--logo-h));
/* --- Bewegung --- */
--transition: 300ms ease;