"Event des Jahres": Aktivieren-Schalter, leer bis befuellt, Detail-Fenster

Nutzer-Wunsch 20.08.2026: "diese zwei kisten sollen leer sein solang wie
ich nichts rein setze, am besten sollen die sogar immer nur erscheinen
wenn ich was rein setze und sie aktiviere ... wen ich drauf druecke dass
dann ein kleines fenster aufgeht wo das bild bissl groesser ist mit einem
groesseren text und beschreibung, und mit einem link ... die kacheln
sollen auch bissl spezieller sein."

- Kein hartkodierter Standardinhalt mehr auf der Startseite -- die Kachel
  UND der ganze Abschnitt bleiben komplett unsichtbar, bis mindestens ein
  Event in der Verwaltung ausgefuellt UND ueber einen neuen "Aktiv"-Schalter
  freigeschaltet ist. Genau 1 aktives Event -> zentrierte Einzelkachel
  statt halbleerem Zwei-Spalten-Raster.
- Klick auf eine Kachel oeffnet jetzt ein Detail-Fenster (groesseres Bild,
  groesserer Titel/Text, optionaler direkter Link) statt sofort
  wegzunavigieren.
- Kacheln bekommen einen goldenen Trophaeen-Akzent + dezenten wandernden
  Lichtschimmer statt der neutralen Standardkarten-Optik.
- Bugfix unterwegs gefunden (Playwright-Screenshot): .jahres-event-card
  blieb trotz [hidden]-Attribut sichtbar (dieselbe Ursache wie der
  frühere .jahres-event-bild-Bug: display:block ueberschreibt die
  eingebaute [hidden]-Regel bei gleicher Spezifitaet) -- explizite
  [hidden]-Regel ergaenzt.
- Backend: server-internal/routes/events.js liefert oeffentlich NUR noch
  aktivierte Slots aus (getEventsPublic), neuer authentifizierter Endpunkt
  getEventsAdmin liefert der Verwaltung auch Entwuerfe zum Vorausfuellen.
  10 automatisierte Tests gegen eine Fake-DB bestanden.

