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; }; const newsContainer = document.getElementById("news-container"); const abfahrtWrapper = document.getElementById("abfahrt-wrapper"); const abfahrtInfo = document.getElementById("abfahrt-info"); const marketTrack = document.getElementById("market-ticker-track"); const marketContainer = document.getElementById("market-ticker"); async function loadNews() { if (!newsContainer) return; try { const res = await fetch("/api/artikeltext", { headers: { "Accept": "application/x-ndjson" }, cache: "no-store", }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const text = await res.text(); const lines = text.split("\n").map((line) => line.trim()).filter(Boolean); const items = []; const seen = new Set(); for (const line of lines) { try { const parsed = JSON.parse(line); const fallbackTitle = stripTags(parsed.title); const id = parsed.id || parsed.link || fallbackTitle || `idx:${items.length}`; if (seen.has(id)) continue; seen.add(id); items.push({ id, title: stripTags(parsed.title), text: stripTags(parsed.text), link: parsed.link, }); } catch (err) { console.warn("NDJSON parse error", err); } if (items.length >= 20) break; } newsContainer.innerHTML = ""; if (!items.length) { newsContainer.innerHTML = '
Keine News gefunden.
'; return; } const fragment = document.createDocumentFragment(); items.forEach((item) => { const wrapper = document.createElement("div"); wrapper.className = "news-item"; const title = document.createElement("div"); title.className = "news-title"; title.textContent = item.title || "Kein Titel"; const textEl = document.createElement("div"); textEl.className = "news-text"; textEl.textContent = item.text || "[Kein Text verfuegbar]"; const linkWrap = document.createElement("div"); linkWrap.className = "news-link"; const link = document.createElement("a"); link.href = item.link || "#"; link.textContent = "Zum Artikel"; link.target = "_blank"; link.rel = "noopener noreferrer"; linkWrap.appendChild(link); wrapper.appendChild(title); wrapper.appendChild(textEl); wrapper.appendChild(linkWrap); fragment.appendChild(wrapper); }); newsContainer.appendChild(fragment); } catch (err) { console.warn("News Fehler:", err); newsContainer.innerHTML = '
Fehler beim Laden der News.
'; } } function renderMarketTicker(quotes) { if (!marketTrack || !marketContainer) return; marketTrack.innerHTML = ""; if (!quotes || quotes.length === 0) { marketTrack.textContent = "Keine Marktdaten verfuegbar"; marketTrack.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(); marketTrack.appendChild(fragment); const duration = Math.max(90, Math.min(180, quotes.length * 10)); marketTrack.style.setProperty("--ticker-duration", `${duration}s`); marketTrack.style.animation = "none"; void marketTrack.offsetWidth; marketTrack.style.animation = `ticker-move var(--ticker-duration, ${duration}s) linear infinite`; } async function loadMarket() { try { const res = await fetch("/api/market", { cache: "no-store" }); if (!res.ok) throw new Error("Market API nicht verfuegbar"); const data = await res.json(); if (!Array.isArray(data)) throw new Error("Ungueltige Marktdaten"); renderMarketTicker(data); } catch (err) { console.warn("Marktdaten Fehler:", err); renderMarketTicker([]); } } async function loadRMV() { if (!abfahrtInfo || !abfahrtWrapper) return; try { const response = await fetch("/api/rmv", { cache: "no-store" }); if (!response.ok) throw new Error("RMV-JSON nicht erreichbar"); const data = await response.json(); abfahrtWrapper.style.display = "block"; if (!data.abfahrten || data.abfahrten.length === 0) { abfahrtInfo.innerHTML = "

Keine Abfahrten gefunden.

"; return; } const tripHTML = data.abfahrten.map((trip) => { const legsHTML = trip.map((leg) => `
${leg.linie}
${leg.abfahrt} -> ${leg.ankunft}
${leg.von} -> ${leg.nach}
`).join("
"); return `
${legsHTML}
`; }).join(""); abfahrtInfo.innerHTML = tripHTML; } catch (err) { console.warn("RMV-Info nicht geladen:", err); abfahrtInfo.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); } 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; 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 loadDefconStatus() { 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 loadBunkerStatus() { 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); } } loadBunkerStatus(); loadDefconStatus(); loadNews(); loadRMV(); loadMarket(); setInterval(loadBunkerStatus, 5 * 60 * 1000); setInterval(loadDefconStatus, 2 * 60 * 1000); setInterval(loadNews, 2 * 60 * 1000); setInterval(loadRMV, 10 * 60 * 1000); setInterval(loadMarket, 2 * 60 * 1000); const defconBtn = document.getElementById("defcon-button"); if (defconBtn) { defconBtn.addEventListener("click", newDefconStatus); }