/* ===================================================================== streamplan.js — "Live-Radar" NUR für streamplan.html (20.08.2026, Nutzer-Wunsch: "soll viel geiler und spezieller sein, überrasch mich", ergänzt um eine zweite Uhr für ein "spezielles Live-Event": "sehr wichtig ist dass ich auch spezielle live events da auch eintragen kann in der verwaltungsseite so dass da eine zweite uhr erscheint wo die zeit dann für dieses spezielle event läuft"). Zwei 24-Stunden-Ziffernblätter (reines SVG, keine Bild-Assets nötig): - Uhr 1 (immer sichtbar): der feste 20-Uhr-Termin leuchtet dauerhaft als Fixpunkt, ein "Jetzt"-Zeiger bewegt sich mit der echten Uhrzeit der besuchenden Person mit. Live laut /live-status -> Rot/Puls + "Jetzt anschauen". - Uhr 2 (nur wenn in der Verwaltung aktiviert): zählt zu einem exakten Zieldatum+Uhrzeit runter (Tage möglich, nicht nur Stunden), Magenta/ Violett statt Cyan/Gold, damit beide Uhren auf einen Blick unterscheidbar sind ("bissl Farbe"). ===================================================================== */ (function () { const API_BASE_URL = "https://postfach.dogfather-universe.com"; const SVG_NS = "http://www.w3.org/2000/svg"; const CX = 150, CY = 150, R_OUT = 130, R_TICK_IN = 116, R_TICK_MAJOR_IN = 106, R_HAND = 100; function polar(r, stundenAnteil) { const winkel = (stundenAnteil / 24) * 360 - 90; // 0 Uhr oben, im Uhrzeigersinn const rad = (winkel * Math.PI) / 180; return { x: CX + r * Math.cos(rad), y: CY + r * Math.sin(rad) }; } /** Zeichnet die 24 Stundenstriche + einen Fixpunkt-Leuchtpunkt bei "fixStunde" (kann eine Dezimalzahl sein, z.B. 19.5 = 19:30) in die übergebenen -Container. Läuft einmalig pro Uhr. */ function zeichneZiffernblatt(ticksHost, fixHost, fixStunde) { ticksHost.innerHTML = ""; for (let h = 0; h < 24; h++) { const major = h % 6 === 0; const p1 = polar(R_OUT, h); const p2 = polar(major ? R_TICK_MAJOR_IN : R_TICK_IN, h); const line = document.createElementNS(SVG_NS, "line"); line.setAttribute("x1", p1.x); line.setAttribute("y1", p1.y); line.setAttribute("x2", p2.x); line.setAttribute("y2", p2.y); line.setAttribute("class", major ? "radar-tick radar-tick-major" : "radar-tick"); ticksHost.appendChild(line); } fixHost.innerHTML = ""; if (fixStunde != null) { const fixPos = polar(R_OUT + 11, fixStunde); const fixDot = document.createElementNS(SVG_NS, "circle"); fixDot.setAttribute("cx", fixPos.x); fixDot.setAttribute("cy", fixPos.y); fixDot.setAttribute("r", 6.5); fixDot.setAttribute("class", "radar-fixmarker-dot"); fixHost.appendChild(fixDot); } } function zeigerAktualisieren(handEl, dotEl, stundenAnteil) { const spitze = polar(R_HAND, stundenAnteil); handEl.setAttribute("x2", spitze.x); handEl.setAttribute("y2", spitze.y); dotEl.setAttribute("cx", spitze.x); dotEl.setAttribute("cy", spitze.y); } function zweistellig(n) { return String(n).padStart(2, "0"); } /* =================================================================== Uhr 1: täglicher 20-Uhr-Fixtermin (bereits bestehend, unverändert im Verhalten) ================================================================= */ const radar = document.getElementById("stream-radar"); if (radar) { const FIX_STUNDE = 20; zeichneZiffernblatt(document.getElementById("radar-ticks"), document.getElementById("radar-fixmarker"), FIX_STUNDE); const nowHand = document.getElementById("radar-now-hand"); const nowDot = document.getElementById("radar-now-dot"); function zeigerTick() { const jetzt = new Date(); zeigerAktualisieren(nowHand, nowDot, jetzt.getHours() + jetzt.getMinutes() / 60 + jetzt.getSeconds() / 3600); } zeigerTick(); setInterval(zeigerTick, 15000); function naechsterFixTermin() { const jetzt = new Date(); const ziel = new Date(jetzt); ziel.setHours(FIX_STUNDE, 0, 0, 0); if (ziel <= jetzt) ziel.setDate(ziel.getDate() + 1); return ziel; } const countdownEl = document.getElementById("radar-countdown"); const statusText = document.getElementById("radar-status-text"); const statusSub = document.getElementById("radar-status-sub"); const watchBtn = document.getElementById("radar-watch-btn"); let istLive = false; let letzterTitel = null; function countdownTick() { if (istLive) return; const diffMs = naechsterFixTermin() - new Date(); const gesamtSek = Math.max(0, Math.floor(diffMs / 1000)); const std = Math.floor(gesamtSek / 3600); const min = Math.floor((gesamtSek % 3600) / 60); const sek = gesamtSek % 60; countdownEl.textContent = `${zweistellig(std)}:${zweistellig(min)}:${zweistellig(sek)}`; } setInterval(countdownTick, 1000); function texteNeuSetzen() { const t = window.dogiUebersetzen || ((k) => k); if (istLive) { statusText.textContent = t("sp_radar_live"); countdownEl.textContent = letzterTitel || t("sp_radar_live_generic"); statusSub.textContent = ""; watchBtn.hidden = false; } else { statusText.textContent = t("sp_radar_offline"); statusSub.textContent = t("sp_radar_countdown_label"); watchBtn.hidden = true; countdownTick(); } } function liveStatusAnwenden(data) { istLive = !!(data && data.live); letzterTitel = data && data.title ? data.title : null; radar.classList.toggle("ist-live", istLive); texteNeuSetzen(); } function liveStatusLaden() { return fetch(`${API_BASE_URL}/live-status`, { cache: "no-store" }) .then((res) => res.json()) .then(liveStatusAnwenden) .catch(() => { istLive = false; radar.classList.remove("ist-live"); texteNeuSetzen(); }); } liveStatusLaden(); setInterval(liveStatusLaden, 30000); document.addEventListener("visibilitychange", () => { if (!document.hidden) liveStatusLaden(); }); document.addEventListener("dogi-sprache-geaendert", texteNeuSetzen); } /* =================================================================== Uhr 2: "spezielles Live-Event" aus der Verwaltung (20.08.2026, neu). Nur sichtbar, wenn /special-event ein aktives, noch bevorstehendes Event liefert. Zieluhrzeit kann Tage entfernt sein -> Countdown zeigt bei Bedarf "Xd HH:MM:SS" statt nur HH:MM:SS. ================================================================= */ const seWrap = document.getElementById("se-radar-wrap"); if (seWrap) { const seRadar = document.getElementById("se-radar"); const seNowHand = document.getElementById("se-radar-now-hand"); const seNowDot = document.getElementById("se-radar-now-dot"); const seCountdown = document.getElementById("se-radar-countdown"); const seTitel = document.getElementById("se-radar-titel"); const seLink = document.getElementById("se-radar-link"); const seTeilenBtn = document.getElementById("se-radar-teilen"); let zielDatum = null; let aktuellesEvent = null; // für dogiTeilen() beim Klick (21.08.2026) let countdownIntervalId = null; let zeigerIntervalId = null; function seCountdownTick() { if (!zielDatum) return; const diffMs = zielDatum - new Date(); if (diffMs <= 0) { seRadar.classList.add("ist-live"); seCountdown.textContent = window.dogiUebersetzen ? window.dogiUebersetzen("sp_radar_live_generic") : "Live!"; clearInterval(countdownIntervalId); return; } const gesamtSek = Math.floor(diffMs / 1000); const tage = Math.floor(gesamtSek / 86400); const std = Math.floor((gesamtSek % 86400) / 3600); const min = Math.floor((gesamtSek % 3600) / 60); const sek = gesamtSek % 60; seCountdown.textContent = tage > 0 ? `${tage}T ${zweistellig(std)}:${zweistellig(min)}:${zweistellig(sek)}` : `${zweistellig(std)}:${zweistellig(min)}:${zweistellig(sek)}`; } // Der "Jetzt"-Zeiger zeigt wie bei Uhr 1 die ECHTE aktuelle Uhrzeit -- // der magentafarbene Fixpunkt markiert dagegen die Tageszeit, zu der // das spezielle Event stattfindet. So bleibt die Bildsprache zwischen // beiden Uhren konsistent (nur der Fixpunkt unterscheidet sich). function seZeigerTick() { const jetzt = new Date(); zeigerAktualisieren(seNowHand, seNowDot, jetzt.getHours() + jetzt.getMinutes() / 60 + jetzt.getSeconds() / 3600); } function specialEventLaden() { return fetch(`${API_BASE_URL}/special-event`, { cache: "no-store" }) .then((res) => res.json()) .then((data) => { const event = data && data.ok ? data.event : null; if (countdownIntervalId) clearInterval(countdownIntervalId); if (zeigerIntervalId) clearInterval(zeigerIntervalId); if (!event) { seWrap.hidden = true; zielDatum = null; return; } zielDatum = new Date(event.zieldatum); aktuellesEvent = event; seWrap.hidden = false; seRadar.classList.remove("ist-live"); seTitel.textContent = window.dogiFeld ? window.dogiFeld(event.titel) : event.titel?.de || ""; zeichneZiffernblatt(document.getElementById("se-radar-ticks"), document.getElementById("se-radar-fixmarker"), zielDatum.getHours() + zielDatum.getMinutes() / 60); seZeigerTick(); seCountdownTick(); zeigerIntervalId = setInterval(seZeigerTick, 15000); countdownIntervalId = setInterval(seCountdownTick, 1000); if (event.link) { seLink.href = event.link; seLink.hidden = false; } else { seLink.hidden = true; } }) .catch(() => { seWrap.hidden = true; }); } specialEventLaden(); setInterval(specialEventLaden, 60000); // reicht -- Aenderungen in der Verwaltung muessen nicht sekundengenau ankommen document.addEventListener("dogi-sprache-geaendert", () => { if (zielDatum) specialEventLaden(); }); // Teilen (21.08.2026, Nutzer-Wunsch: "... speziellen live events soll man // auch teilen können") -- teilt die Streamplan-Seite selbst (dort steht // ja bereits automatisch das aktuell aktive Event), zusammen mit Titel // im Text, damit klar ist, worum es geht. Nur EIN spezielles Event kann // je aktiv sein, deshalb reicht ein einziger Knopf ohne Slot-Bezug. seTeilenBtn?.addEventListener("click", () => { if (!aktuellesEvent) return; const titel = window.dogiFeld ? window.dogiFeld(aktuellesEvent.titel) : aktuellesEvent.titel?.de || ""; window.dogiTeilen?.( { title: titel, text: `🔴 ${titel} — spezielles Live-Event bei DogFather!`, url: `${location.origin}/streamplan.html`, }, seTeilenBtn ); }); } })();