commit 03e1c488328ab0c5a2e7084621068435c436022b Author: Thies Mueller Date: Sat Jun 20 00:37:22 2026 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a2d72a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.js \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..41a61e7 --- /dev/null +++ b/app.js @@ -0,0 +1,255 @@ +let rennplanData = {}; +let resultsData = {}; +let messageData = {}; + +document.addEventListener("DOMContentLoaded", () => { + initTabs(); + loadData(); + + setInterval(loadData, CONFIG.REFRESH_INTERVAL); +}); + +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 = `
${messageData.message}
`; + 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 += ` +
+ +
+
+ ${laufNum} +
+ +

+ Lauf ${laufNum} +

+
+ + ${races.map(([_, race]) => ` +
+ +
+
+
${race.name}
+
${race.art}
+
+ +
+ ${race.zeit} +
+
+ +
+ + Bahn 1 · ${race.bahn1} + + + + Bahn 2 · ${race.bahn2} + + + + Bahn 3 · ${race.bahn3} + +
+ +
+ `).join("")} + +
`; + }); +} + +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 ` +
+
+
${data.boot}
+ + ${isWinner ? ` + + Sieger + ` : ""} +
+ +
+ ${data.zeit} +
+ +
+ ${formatLaneLabel(key)} +
+
`; + }; + + container.innerHTML += ` +
+ +
+
+
+
${race.title}
+
${race.art}
+
+ +
+ Lauf ${race.lauf} +
+
+ +
+ ${formatDateTime(race.start)} +
+
+ +
+ ${lane("bahn1", race.bahn1)} + ${lane("bahn2", race.bahn2)} + ${lane("bahn3", race.bahn3)} +
+ +
`; + }); +} + +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" + }); +} \ No newline at end of file diff --git a/config.example.js b/config.example.js new file mode 100644 index 0000000..a23b683 --- /dev/null +++ b/config.example.js @@ -0,0 +1,7 @@ +const CONFIG = { + RESULTS_URL: "https://example.com/api/results", + RENNPLAN_URL: "https://example.com/api/rennplan", + MESSAGE_URL: "https://example.com/api/message", + + REFRESH_INTERVAL: 30000 // 30 Sekunden +}; \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..d35a485 --- /dev/null +++ b/index.html @@ -0,0 +1,50 @@ + + + + + + Sport am Tankumsee Rennplan & Ergebnisse + + + + + + + + +
+
+

Sport am Tankumsee

