Files
2026-06-20 01:05:39 +02:00

313 lines
8.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
let rennplanData = {};
let resultsData = {};
let messageData = {};
document.addEventListener("DOMContentLoaded", () => {
initAppUI();
initTabs();
initAppPromotion();
loadData();
setInterval(loadData, CONFIG.REFRESH_INTERVAL);
});
function initAppUI() {
const fullTitle = `${CONFIG.EVENT_NAME} Rennplan & Ergebnisse`;
document.title = fullTitle;
const titleEl = document.getElementById("app-title");
if (titleEl) {
titleEl.textContent = fullTitle;
}
}
function initTabs() {
const rennplanBtn = document.getElementById("tab-rennplan");
const resultsBtn = document.getElementById("tab-ergebnisse");
const rennplanView = document.getElementById("rennplan-view");
const resultsView = document.getElementById("ergebnisse-view");
rennplanBtn.addEventListener("click", () => {
rennplanView.classList.remove("hidden");
resultsView.classList.add("hidden");
setTabActive(rennplanBtn, resultsBtn);
});
resultsBtn.addEventListener("click", () => {
resultsView.classList.remove("hidden");
rennplanView.classList.add("hidden");
setTabActive(resultsBtn, rennplanBtn);
});
}
function setTabActive(active, inactive) {
active.className =
"flex-1 py-4 text-indigo-600 border-b-4 border-indigo-600 font-medium";
inactive.className =
"flex-1 py-4 text-slate-500 font-medium";
}
async function loadData() {
try {
const scrollY = window.scrollY;
const [rennplanRes, resultsRes, messageRes] = await Promise.all([
fetch(CONFIG.RENNPLAN_URL),
fetch(CONFIG.RESULTS_URL),
fetch(CONFIG.MESSAGE_URL)
]);
rennplanData = await rennplanRes.json();
resultsData = await resultsRes.json();
messageData = await messageRes.json();
renderMessage();
renderRennplan();
renderResults();
requestAnimationFrame(() => {
window.scrollTo(0, scrollY);
});
} catch (err) {
console.error("API Fehler:", err);
}
}
function renderMessage() {
const box = document.getElementById("message-box");
if (!messageData?.message) {
box.classList.add("hidden");
return;
}
let style = "";
switch (messageData.type) {
case "danger":
style = "bg-red-50 border-red-400 text-red-900";
break;
case "warning":
style = "bg-amber-50 border-amber-400 text-amber-900";
break;
default:
style = "bg-blue-50 border-blue-400 text-blue-900";
}
box.className = `border-l-4 p-4 rounded-xl shadow-sm ${style}`;
box.innerHTML = `<div class="font-medium">${messageData.message}</div>`;
box.classList.remove("hidden");
}
function renderRennplan() {
const container = document.getElementById("rennplan-content");
container.innerHTML = "";
const rennplan = rennplanData.rennplan || {};
const sortedLaeufe = Object.keys(rennplan)
.sort((a, b) => {
return parseInt(b.replace("lauf", "")) - parseInt(a.replace("lauf", ""));
});
sortedLaeufe.forEach(laufKey => {
const laufNum = laufKey.replace("lauf", "");
const races = Object.entries(rennplan[laufKey])
.sort((a, b) => Number(a[0]) - Number(b[0]));
container.innerHTML += `
<section class="space-y-4">
<div class="flex items-center gap-3 mb-2">
<div class="h-10 w-10 rounded-full bg-indigo-600 text-white flex items-center justify-center font-semibold">
${laufNum}
</div>
<h2 class="text-xl font-medium text-slate-800">
Lauf ${laufNum}
</h2>
</div>
${races.map(([_, race]) => `
<div class="bg-white rounded-2xl shadow-sm overflow-hidden">
<div class="bg-indigo-50 px-5 py-4 flex justify-between">
<div>
<div class="text-lg font-medium">${race.name}</div>
<div class="text-sm text-slate-500">${race.art}</div>
</div>
<div class="text-indigo-600 font-semibold">
${race.zeit}
</div>
</div>
<div class="p-5 flex flex-wrap gap-2 text-sm">
<span class="bg-slate-100 px-3 py-1 rounded-full">
Bahn 1 · ${race.bahn1}
</span>
<span class="bg-slate-100 px-3 py-1 rounded-full">
Bahn 2 · ${race.bahn2}
</span>
<span class="bg-slate-100 px-3 py-1 rounded-full">
Bahn 3 · ${race.bahn3}
</span>
</div>
</div>
`).join("")}
</section>`;
});
}
function renderResults() {
const container = document.getElementById("results-content");
container.innerHTML = "";
const races = Object.entries(resultsData.ergebnisse || {})
.sort((a, b) => Number(b[0]) - Number(a[0]));
races.forEach(([_, race]) => {
const lane = (key, data, winner) => {
const isWinner = race.winner === key;
return `
<div class="
p-4 rounded-xl border
${isWinner ? "bg-green-50 border-green-400" : "bg-slate-50 border-slate-200"}
">
<div class="flex justify-between items-center">
<div class="font-medium">${data.boot}</div>
${isWinner ? `
<span class="bg-green-600 text-white text-xs px-2 py-1 rounded-full">
Sieger
</span>` : ""}
</div>
<div class="text-sm text-slate-500 mt-1">
${data.zeit}
</div>
<div class="text-xs text-slate-400 mt-1">
${formatLaneLabel(key)}
</div>
</div>`;
};
container.innerHTML += `
<div class="bg-white rounded-3xl shadow-sm overflow-hidden">
<div class="bg-indigo-600 text-white p-5">
<div class="flex justify-between">
<div>
<div class="text-lg font-medium">${race.title}</div>
<div class="text-indigo-100 text-sm">${race.art}</div>
</div>
<div class="text-sm">
Lauf ${race.lauf}
</div>
</div>
<div class="text-indigo-100 text-sm mt-2">
${formatDateTime(race.start)}
</div>
</div>
<div class="p-5 grid gap-3 md:grid-cols-3">
${lane("bahn1", race.bahn1)}
${lane("bahn2", race.bahn2)}
${lane("bahn3", race.bahn3)}
</div>
</div>`;
});
}
function formatLaneLabel(key) {
const map = {
bahn1: "Bahn 1",
bahn2: "Bahn 2",
bahn3: "Bahn 3"
};
return map[key] || key;
}
function formatDateTime(dateString) {
const match = dateString.match(
/^(\d{4})-(\d{2})-(\d{2})-(\d{2}):(\d{2}):(\d{2})$/
);
if (!match) return dateString;
const [, y, m, d, h, i, s] = match;
const date = new Date(y, m - 1, d, h, i, s);
return date.toLocaleString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit"
});
}
function initAppPromotion() {
if (CONFIG.IOS_APP_ID) {
const meta = document.createElement("meta");
meta.name = "apple-itunes-app";
meta.content = `app-id=${CONFIG.IOS_APP_ID}`;
document.head.appendChild(meta);
}
renderAndroidPromo();
}
function isAndroidDevice() {
return /android/i.test(navigator.userAgent || "");
}
function renderAndroidPromo() {
const promo = document.getElementById("app-promo");
if (!promo) return;
const isAndroid = isAndroidDevice();
console.log("Android detected:", isAndroid);
if (!isAndroid || !CONFIG.ANDROID_PACKAGE_NAME) {
promo.classList.add("hidden");
return;
}
const playStoreUrl =
`https://play.google.com/store/apps/details?id=${CONFIG.ANDROID_PACKAGE_NAME}`;
promo.innerHTML = `
<div class="bg-indigo-50 border border-indigo-200 text-indigo-800 p-4 rounded-xl flex items-center justify-between shadow-sm">
<div class="font-medium">
Jetzt die Android App herunterladen
</div>
<a href="${playStoreUrl}" target="_blank"
class="bg-indigo-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-indigo-700">
Öffnen
</a>
</div>
`;
promo.classList.remove("hidden");
}