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 ``;
}).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"));
})();