function renderGuidePanel(titleKey, itemKeys, extras = "", { t }) { return `

${t(titleKey)}

${extras}
`; } function renderGuideOverviewCard(titleKey, blurbKey, { t }) { return `
${t(titleKey)}

${t(blurbKey)}

`; } export function renderGuideView({ dom, t }) { dom.view.innerHTML = `

${t("guide.title")}

${t("guide.intro")}

${renderGuideOverviewCard("guide.sponsor_title", "guide.card_sponsor_blurb", { t })} ${renderGuideOverviewCard("guide.race_title", "guide.card_race_blurb", { t })} ${renderGuideOverviewCard("guide.team_title", "guide.card_team_blurb", { t })} ${renderGuideOverviewCard("guide.windows_title", "guide.card_decoder_blurb", { t })}
${renderGuidePanel("guide.sponsor_title", ["guide.sponsor_1", "guide.sponsor_2", "guide.sponsor_3", "guide.sponsor_4", "guide.sponsor_5", "guide.sponsor_6"], "", { t })} ${renderGuidePanel("guide.race_wizard_title", ["guide.race_wizard_1", "guide.race_wizard_2", "guide.race_wizard_3", "guide.race_wizard_4", "guide.race_wizard_5", "guide.race_wizard_6", "guide.race_wizard_7"], "", { t })}
${renderGuidePanel("guide.race_title", ["guide.race_1", "guide.race_2", "guide.race_3", "guide.race_4", "guide.race_4a", "guide.race_5", "guide.race_6", "guide.race_7", "guide.race_8", "guide.race_9", "guide.race_10"], "", { t })} ${renderGuidePanel("guide.manage_steps_title", ["guide.manage_steps_1", "guide.manage_steps_2", "guide.manage_steps_3", "guide.manage_steps_4", "guide.manage_steps_5", "guide.manage_steps_6", "guide.manage_steps_7", "guide.manage_steps_8"], "", { t })}
${renderGuidePanel("guide.race_format_title", ["guide.race_format_0", "guide.race_format_1", "guide.race_format_2", "guide.race_format_3", "guide.race_format_4", "guide.race_format_5", "guide.race_format_6", "guide.race_format_7", "guide.race_format_8", "guide.race_format_9", "guide.race_format_10", "guide.race_format_11", "guide.race_format_11a", "guide.race_format_12", "guide.race_format_13", "guide.race_format_14"], "", { t })} ${renderGuidePanel("guide.validation_title", ["guide.validation_1", "guide.validation_2", "guide.validation_3", "guide.validation_4", "guide.validation_5", "guide.validation_6", "guide.validation_7", "guide.validation_8"], "", { t })}
${renderGuidePanel("guide.free_practice_title", ["guide.free_practice_1", "guide.free_practice_2", "guide.free_practice_3"], "", { t })} ${renderGuidePanel("guide.open_practice_title", ["guide.open_practice_1", "guide.open_practice_2", "guide.open_practice_3"], "", { t })}
${renderGuidePanel("guide.team_title", ["guide.team_1", "guide.team_2", "guide.team_3", "guide.team_4", "guide.team_5", "guide.team_6"], "", { t })} ${renderGuidePanel("guide.qualifying_title", ["guide.qualifying_1", "guide.qualifying_2", "guide.qualifying_3", "guide.qualifying_4", "guide.qualifying_5"], "", { t })}
${renderGuidePanel("guide.dashboard_title", ["guide.dashboard_1", "guide.dashboard_2", "guide.dashboard_3"], "", { t })} ${renderGuidePanel("guide.host_title", ["guide.host_1", "guide.host_2", "guide.host_3", "guide.host_4", "guide.host_5", "guide.host_6", "guide.host_7", "guide.host_8"], "", { t })}
${renderGuidePanel("guide.windows_title", ["guide.windows_1", "guide.windows_2", "guide.windows_3", "guide.windows_4", "guide.windows_5"], "", { t })} ${renderGuidePanel("guide.linux_title", ["guide.linux_1", "guide.linux_2", "guide.linux_3"], "", { t })}

${t("guide.sqlite_title")}

${t("guide.ammc_ref")}

`; } export function renderOverlayPageView(deps) { const { state, dom, t, escapeHtml, formatLap, formatCountdown, formatElapsedClock, formatPredictedLapDelta, getActiveSession, buildLeaderboard, ensureSessionResult, getSessionTiming, getVisiblePassings, resolveEventBranding, buildPracticeStandings, buildQualifyingStandings, buildFinalStandings, getOverlayModeLabel, getStatusLabel, getObsOverlayConfig, buildOverlayPanels, normalizeStartMode, renderPositionGrid, getEventName, getSessionTypeLabel, getStartModeLabel, renderRaceStandingsTableView, renderTeamOverlay, renderObsOverlay, renderOverlayLeaderboard, renderOverlaySidePanel, formatTeamActiveMemberLabel, getSessionGridEntries, overlayViewMode, overlayMode, publicOverlayMode, overlayEvents, overlayRotationIndex, openOverlayWindow, buildOverlayUrl } = deps; const active = getActiveSession(); const leaderboard = active ? buildLeaderboard(active).slice(0, 12) : []; const result = active ? ensureSessionResult(active.id) : null; const sessionTiming = active ? getSessionTiming(active) : null; const overlayClock = sessionTiming?.followUpActive ? formatCountdown(sessionTiming?.followUpRemainingMs ?? 0) : sessionTiming?.untimed ? formatElapsedClock(sessionTiming?.elapsedMs ?? 0) : formatCountdown(sessionTiming?.remainingMs ?? 0); const recent = active && result ? getVisiblePassings(result).slice(-8).reverse() : []; const event = active ? state.events.find((item) => item.id === active.eventId) : null; const branding = resolveEventBranding(event); const practiceRows = event ? buildPracticeStandings(event) : []; const qualifyingRows = event ? buildQualifyingStandings(event) : []; const finalRows = event ? buildFinalStandings(event) : []; const topRow = leaderboard[0] || null; const fastestRow = [...leaderboard].filter((row) => Number.isFinite(row.bestLapMs)).sort((left, right) => left.bestLapMs - right.bestLapMs)[0] || null; const modeLabel = getOverlayModeLabel(overlayViewMode, { t }); const overlayStatusLabel = sessionTiming?.followUpActive ? t("timing.follow_up_active") : active ? getStatusLabel(active.status) : ""; const obsConfig = overlayViewMode === "obs" ? getObsOverlayConfig() : null; const rotatingPanels = buildOverlayPanels(active, recent, { t, overlayEvents, normalizeStartMode, renderPositionGrid }); const activePanel = rotatingPanels.length ? rotatingPanels[overlayRotationIndex % rotatingPanels.length] : null; const denseOverlay = overlayViewMode === "leaderboard" || overlayViewMode === "tv"; dom.view.innerHTML = ` ${overlayMode ? "" : `

${t("overlay.title")}

`}
${active ? ` ${overlayViewMode === "obs" ? "" : `
${branding.logoDataUrl ? `` : ""}

${escapeHtml(getEventName(active.eventId))}

${escapeHtml(getSessionTypeLabel(active.type))} ${overlayViewMode !== "tv" ? `${escapeHtml(getStartModeLabel(active.startMode))}` : ""} ${escapeHtml(modeLabel)}

${escapeHtml(active.name)}

${escapeHtml(branding.brandName || "JMK RB RaceController")}

${overlayViewMode === "obs" && obsConfig && !obsConfig.showClock ? "" : `
${overlayClock}
`}
${escapeHtml(overlayStatusLabel)}
`} ${overlayViewMode === "speaker" ? `
P1

${escapeHtml(topRow?.displayName || topRow?.driverName || t("common.unknown_driver"))}

${t("table.result")}: ${escapeHtml(topRow?.resultDisplay || "-")}

${t("table.best_lap")}: ${formatLap(topRow?.bestLapMs)}

${t("overlay.last_passings")}

${recent.length ? recent.map((passing) => `
${escapeHtml(passing.displayName || passing.teamName || passing.driverName || t("common.unknown_driver"))} ${formatLap(passing.lapMs)}
`).join("") : `

${t("timing.no_passings")}

`}

${t("events.position_grid")}

${normalizeStartMode(active.startMode) === "position" ? renderPositionGrid(active) : `

${t("events.na")}

`}

${t("overlay.event_markers")}

${overlayEvents.length ? overlayEvents.map((item) => `
${escapeHtml(item.label)} ${new Date(item.ts).toLocaleTimeString()}
`).join("") : `

${t("timing.no_passings")}

`}
` : overlayViewMode === "results" ? `

${t("events.practice_standings")}

${renderRaceStandingsTableView(practiceRows, t("events.no_practice_results"))}

${t("events.qualifying_standings")}

${renderRaceStandingsTableView(qualifyingRows, t("events.no_qualifying_results"))}

${t("events.final_standings")}

${renderRaceStandingsTableView(finalRows, t("events.no_final_results"))}
` : overlayViewMode === "team" ? renderTeamOverlay(leaderboard, result, sessionTiming, { t, escapeHtml, formatLap, formatPredictedLapDelta, formatTeamActiveMemberLabel, getVisiblePassings, renderOverlayLeaderboard }) : overlayViewMode === "obs" ? renderObsOverlay(active, leaderboard, result, sessionTiming, branding, { t, escapeHtml, formatLap, getVisiblePassings, getSessionTypeLabel, getStartModeLabel, getStatusLabel, getEventName, getObsOverlayConfig, normalizeStartMode, renderPositionGrid, getSessionGridEntries, formatTeamActiveMemberLabel, renderOverlayLeaderboard }) : overlayViewMode === "tv" ? `
${t("overlay.fastest_lap")} ${formatLap(fastestRow?.bestLapMs)}
${escapeHtml(fastestRow?.displayName || fastestRow?.driverName || "-")}
${t("table.laps")}: ${topRow?.laps || 0} | ${t("timing.total_passings")}: ${getVisiblePassings(result).length || 0}
${renderOverlayLeaderboard(leaderboard, { t, escapeHtml, formatTeamActiveMemberLabel, formatLap, formatPredictedLapDelta })}
` : `
${t("overlay.fastest_lap")} ${formatLap(fastestRow?.bestLapMs)}
${escapeHtml(fastestRow?.displayName || fastestRow?.driverName || "-")}
${t("table.laps")}: ${topRow?.laps || 0} | ${t("timing.total_passings")}: ${result?.passings.length || 0}
${renderOverlayLeaderboard(leaderboard, { t, escapeHtml, formatTeamActiveMemberLabel, formatLap, formatPredictedLapDelta })}
`} ` : `

${t("overlay.title")}

${t("overlay.no_active")}

`}
`; document.getElementById("overlayFullscreen")?.addEventListener("click", async () => { const target = document.documentElement; if (!document.fullscreenElement) { await target.requestFullscreen?.().catch(() => {}); return; } await document.exitFullscreen?.().catch(() => {}); }); document.getElementById("overlayLaunchLeaderboard")?.addEventListener("click", () => openOverlayWindow("leaderboard")); document.getElementById("overlayLaunchSpeaker")?.addEventListener("click", () => openOverlayWindow("speaker")); document.getElementById("overlayLaunchResults")?.addEventListener("click", () => openOverlayWindow("results")); document.getElementById("overlayLaunchTv")?.addEventListener("click", () => openOverlayWindow("tv")); document.getElementById("overlayLaunchTeam")?.addEventListener("click", () => openOverlayWindow("team")); document.getElementById("overlayLaunchObs")?.addEventListener("click", () => openOverlayWindow("obs", { public: true })); document.getElementById("overlayCopyObsUrl")?.addEventListener("click", async () => { const url = buildOverlayUrl("obs", { public: true }); if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(url).catch(() => {}); return; } window.prompt("Copy OBS URL", url); }); }