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 = [];
for (const line of lines) {
try {
const parsed = JSON.parse(line);
items.push({
title: stripTags(parsed.title),
text: stripTags(parsed.text),
link: parsed.link,
});
} catch (err) {
console.warn("NDJSON parse error", err);
}
if (items.length >= 60) 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 ``;
}).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);
}