window.addEventListener("load", () => { const params = new URLSearchParams(window.location.search); if (params.get("lofi") === "true") { zeigeYouTubeVideo(); } }); const stripTags = (html) => (typeof html === "string" ? html.replace(/<[^>]*>/g, "") : ""); const formatBid = (value) => { const num = Number(value); if (Number.isNaN(num)) return "n/a"; const abs = Math.abs(num); if (abs >= 100) return num.toFixed(2); if (abs >= 10) return num.toFixed(3); return num.toFixed(4); }; const instrumentEmoji = (inst) => { const map = { "USD/JPY": "$/¥", "OIL/USD": "🛢️", "XAU/USD": "⛏️", "USD/EUR": "€/$", "USD/CHF": "$/CHF", "USD/GBP": "£/$", }; return map[inst] || inst; }; async function ladeNews() { const container = document.getElementById("news-container"); if (!container) return; const loadingEl = null; const newsIndex = new Map(); const getKey = (nachricht) => { if (nachricht?.link) return `link:${nachricht.link}`; if (nachricht?.title) return `title:${stripTags(nachricht.title)}`; if (nachricht?.publishedAt) return `ts:${nachricht.publishedAt}`; return `idx:${newsIndex.size}`; }; const buildItem = (nachricht) => { const item = document.createElement("div"); item.className = "news-item"; const img = document.createElement("img"); img.className = "news-thumb"; img.loading = "lazy"; const content = document.createElement("div"); content.className = "news-content"; const titleEl = document.createElement("div"); titleEl.className = "news-title"; const textEl = document.createElement("div"); textEl.className = "news-text"; const linkWrap = document.createElement("div"); linkWrap.className = "news-link"; const linkEl = document.createElement("a"); linkEl.target = "_blank"; linkEl.rel = "noopener noreferrer"; linkWrap.appendChild(linkEl); content.appendChild(titleEl); content.appendChild(textEl); content.appendChild(linkWrap); item.appendChild(content); return { item, img, titleEl, textEl, linkEl }; }; const updateFields = (state, nachricht) => { const title = stripTags(nachricht.title) || "Kein Titel"; const link = nachricht.link || "#"; const rawText = stripTags(nachricht.text) || "[Kein Text verfügbar]"; const text = rawText.length > 140 ? `${rawText.slice(0, 140)}...` : rawText; if (title && state.title !== title) { state.title = title; state.titleEl.textContent = title; } if (text && state.text !== text) { state.text = text; state.textEl.textContent = text; } if (link && state.link !== link) { state.link = link; state.linkEl.href = link; state.linkEl.textContent = "🔗 Zum Artikel"; } const isDataImg = typeof nachricht.image === "string" && nachricht.image.startsWith("data:image"); if (isDataImg && state.image !== nachricht.image) { state.image = nachricht.image; state.img.src = nachricht.image; state.img.alt = title || "Artikelbild"; if (!state.img.isConnected) { state.item.prepend(state.img); } } }; const renderItem = (nachricht) => { const key = getKey(nachricht); const existing = newsIndex.get(key); if (existing) { updateFields(existing, nachricht); return; } const state = buildItem(nachricht); updateFields(state, nachricht); newsIndex.set(key, state); container.appendChild(state.item); }; try { const res = await fetch("/api/artikeltext", { headers: { "Accept": "application/x-ndjson" }, cache: "no-store", }); if (!res.ok) { const t = await res.text().catch(() => ""); throw new Error(`HTTP ${res.status}: ${t.slice(0, 200)}`); } if (!res.body) throw new Error("Streaming wird vom Browser nicht unterstützt."); let foundAny = false; const reader = res.body.getReader(); const decoder = new TextDecoder("utf-8"); let buffer = ""; while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() ?? ""; for (const line of lines) { const s = line.trim(); if (!s) continue; try { renderItem(JSON.parse(s)); foundAny = true; } catch (e) { console.warn("NDJSON parse error:", s.slice(0, 200)); } } } const last = buffer.trim(); if (last) { try { renderItem(JSON.parse(last)); foundAny = true; } catch {} } if (loadingEl && loadingEl.isConnected) loadingEl.remove(); if (!foundAny && !container.hasChildNodes()) { container.innerHTML = '
Keine News gefunden.
'; } } catch (error) { console.error("ladeNews Fehler:", error); if (loadingEl && loadingEl.isConnected) loadingEl.remove(); if (!container.hasChildNodes()) { container.innerHTML = '
Fehler beim Laden der News.
'; } } } async function ladeRMV() { try { const response = await fetch("/api/rmv"); if (!response.ok) throw new Error("RMV-JSON nicht erreichbar"); const daten = await response.json(); const infoBox = document.getElementById("abfahrt-info"); const wrapper = document.getElementById("abfahrt-wrapper"); wrapper.style.display = "block"; if (!daten.abfahrten || daten.abfahrten.length === 0) { infoBox.innerHTML = "

Keine Abfahrten gefunden.

"; return; } const tripHTML = daten.abfahrten.map(trip => { const legsHTML = trip.map(leg => `
${leg.linie}
${leg.abfahrt} → ${leg.ankunft}
${leg.von} → ${leg.nach}
`).join("
"); return `
${legsHTML}
`; }).join(""); infoBox.innerHTML = tripHTML; } catch (error) { console.warn("RMV-Info nicht geladen:", error); document.getElementById("abfahrt-info").innerText = "Fehler beim Laden."; } } function toggleMensaIframe() { const container = document.getElementById("mensa-iframe-container"); container.style.display = (container.style.display === "none") ? "block" : "none"; } function zeigeGongVideo() { const overlay = document.createElement("div"); overlay.style.position = "fixed"; overlay.style.top = 0; overlay.style.left = 0; overlay.style.width = "100vw"; overlay.style.height = "100vh"; overlay.style.backgroundColor = "rgba(0, 0, 0, 0.9)"; overlay.style.display = "flex"; overlay.style.alignItems = "center"; overlay.style.justifyContent = "center"; overlay.style.zIndex = 9999; const video = document.createElement("video"); video.src = "/media/gong"; video.autoplay = true; video.controls = true; video.style.maxWidth = "90%"; video.style.maxHeight = "90%"; overlay.appendChild(video); document.body.appendChild(overlay); video.onended = () => document.body.removeChild(overlay); overlay.onclick = () => document.body.removeChild(overlay); } let fxCache = []; let fxIndex = 0; function rotiereFX() { const el = document.getElementById("fx-rotator"); if (!el || fxCache.length === 0) return; const item = fxCache[fxIndex % fxCache.length]; el.textContent = `${instrumentEmoji(item.instrument)} ${formatBid(item.bid)}`; fxIndex++; } function renderMarketTicker(quotes) { const track = document.getElementById("market-ticker-track"); const container = document.getElementById("market-ticker"); if (!track || !container) return; track.innerHTML = ""; if (!quotes || quotes.length === 0) { track.textContent = "Keine Marktdaten verfügbar"; track.style.animation = "none"; return; } const fragment = document.createDocumentFragment(); const addItems = () => quotes.forEach(q => { const span = document.createElement("span"); span.className = "ticker-item"; span.textContent = `${instrumentEmoji(q.instrument)} ${formatBid(q.bid)}`; fragment.appendChild(span); }); addItems(); addItems(); track.appendChild(fragment); const duration = Math.max(18, Math.min(60, quotes.length * 4)); track.style.setProperty("--ticker-duration", `${duration}s`); track.style.animation = "none"; void track.offsetWidth; track.style.animation = `ticker-move var(--ticker-duration, ${duration}s) linear infinite`; } async function ladeMarketDaten() { try { const res = await fetch("/api/market", { cache: "no-store" }); if (!res.ok) throw new Error("Market API nicht verfügbar"); const data = await res.json(); if (!Array.isArray(data)) throw new Error("Ungültige Marktdaten"); fxCache = data; rotiereFX(); renderMarketTicker(data); } catch (err) { console.warn("Marktdaten Fehler:", err); const el = document.getElementById("fx-rotator"); if (el) el.textContent = "FX Fehler"; renderMarketTicker([]); } } function zeigeYouTubeVideo() { const overlay = document.createElement("div"); overlay.style.position = "fixed"; overlay.style.top = 0; overlay.style.left = 0; overlay.style.width = "100vw"; overlay.style.height = "100vh"; overlay.style.backgroundColor = "rgba(0, 0, 0, 0.9)"; overlay.style.display = "flex"; overlay.style.alignItems = "center"; overlay.style.justifyContent = "center"; overlay.style.zIndex = 9999; const iframe = document.createElement("iframe"); const videoIds = [ "zhDwjnYZiCo", "Na0w3Mz46GA", "OO2kPK5-qno", "Yqk13qPcXis", "MZhivjxcF-M", "uMEvzhckqBw", "uMEvzhckqBw", "TGan48YE9Us", "-Xh4BNbxpI8", "r7kxh_vuBpo", "bdUbACCWmoY", ]; const zufallsId = videoIds[Math.floor(Math.random() * videoIds.length)]; iframe.src = `https://www.youtube.com/embed/${zufallsId}?autoplay=1`; iframe.allow = "autoplay; fullscreen"; iframe.allowFullscreen = true; iframe.style.width = "90%"; iframe.style.height = "90%"; iframe.style.border = "none"; overlay.appendChild(iframe); overlay.onclick = () => document.body.removeChild(overlay); document.body.appendChild(overlay); if (overlay.requestFullscreen) { overlay.requestFullscreen(); } else if (overlay.webkitRequestFullscreen) { overlay.webkitRequestFullscreen(); } else if (overlay.msRequestFullscreen) { overlay.msRequestFullscreen(); } } document.addEventListener("DOMContentLoaded", () => { const hazard = document.getElementById("hazard-symbol"); if (hazard) { hazard.addEventListener("click", () => { const elem = document.documentElement; if (elem.requestFullscreen) { elem.requestFullscreen(); } else if (elem.webkitRequestFullscreen) { elem.webkitRequestFullscreen(); } else if (elem.msRequestFullscreen) { elem.msRequestFullscreen(); } }); } }); let currentDefcon = 5; let letzteDefconÄnderung = Date.now(); function aktualisiereDefconButton(level) { const btn = document.getElementById("defcon-button"); if (!btn) return; const farben = { 1: "#ff0000", 2: "#ff9900", 3: "#ffff00", 4: "#0000ff", 5: "#000000", }; const farbe = farben[level] || "#555"; btn.textContent = `⚠️ DEFCON ${level}`; btn.style.backgroundColor = farbe; btn.style.color = level === 3 ? "black" : "white"; btn.style.fontWeight = "bold"; btn.style.transition = "background-color 0.5s, color 0.5s"; if (level === 1) { document.body.style.backgroundColor = farbe; } else { document.body.style.backgroundColor = "#000000"; } } async function ladeDefconStatus() { try { const res = await fetch("/api/defcon"); if (!res.ok) throw new Error("Fehler beim Abruf"); const data = await res.json(); currentDefcon = data.level; aktualisiereDefconButton(currentDefcon); } catch (err) { console.warn("DEFCON Abruf fehlgeschlagen:", err); } } async function sendeNeuesDefconLevel(level) { try { const response = await fetch("/api/defcon", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ level: level }), }); if (!response.ok) throw new Error("Fehler beim Senden des neuen DEFCON-Levels"); await response.json(); } catch (err) { console.error("Fehler beim POST:", err); } } function newDefconStatus() { currentDefcon -= 1; if (currentDefcon < 1) { currentDefcon = 5; } sendeNeuesDefconLevel(currentDefcon); aktualisiereDefconButton(currentDefcon); } async function ladeBunkerStatus() { try { const res = await fetch("/api/bunker-status"); if (!res.ok) throw new Error("Fehler beim Abruf"); const data = await res.json(); const icon = document.getElementById("hazard-symbol"); if (!icon) return; const inMainzNetz = Boolean(data && data.online); if (inMainzNetz) { icon.style.backgroundColor = "limegreen"; icon.style.color = "black"; icon.style.padding = "4px 8px"; icon.style.borderRadius = "4px"; } else { icon.style.backgroundColor = ""; icon.style.color = ""; icon.title = ""; } } catch (err) { console.warn("Bunker-Status nicht abrufbar:", err); } } ladeBunkerStatus(); setInterval(ladeBunkerStatus, 60 * 1000); ladeDefconStatus(); ladeNews(); ladeRMV(); ladeMarketDaten(); setInterval(ladeNews, 30 * 1000); setInterval(ladeRMV, 5 * 60 * 1000); setInterval(ladeDefconStatus, 60 * 1000); setInterval(() => { rotiereFX(); }, 8 * 1000); setInterval(ladeMarketDaten, 30 * 1000); const defconBtn = document.getElementById("defcon-button"); if (defconBtn) { defconBtn.addEventListener("click", newDefconStatus); } (function initLpaButton() { const TARGET_LPA = "https://aerztepruefung.service24.rlp.de/intelliform/admin/intelliForm-Spaces/LPA/Studentenbereich"; const btn = document.getElementById("lpa-button"); if (!btn) return; btn.addEventListener("click", () => window.open(TARGET_LPA, "_blank")); })();