Files
tsv08kulmbach-website/public/assets/js/stadionzeitung-viewer.js

119 lines
4.5 KiB
JavaScript
Raw Permalink 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
/* Stadionzeitung-Swipe-Viewer: rüstet den gestapelten No-JS-Fallback
([data-stadionzeitung-viewer]) auf einen horizontalen Swiper um. Kein
eigener Touch-Gesten-Code natives CSS-Scroll-Snap (components.css) macht
das Swipen butterweich, JS liefert nur Zähler, Pfeiltasten und die
Vor-/Zurück-Buttons obendrauf. Ohne JS bleiben alle Seiten normal gestapelt. */
(function () {
'use strict';
function initViewer(root) {
var track = root.querySelector('[data-viewer-track]');
var pages = Array.prototype.slice.call(root.querySelectorAll('[data-viewer-page]'));
var controls = root.querySelector('[data-viewer-controls]');
var counter = root.querySelector('[data-viewer-current]');
var prevBtn = root.querySelector('[data-viewer-prev]');
var nextBtn = root.querySelector('[data-viewer-next]');
var fullBtn = root.querySelector('[data-viewer-full]');
if (!track || pages.length <= 1) {
return;
}
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var index = 0;
var ticking = false;
function updateUI() {
if (counter) {
counter.textContent = String(index + 1);
}
if (prevBtn) {
prevBtn.disabled = index === 0;
}
if (nextBtn) {
nextBtn.disabled = index === pages.length - 1;
}
}
function scrollToIndex(i) {
index = Math.max(0, Math.min(pages.length - 1, i));
track.scrollTo({ left: index * track.clientWidth, behavior: reduce ? 'auto' : 'smooth' });
updateUI();
}
track.addEventListener('scroll', function () {
if (ticking) {
return;
}
ticking = true;
requestAnimationFrame(function () {
index = Math.round(track.scrollLeft / track.clientWidth);
updateUI();
ticking = false;
});
}, { passive: true });
window.addEventListener('resize', function () {
// Re-snap ohne Animation, z. B. nach Rotation am Handy — sonst
// steht der Track auf einer krummen Zwischenposition.
track.scrollTo({ left: index * track.clientWidth, behavior: 'auto' });
});
if (prevBtn) {
prevBtn.addEventListener('click', function () { scrollToIndex(index - 1); });
}
if (nextBtn) {
nextBtn.addEventListener('click', function () { scrollToIndex(index + 1); });
}
/* Vollbild-Lesemodus. Bewusst NICHT die Fullscreen-API: iOS Safari
unterstützt requestFullscreen() nur auf <video>, der Knopf wäre auf
iPhones wirkungslos. Zwei Klassen und CSS erledigen dasselbe überall
gleich und die Adressleiste des Browsers bleibt erreichbar. */
function setFull(on) {
document.body.classList.toggle('is-sz-full', on);
root.classList.toggle('is-full', on);
if (fullBtn) {
fullBtn.setAttribute('aria-pressed', on ? 'true' : 'false');
}
// Die Track-Breite hat sich gerade geändert — ohne Neu-Einrasten
// stünde der Swiper auf einer krummen Zwischenposition.
requestAnimationFrame(function () {
track.scrollTo({ left: index * track.clientWidth, behavior: 'auto' });
});
}
if (fullBtn) {
fullBtn.addEventListener('click', function () {
setFull(!root.classList.contains('is-full'));
});
}
document.addEventListener('keydown', function (event) {
var active = document.activeElement;
if (active && /^(INPUT|TEXTAREA|SELECT)$/.test(active.tagName)) {
return;
}
if (event.key === 'Escape' && root.classList.contains('is-full')) {
event.preventDefault();
setFull(false);
if (fullBtn) {
fullBtn.focus();
}
} else if (event.key === 'ArrowLeft') {
scrollToIndex(index - 1);
} else if (event.key === 'ArrowRight') {
scrollToIndex(index + 1);
}
});
root.classList.add('is-enhanced');
if (controls) {
controls.hidden = false;
}
updateUI();
}
Array.prototype.slice.call(document.querySelectorAll('[data-stadionzeitung-viewer]')).forEach(initViewer);
})();