Files
dogfather-universe/assets/js/streamplan.js
T
DogFatherGitandClaude Opus 5 519e209bce Zweite Live-Uhr fuer "spezielles Live-Event" + mehr Farbe im Radar
Nutzer-Wunsch 20.08.2026: "wuensch mir nur noch bissl farbe und 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 fuer dieses spezielle event laeuft."

- Neue Verwaltungs-Sektion "Spezielles Live-Event": Titel (Deutsch, wird
  automatisch uebersetzt), echtes Datum+Uhrzeit, optionaler Link, Aktiv-
  Schalter -- nur mit Titel + Zukunftsdatum aktivierbar.
- Zweite Uhr auf streamplan.html (Magenta/Violett statt Cyan/Gold, direkt
  neben der bestehenden), nur sichtbar wenn ein Event aktiviert ist und
  das Zieldatum noch nicht vorbei ist. Echter Countdown inkl. Tage (z.B.
  "2T 03:14:59"), "Jetzt"-Zeiger zeigt wie bei Uhr 1 die tatsaechliche
  Uhrzeit, der magentafarbene Fixpunkt markiert die Tageszeit des Events.
  Zieldatum wird als UTC gespeichert -- jede besuchende Person sieht den
  exakt richtigen Countdown in ihrer eigenen Zeitzone.
- "Bissl Farbe": bunter Farbverlauf (Cyan/Violett/Gold) auf dem aeusseren
  Ring statt reinem Grauton, kraeftigerer Sweep-Farbschein.
- Backend: neue routes/special-event.js (getSpecialEventPublic nur bei
  aktiv+zukuenftig, getSpecialEventAdmin fuer Entwuerfe, saveSpecialEvent
  mit Validierung), 13 automatisierte Tests gegen eine Fake-DB bestanden.
- Bugfix unterwegs gefunden (Playwright-Screenshot, wiederholtes Muster
  auf dieser Seite): .stream-radar-wrap blieb trotz [hidden]-Attribut
  sichtbar (display:block ueberschreibt die eingebaute [hidden]-Regel bei
  gleicher Spezifitaet) -- explizite Regel ergaenzt, per Playwright erneut
  verifiziert (display:none bestaetigt).

Backend-Teil (server-internal/) noch ohne Deploy-Zugriff -- Dogi muss ihn
manuell auf dogiintern ausrollen, sonst bleibt die zweite Uhr unsichtbar.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-20 23:24:17 +02:00

231 lines
9.9 KiB
JavaScript

/* =====================================================================
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 <g>-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");
let zielDatum = null;
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);
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();
});
}
})();