Backend-Teil (server-internal/) noch ohne Deploy-Zugriff -- Dogi muss ihn
manuell auf dogiintern ausrollen, sonst bleibt die Startseite beim alten
Verhalten (immer beide Slots zeigen, kein Aktiv-Schalter in der Verwaltung).

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-20 22:43:28 +02:00
co-authored by Claude Opus 5
parent 208b5ba4ed
commit b3e191d3ca
39 changed files with 545 additions and 318 deletions
+134 -48
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="DOGFATHER UNIVERSE: die Heimat von DogFather als Streamer, der HasiDog-Community und Manager DogFather bei Spicy Media." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260820d" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260820d" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260820d" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260820d" />
<link rel="stylesheet" href="assets/css/main.css?v=20260820f" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260820f" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260820f" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260820f" />
</head>
<body class="start-seite">
<div id="site-header"></div>
@@ -82,34 +82,56 @@
</a>
</div>
<!-- "Event des Jahres": volle Breite wie die 3 Welt-Kacheln zusammen, zeigt bewusst
IMMER genau zwei Events (Nutzer-Wunsch 20.08.2026). Gleiche Kartenoptik/-Tag wie
die Welt-Kacheln oben, damit es wie ein natürlicher vierter Baustein wirkt statt
wie ein fremdes Element. -->
<div class="jahres-events">
<!-- "Event des Jahres": volle Breite wie die 3 Welt-Kacheln zusammen. Seit
20.08.2026 (Nutzer-Wunsch) NUR sichtbar, wenn Dogi/VanVan mindestens ein
Event in der Verwaltung ausgefüllt UND aktiviert haben -- kein
Standard-/Platzhalterinhalt mehr, kompletter Abschnitt bleibt bis dahin
unsichtbar (siehe zeigeJahresEvents() weiter unten). Klick öffnet ein
großes Detail-Fenster (Bild/Titel/Text größer, optionaler direkter Link)
statt sofort wegzunavigieren. -->
<div class="jahres-events" id="jahres-events" hidden>
<div class="jahres-events-head">
<span class="tag" data-i18n="idx_jahres_events_tag">🏆 Event des Jahres</span>
</div>
<div class="jahres-events-grid">
<a class="jahres-event-card" href="zeitreise.html" id="jahres-event-1">
<div class="jahres-events-grid" id="jahres-events-grid">
<button type="button" class="jahres-event-card" id="jahres-event-1" data-slot="1" hidden>
<span class="jahres-event-shine" aria-hidden="true"></span>
<img class="jahres-event-bild" alt="" hidden />
<span class="jahres-event-datum" data-i18n="idx_jahres_event1_datum">7. März 2026</span>
<h3 data-i18n="idx_jahres_event1_h3">Der 28-Stunden-Stream</h3>
<p data-i18n="idx_jahres_event1_p">DogFather stellt sich einer besonderen Herausforderung und streamt 28 Stunden am Stück.</p>
<span class="jahres-event-mehr" data-i18n="idx_jahres_events_mehr">Zur Zeitreise →</span>
</a>
<a class="jahres-event-card" href="zeitreise.html" id="jahres-event-2">
<span class="jahres-event-datum"></span>
<h3></h3>
<p></p>
<span class="jahres-event-mehr" data-i18n="idx_jahres_events_mehr">Mehr erfahren →</span>
</button>
<button type="button" class="jahres-event-card" id="jahres-event-2" data-slot="2" hidden>
<span class="jahres-event-shine" aria-hidden="true"></span>
<img class="jahres-event-bild" alt="" hidden />
<span class="jahres-event-datum" data-i18n="idx_jahres_event2_datum">27. Juli bis 2. August 2026</span>
<h3 data-i18n="idx_jahres_event2_h3">Der Creator Cup</h3>
<p data-i18n="idx_jahres_event2_p">Gemeinsam mit Cidem organisiert DogFather den Creator Cup für die Streamer von Spicy Media.</p>
<span class="jahres-event-mehr" data-i18n="idx_jahres_events_mehr">Zur Zeitreise →</span>
</a>
<span class="jahres-event-datum"></span>
<h3></h3>
<p></p>
<span class="jahres-event-mehr" data-i18n="idx_jahres_events_mehr">Mehr erfahren →</span>
</button>
</div>
</div>
</div>
</section>
<!-- Detail-Fenster für "Event des Jahres" -- bewusst außerhalb der Hero-Sektion
(die hat eigene transform-/overflow-Effekte für die Polarlicht-/Nebel-
Animation, die ein position:fixed-Fenster aus der Zentrierung bringen
könnten). -->
<div class="jahres-event-modal-overlay" id="jahres-event-modal-overlay" hidden>
<div class="jahres-event-modal" role="dialog" aria-modal="true" aria-labelledby="jahres-event-modal-titel">
<button type="button" class="jahres-event-modal-close" id="jahres-event-modal-close" aria-label="Schließen">✕</button>
<img class="jahres-event-modal-bild" id="jahres-event-modal-bild" alt="" hidden />
<div class="jahres-event-modal-body">
<span class="jahres-event-modal-datum" id="jahres-event-modal-datum"></span>
<h2 id="jahres-event-modal-titel"></h2>
<p id="jahres-event-modal-text"></p>
<a class="btn btn-primary jahres-event-modal-link" id="jahres-event-modal-link" href="#" target="_blank" rel="noopener" hidden></a>
</div>
</div>
</div>
<!-- ================= CHARAKTERE: DOGFATHER-HUSKY & HASIDOG-HASE ================= -->
<section class="section-tight">
<div class="container text-center">
@@ -271,11 +293,11 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/data-modis.js?v=20260820d"></script>
<script src="assets/js/data-scouts.js?v=20260820d"></script>
<script src="assets/js/data-aktionen.js?v=20260820d"></script>
<script src="assets/js/i18n-index.js?v=20260820d"></script>
<script src="assets/js/main.js?v=20260820d"></script>
<script src="assets/js/data-modis.js?v=20260820f"></script>
<script src="assets/js/data-scouts.js?v=20260820f"></script>
<script src="assets/js/data-aktionen.js?v=20260820f"></script>
<script src="assets/js/i18n-index.js?v=20260820f"></script>
<script src="assets/js/main.js?v=20260820f"></script>
<script>
// Gleiche URL wie in assets/js/forms.js / postfach.html — läuft seit
// 05.08.2026 auf dem eigenen Server statt auf Cloudflare Workers.
@@ -358,51 +380,115 @@ document.addEventListener("DOMContentLoaded", () => {
}
/* "Event des Jahres": seit 20.08.2026 in der Verwaltung pflegbar (Bild,
Text, Link je Slot, siehe verwaltung.html + server-internal/routes/
events.js). Lädt beim Seitenaufruf, was Dogi/VanVan zuletzt gespeichert
haben — bleibt der Fetch aus (noch nie gespeichert, Dienst nicht
erreichbar) oder ist ein Slot noch leer, bleibt bewusst der fest
eingebaute Standardinhalt aus dem HTML stehen (dieselben zwei echten
Events wie bisher) statt einer kaputten/leeren Kachel. */
function wendeJahresEventAn(slot, daten) {
if (!daten) return; // kein gespeicherter Eintrag -> Standardinhalt im HTML bleibt
Text, Link, Aktiv-Schalter je Slot, siehe verwaltung.html +
server-internal/routes/events.js). Nutzer-Wunsch 20.08.2026: "die
kisten sollen leer sein solang wie ich nichts rein setze, am besten
sollen die sogar immer nur erscheinen wenn ich was rein setze und sie
aktiviere" — der Server liefert über /events-of-year ohnehin NUR
aktivierte Slots (inaktive/leere kommen als null), hier also einfach:
kein Eintrag -> Kachel bleibt versteckt, kompletter Abschnitt bleibt
versteckt, wenn beide Slots leer sind. Klick öffnet ein großes
Detail-Fenster statt sofort zu navigieren. */
const feldJahresEvent = (x) => (window.dogiFeld ? window.dogiFeld(x) : x || "");
let jahresEventsDaten = [null, null];
function zeigeJahresEventKachel(slot, daten) {
const karte = document.getElementById(`jahres-event-${slot}`);
if (!karte) return;
const feld = (x) => (window.dogiFeld ? window.dogiFeld(x) : x || "");
if (!daten) {
karte.hidden = true;
return;
}
const bild = karte.querySelector(".jahres-event-bild");
if (daten.bild) {
bild.src = daten.bild;
bild.alt = feld(daten.titel);
bild.alt = feldJahresEvent(daten.titel);
bild.hidden = false;
} else {
bild.hidden = true;
}
karte.querySelector(".jahres-event-datum").textContent = daten.datum || "";
karte.querySelector("h3").textContent = feld(daten.titel);
karte.querySelector("p").textContent = feld(daten.text);
if (daten.link) karte.href = daten.link;
karte.querySelector("h3").textContent = feldJahresEvent(daten.titel);
karte.querySelector("p").textContent = feldJahresEvent(daten.text);
karte.hidden = false;
}
function aktualisiereJahresEventsSichtbarkeit() {
const aktiveSlots = jahresEventsDaten.filter(Boolean).length;
const abschnitt = document.getElementById("jahres-events");
const grid = document.getElementById("jahres-events-grid");
abschnitt.hidden = aktiveSlots === 0;
// Genau 1 aktives Event: Kachel zentriert und etwas größer statt halb
// leerem Raster (Nutzer-Wunsch: "kacheln sollen bissl spezieller sein").
grid.classList.toggle("jahres-events-grid-einzeln", aktiveSlots === 1);
}
function jahresEventModalOeffnen(slot) {
const daten = jahresEventsDaten[slot - 1];
if (!daten) return;
const bild = document.getElementById("jahres-event-modal-bild");
if (daten.bild) {
bild.src = daten.bild;
bild.alt = feldJahresEvent(daten.titel);
bild.hidden = false;
} else {
bild.hidden = true;
}
document.getElementById("jahres-event-modal-datum").textContent = daten.datum || "";
document.getElementById("jahres-event-modal-titel").textContent = feldJahresEvent(daten.titel);
document.getElementById("jahres-event-modal-text").textContent = feldJahresEvent(daten.text);
const linkEl = document.getElementById("jahres-event-modal-link");
if (daten.link) {
linkEl.href = daten.link;
linkEl.textContent = feldJahresEvent(window.I18N_PAGE?.idx_jahres_event_modal_link) || "Direkt ansehen →";
linkEl.hidden = false;
} else {
linkEl.hidden = true;
}
const overlay = document.getElementById("jahres-event-modal-overlay");
overlay.hidden = false;
requestAnimationFrame(() => overlay.classList.add("offen"));
document.body.classList.add("modal-offen");
}
function jahresEventModalSchliessen() {
const overlay = document.getElementById("jahres-event-modal-overlay");
overlay.classList.remove("offen");
document.body.classList.remove("modal-offen");
setTimeout(() => { overlay.hidden = true; }, 220);
}
document.getElementById("jahres-event-1")?.addEventListener("click", () => jahresEventModalOeffnen(1));
document.getElementById("jahres-event-2")?.addEventListener("click", () => jahresEventModalOeffnen(2));
document.getElementById("jahres-event-modal-close")?.addEventListener("click", jahresEventModalSchliessen);
document.getElementById("jahres-event-modal-overlay")?.addEventListener("click", (e) => {
if (e.target.id === "jahres-event-modal-overlay") jahresEventModalSchliessen();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && !document.getElementById("jahres-event-modal-overlay").hidden) jahresEventModalSchliessen();
});
if (API_BASE_URL) {
let letzteJahresEvents = null;
const ladeJahresEvents = () =>
fetch(`${API_BASE_URL}/events-of-year`, { cache: "no-store" })
.then((res) => res.json())
.then((data) => {
if (!data?.ok) return;
letzteJahresEvents = data.events;
wendeJahresEventAn(1, data.events[0]);
wendeJahresEventAn(2, data.events[1]);
jahresEventsDaten = data.events;
zeigeJahresEventKachel(1, data.events[0]);
zeigeJahresEventKachel(2, data.events[1]);
aktualisiereJahresEventsSichtbarkeit();
})
.catch(() => {
/* Standardinhalt aus dem HTML bleibt stehen -- kein sichtbarer Fehler nötig. */
/* Kein Fehler nötig -- der Abschnitt bleibt einfach unsichtbar. */
});
ladeJahresEvents();
// Bei Sprachwechsel mit den zuletzt geladenen Daten neu rendern (kein
// erneuter Server-Aufruf nötig) — Titel/Text stehen ja bereits in allen
// Sprachen im geladenen Objekt.
document.addEventListener("dogi-sprache-geaendert", () => {
if (!letzteJahresEvents) return;
wendeJahresEventAn(1, letzteJahresEvents[0]);
wendeJahresEventAn(2, letzteJahresEvents[1]);
zeigeJahresEventKachel(1, jahresEventsDaten[0]);
zeigeJahresEventKachel(2, jahresEventsDaten[1]);
});
}