From 0d71cd5a184503d6f51f072a28d56c12eb11a650 Mon Sep 17 00:00:00 2001 From: larssand Date: Sat, 14 Mar 2026 16:19:45 +0100 Subject: [PATCH] nya displaykort och race-rader i overlayn --- src/app.js | 177 +++++++++++++++++++++++++++++++++---------------- src/styles.css | 103 ++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+), 56 deletions(-) diff --git a/src/app.js b/src/app.js index 55a1a7d..ff69e47 100644 --- a/src/app.js +++ b/src/app.js @@ -440,6 +440,8 @@ const TRANSLATIONS = { "overlay.mode_results": "Resultat", "overlay.fastest_lap": "Snabbaste varv", "overlay.fullscreen": "Fullscreen", + "overlay.leaderboard_live": "Live leaderboard", + "overlay.rotating_panel": "Displaypanel", "overlay.event_markers": "Eventmarkörer", "guide.host_title": "Hur Managed AMMC körs", "guide.host_1": "1. AMMC körs alltid på samma maskin som `npm start` eller `node server.js` körs på.", @@ -898,6 +900,8 @@ const TRANSLATIONS = { "overlay.mode_results": "Results", "overlay.fastest_lap": "Fastest Lap", "overlay.fullscreen": "Fullscreen", + "overlay.leaderboard_live": "Live leaderboard", + "overlay.rotating_panel": "Display panel", "overlay.event_markers": "Event markers", "guide.host_title": "How Managed AMMC Runs", "guide.host_1": "1. AMMC always runs on the same machine where `npm start` or `node server.js` is running.", @@ -949,6 +953,8 @@ let baselineAppVersion = ""; let selectedLeaderboardKey = null; let selectedGridSessionId = null; let overlaySyncTimer = null; +let overlayRotationTimer = null; +let overlayRotationIndex = 0; let overlayEvents = []; let lastOverlayLeaderKeyBySession = {}; let lastOverlayTop3BySession = {}; @@ -994,6 +1000,7 @@ async function init() { startAppVersionPolling(); if (overlayMode) { startOverlaySync(); + startOverlayRotation(); if (state.settings.wsUrl) { connectDecoder(); } @@ -1545,6 +1552,16 @@ function startOverlaySync() { }, 2000); } +function startOverlayRotation() { + clearInterval(overlayRotationTimer); + overlayRotationTimer = setInterval(() => { + overlayRotationIndex = (overlayRotationIndex + 1) % 3; + if (currentView === "overlay" && overlayViewMode === "leaderboard") { + renderView(); + } + }, 8000); +} + function renderNav() { if (overlayMode) { dom.nav.innerHTML = ""; @@ -3473,6 +3490,8 @@ function renderOverlay() { const fastestRow = [...leaderboard].filter((row) => Number.isFinite(row.bestLapMs)).sort((left, right) => left.bestLapMs - right.bestLapMs)[0] || null; const modeLabel = getOverlayModeLabel(overlayViewMode); + const rotatingPanels = buildOverlayPanels(active, recent); + const activePanel = rotatingPanels.length ? rotatingPanels[overlayRotationIndex % rotatingPanels.length] : null; dom.view.innerHTML = `
@@ -3565,6 +3584,13 @@ function renderOverlay() { : `
+
+
+ ${t("overlay.fastest_lap")} + ${formatLap(fastestRow?.bestLapMs)} +
+
${escapeHtml(fastestRow?.driverName || "-")}
+
${t("overlay.fastest_lap")} @@ -3582,47 +3608,15 @@ function renderOverlay() { ${sessionTiming?.untimed ? t("timing.elapsed") : t("timing.remaining")}
- ${renderOverlayLeaderboard(leaderboard)} +
+
+

${t("overlay.leaderboard_live")}

+
+ ${renderOverlayLeaderboard(leaderboard)} +
` @@ -3648,6 +3642,58 @@ function renderOverlay() { }); } +function buildOverlayPanels(active, recent) { + return [ + { + title: t("overlay.last_passings"), + content: recent.length + ? recent + .map( + (passing) => ` +
+ ${escapeHtml(passing.driverName || passing.transponder || t("common.unknown_driver"))} + ${new Date(passing.timestamp).toLocaleTimeString()} +
+ ` + ) + .join("") + : `

${t("timing.no_passings")}

`, + }, + { + title: t("overlay.event_markers"), + content: overlayEvents.length + ? overlayEvents + .map( + (item) => ` +
+ ${escapeHtml(item.label)} + ${new Date(item.ts).toLocaleTimeString()} +
+ ` + ) + .join("") + : `

${t("timing.no_passings")}

`, + }, + { + title: t("events.position_grid"), + content: + active && normalizeStartMode(active.startMode) === "position" ? renderPositionGrid(active) : `

${t("events.na")}

`, + }, + ]; +} + +function renderOverlaySidePanel(panel) { + return ` +
+
+

${escapeHtml(panel.title)}

+ ${t("overlay.rotating_panel")} +
+ ${panel.content} +
+ `; +} + function renderLeaderboardModal(session, row) { const passings = getCompetitorPassings(session, row); return ` @@ -3737,23 +3783,42 @@ function renderOverlayLeaderboard(rows) { return `

${t("timing.no_laps")}

`; } - return renderTable( - [t("table.pos"), t("table.driver"), t("table.laps"), t("table.result"), t("table.best_lap"), t("table.ahead_gap"), t("table.own_delta")], - rows.map((row, idx) => { - const posClass = idx === 0 ? "pos-1" : idx === 1 ? "pos-2" : idx === 2 ? "pos-3" : ""; - return ` - - ${idx + 1} - ${escapeHtml(row.driverName)} - ${row.laps} - ${escapeHtml(row.resultDisplay)} - ${formatLap(row.bestLapMs)} - ${escapeHtml(row.gapAhead || "-")} - ${escapeHtml(row.lapDelta || "-")} - - `; - }) - ); + return ` +
+ ${rows + .map((row, idx) => { + const posClass = idx === 0 ? "pos-1" : idx === 1 ? "pos-2" : idx === 2 ? "pos-3" : ""; + return ` +
+
+ ${idx + 1} +
+
+ ${escapeHtml(row.driverName)} + ${escapeHtml(row.transponder || "-")} +
+
+ + ${escapeHtml(row.resultDisplay)} +
+
+ + ${escapeHtml(row.gapAhead || "-")} +
+
+ + ${escapeHtml(row.lapDelta || "-")} +
+
+ + ${formatLap(row.bestLapMs)} +
+
+ `; + }) + .join("")} +
+ `; } function renderRecentPassings(session) { diff --git a/src/styles.css b/src/styles.css index 782812c..f6d13fa 100644 --- a/src/styles.css +++ b/src/styles.css @@ -708,6 +708,55 @@ select:focus { gap: 16px; } +.overlay-fastest-banner, +.overlay-leaderboard-card { + border: 1px solid var(--line); + border-radius: 18px; + background: rgba(7, 12, 20, 0.9); + box-shadow: var(--shadow); +} + +.overlay-fastest-banner { + display: flex; + justify-content: space-between; + align-items: end; + gap: 16px; + padding: 18px 20px; + background: + linear-gradient(135deg, rgba(225, 6, 0, 0.18), rgba(225, 6, 0, 0.04)), + rgba(7, 12, 20, 0.92); +} + +.overlay-fastest-banner span, +.overlay-fastest-driver { + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.overlay-fastest-banner strong { + display: block; + margin-top: 6px; + font-family: Orbitron, sans-serif; + font-size: clamp(2rem, 4vw, 3.2rem); +} + +.overlay-leaderboard-card { + padding: 14px; +} + +.overlay-section-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 12px; +} + +.overlay-section-head h3 { + margin: 0; +} + .overlay-stats-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); @@ -743,6 +792,10 @@ select:focus { padding: 14px; } +.overlay-rotating-card { + min-height: 320px; +} + .overlay-side-card h3 { margin: 0 0 10px; } @@ -759,6 +812,52 @@ select:focus { border-bottom: 0; } +.overlay-race-list { + display: grid; + gap: 10px; +} + +.overlay-race-row { + display: grid; + grid-template-columns: 72px minmax(220px, 1.4fr) repeat(3, minmax(140px, 0.8fr)) minmax(150px, 0.9fr); + gap: 12px; + align-items: center; + padding: 14px 16px; + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 16px; + background: rgba(255, 255, 255, 0.03); +} + +.overlay-race-row-leader { + border-color: rgba(225, 6, 0, 0.45); + background: linear-gradient(135deg, rgba(225, 6, 0, 0.12), rgba(255, 255, 255, 0.03)); +} + +.overlay-race-driver strong, +.overlay-race-metric strong, +.overlay-race-best strong { + display: block; +} + +.overlay-race-driver strong { + font-size: clamp(1.2rem, 2vw, 1.7rem); +} + +.overlay-race-driver span, +.overlay-race-metric label, +.overlay-race-best label { + color: var(--muted); + font-size: 0.78rem; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.overlay-race-metric strong, +.overlay-race-best strong { + font-family: Orbitron, sans-serif; + font-size: clamp(1rem, 1.6vw, 1.35rem); +} + .overlay-empty { display: grid; place-items: center; @@ -919,6 +1018,10 @@ select:focus { grid-template-columns: 1fr; } + .overlay-race-row { + grid-template-columns: 56px 1fr; + } + .overlay-speaker { grid-template-columns: 1fr; }