+
+
+ +
+ + +
+
+ + + + + +
+ +
+
+
+ + + +
+ + + \ No newline at end of file diff --git a/input.css b/input.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/input.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..b7bf336 --- /dev/null +++ b/style.css @@ -0,0 +1,576 @@ +/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */ +@layer properties; +@layer theme, base, components, utilities; +@layer theme { + :root, :host { + --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + --color-red-50: oklch(97.1% 0.013 17.38); + --color-red-400: oklch(70.4% 0.191 22.216); + --color-red-900: oklch(39.6% 0.141 25.723); + --color-amber-50: oklch(98.7% 0.022 95.277); + --color-amber-400: oklch(82.8% 0.189 84.429); + --color-amber-900: oklch(41.4% 0.112 45.904); + --color-green-50: oklch(98.2% 0.018 155.826); + --color-green-400: oklch(79.2% 0.209 151.711); + --color-green-600: oklch(62.7% 0.194 149.214); + --color-blue-50: oklch(97% 0.014 254.604); + --color-blue-400: oklch(70.7% 0.165 254.624); + --color-blue-900: oklch(37.9% 0.146 265.522); + --color-indigo-50: oklch(96.2% 0.018 272.314); + --color-indigo-100: oklch(93% 0.034 272.788); + --color-indigo-600: oklch(51.1% 0.262 276.966); + --color-slate-50: oklch(98.4% 0.003 247.858); + --color-slate-100: oklch(96.8% 0.007 247.896); + --color-slate-200: oklch(92.9% 0.013 255.508); + --color-slate-400: oklch(70.4% 0.04 256.788); + --color-slate-500: oklch(55.4% 0.046 257.417); + --color-slate-800: oklch(27.9% 0.041 260.031); + --color-white: #fff; + --spacing: 0.25rem; + --container-7xl: 80rem; + --text-xs: 0.75rem; + --text-xs--line-height: calc(1 / 0.75); + --text-sm: 0.875rem; + --text-sm--line-height: calc(1.25 / 0.875); + --text-lg: 1.125rem; + --text-lg--line-height: calc(1.75 / 1.125); + --text-xl: 1.25rem; + --text-xl--line-height: calc(1.75 / 1.25); + --font-weight-medium: 500; + --font-weight-semibold: 600; + --tracking-wide: 0.025em; + --radius-xl: 0.75rem; + --radius-2xl: 1rem; + --radius-3xl: 1.5rem; + --default-font-family: var(--font-sans); + --default-mono-font-family: var(--font-mono); + } +} +@layer base { + *, ::after, ::before, ::backdrop, ::file-selector-button { + box-sizing: border-box; + margin: 0; + padding: 0; + border: 0 solid; + } + html, :host { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + tab-size: 4; + font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); + font-feature-settings: var(--default-font-feature-settings, normal); + font-variation-settings: var(--default-font-variation-settings, normal); + -webkit-tap-highlight-color: transparent; + } + hr { + height: 0; + color: inherit; + border-top-width: 1px; + } + abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + } + h1, h2, h3, h4, h5, h6 { + font-size: inherit; + font-weight: inherit; + } + a { + color: inherit; + -webkit-text-decoration: inherit; + text-decoration: inherit; + } + b, strong { + font-weight: bolder; + } + code, kbd, samp, pre { + font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); + font-feature-settings: var(--default-mono-font-feature-settings, normal); + font-variation-settings: var(--default-mono-font-variation-settings, normal); + font-size: 1em; + } + small { + font-size: 80%; + } + sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + sub { + bottom: -0.25em; + } + sup { + top: -0.5em; + } + table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse; + } + :-moz-focusring { + outline: auto; + } + progress { + vertical-align: baseline; + } + summary { + display: list-item; + } + ol, ul, menu { + list-style: none; + } + img, svg, video, canvas, audio, iframe, embed, object { + display: block; + vertical-align: middle; + } + img, video { + max-width: 100%; + height: auto; + } + button, input, select, optgroup, textarea, ::file-selector-button { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + border-radius: 0; + background-color: transparent; + opacity: 1; + } + :where(select:is([multiple], [size])) optgroup { + font-weight: bolder; + } + :where(select:is([multiple], [size])) optgroup option { + padding-inline-start: 20px; + } + ::file-selector-button { + margin-inline-end: 4px; + } + ::placeholder { + opacity: 1; + } + @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) { + ::placeholder { + color: currentcolor; + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, currentcolor 50%, transparent); + } + } + } + textarea { + resize: vertical; + } + ::-webkit-search-decoration { + -webkit-appearance: none; + } + ::-webkit-date-and-time-value { + min-height: 1lh; + text-align: inherit; + } + ::-webkit-datetime-edit { + display: inline-flex; + } + ::-webkit-datetime-edit-fields-wrapper { + padding: 0; + } + ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { + padding-block: 0; + } + ::-webkit-calendar-picker-indicator { + line-height: 1; + } + :-moz-ui-invalid { + box-shadow: none; + } + button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button { + appearance: button; + } + ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { + height: auto; + } + [hidden]:where(:not([hidden="until-found"])) { + display: none !important; + } +} +@layer utilities { + .sticky { + position: sticky; + } + .top-0 { + top: calc(var(--spacing) * 0); + } + .z-50 { + z-index: 50; + } + .container { + width: 100%; + @media (width >= 40rem) { + max-width: 40rem; + } + @media (width >= 48rem) { + max-width: 48rem; + } + @media (width >= 64rem) { + max-width: 64rem; + } + @media (width >= 80rem) { + max-width: 80rem; + } + @media (width >= 96rem) { + max-width: 96rem; + } + } + .mx-auto { + margin-inline: auto; + } + .mt-1 { + margin-top: calc(var(--spacing) * 1); + } + .mt-2 { + margin-top: calc(var(--spacing) * 2); + } + .mb-4 { + margin-bottom: calc(var(--spacing) * 4); + } + .mb-6 { + margin-bottom: calc(var(--spacing) * 6); + } + .flex { + display: flex; + } + .grid { + display: grid; + } + .hidden { + display: none; + } + .h-10 { + height: calc(var(--spacing) * 10); + } + .min-h-screen { + min-height: 100vh; + } + .w-10 { + width: calc(var(--spacing) * 10); + } + .max-w-7xl { + max-width: var(--container-7xl); + } + .flex-1 { + flex: 1; + } + .flex-wrap { + flex-wrap: wrap; + } + .items-center { + align-items: center; + } + .items-start { + align-items: flex-start; + } + .justify-between { + justify-content: space-between; + } + .justify-center { + justify-content: center; + } + .gap-2 { + gap: calc(var(--spacing) * 2); + } + .gap-3 { + gap: calc(var(--spacing) * 3); + } + .space-y-4 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-y-6 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse))); + } + } + .overflow-hidden { + overflow: hidden; + } + .rounded-2xl { + border-radius: var(--radius-2xl); + } + .rounded-3xl { + border-radius: var(--radius-3xl); + } + .rounded-full { + border-radius: calc(infinity * 1px); + } + .rounded-xl { + border-radius: var(--radius-xl); + } + .border { + border-style: var(--tw-border-style); + border-width: 1px; + } + .border-b-4 { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 4px; + } + .border-l-4 { + border-left-style: var(--tw-border-style); + border-left-width: 4px; + } + .border-amber-400 { + border-color: var(--color-amber-400); + } + .border-blue-400 { + border-color: var(--color-blue-400); + } + .border-green-400 { + border-color: var(--color-green-400); + } + .border-indigo-600 { + border-color: var(--color-indigo-600); + } + .border-red-400 { + border-color: var(--color-red-400); + } + .border-slate-200 { + border-color: var(--color-slate-200); + } + .bg-amber-50 { + background-color: var(--color-amber-50); + } + .bg-blue-50 { + background-color: var(--color-blue-50); + } + .bg-green-50 { + background-color: var(--color-green-50); + } + .bg-green-600 { + background-color: var(--color-green-600); + } + .bg-indigo-50 { + background-color: var(--color-indigo-50); + } + .bg-indigo-600 { + background-color: var(--color-indigo-600); + } + .bg-red-50 { + background-color: var(--color-red-50); + } + .bg-slate-50 { + background-color: var(--color-slate-50); + } + .bg-slate-100 { + background-color: var(--color-slate-100); + } + .bg-white { + background-color: var(--color-white); + } + .p-4 { + padding: calc(var(--spacing) * 4); + } + .p-5 { + padding: calc(var(--spacing) * 5); + } + .px-2 { + padding-inline: calc(var(--spacing) * 2); + } + .px-3 { + padding-inline: calc(var(--spacing) * 3); + } + .px-5 { + padding-inline: calc(var(--spacing) * 5); + } + .px-6 { + padding-inline: calc(var(--spacing) * 6); + } + .py-1 { + padding-block: calc(var(--spacing) * 1); + } + .py-4 { + padding-block: calc(var(--spacing) * 4); + } + .text-lg { + font-size: var(--text-lg); + line-height: var(--tw-leading, var(--text-lg--line-height)); + } + .text-sm { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + } + .text-xl { + font-size: var(--text-xl); + line-height: var(--tw-leading, var(--text-xl--line-height)); + } + .text-xs { + font-size: var(--text-xs); + line-height: var(--tw-leading, var(--text-xs--line-height)); + } + .font-medium { + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium); + } + .font-semibold { + --tw-font-weight: var(--font-weight-semibold); + font-weight: var(--font-weight-semibold); + } + .tracking-wide { + --tw-tracking: var(--tracking-wide); + letter-spacing: var(--tracking-wide); + } + .text-amber-900 { + color: var(--color-amber-900); + } + .text-blue-900 { + color: var(--color-blue-900); + } + .text-indigo-100 { + color: var(--color-indigo-100); + } + .text-indigo-600 { + color: var(--color-indigo-600); + } + .text-red-900 { + color: var(--color-red-900); + } + .text-slate-400 { + color: var(--color-slate-400); + } + .text-slate-500 { + color: var(--color-slate-500); + } + .text-slate-800 { + color: var(--color-slate-800); + } + .text-white { + color: var(--color-white); + } + .shadow-md { + --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .shadow-sm { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .md\:grid-cols-3 { + @media (width >= 48rem) { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + } +} +@property --tw-space-y-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-border-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@property --tw-font-weight { + syntax: "*"; + inherits: false; +} +@property --tw-tracking { + syntax: "*"; + inherits: false; +} +@property --tw-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-inset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-ring-inset { + syntax: "*"; + inherits: false; +} +@property --tw-ring-offset-width { + syntax: ""; + inherits: false; + initial-value: 0px; +} +@property --tw-ring-offset-color { + syntax: "*"; + inherits: false; + initial-value: #fff; +} +@property --tw-ring-offset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@layer properties { + @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { + *, ::before, ::after, ::backdrop { + --tw-space-y-reverse: 0; + --tw-border-style: solid; + --tw-font-weight: initial; + --tw-tracking: initial; + --tw-shadow: 0 0 #0000; + --tw-shadow-color: initial; + --tw-shadow-alpha: 100%; + --tw-inset-shadow: 0 0 #0000; + --tw-inset-shadow-color: initial; + --tw-inset-shadow-alpha: 100%; + --tw-ring-color: initial; + --tw-ring-shadow: 0 0 #0000; + --tw-inset-ring-color: initial; + --tw-inset-ring-shadow: 0 0 #0000; + --tw-ring-inset: initial; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-offset-shadow: 0 0 #0000; + } + } +}