function renderSpeakerToggle(settingKey, labelKey, { state, t }) { return ` `; } function getQuickAddState(transponder, { state }) { const normalized = String(transponder || "").trim(); const driver = state.drivers.find((item) => String(item.transponder || "").trim() === normalized) || null; const car = state.cars.find((item) => String(item.transponder || "").trim() === normalized) || null; return { transponder: normalized, hasDriver: Boolean(driver), hasCar: Boolean(car), }; } function getPreferredClassId(session, { state }) { const event = state.events.find((item) => item.id === session?.eventId); if (event?.classId) { return event.classId; } return state.classes[0]?.id || ""; } function beginQuickAddDraft(session, type, transponder, deps) { const { state, setQuickAddDraft, renderView, getPreferredClassId } = deps; const normalized = String(transponder || "").trim(); if (!normalized) { return; } if (type === "driver" && state.drivers.some((item) => String(item.transponder || "").trim() === normalized)) { return; } if (type === "car" && state.cars.some((item) => String(item.transponder || "").trim() === normalized)) { return; } setQuickAddDraft({ type, transponder: normalized, classId: getPreferredClassId(session), name: type === "driver" ? normalized : `Car ${normalized}`, }); renderView(); } function renderQuickAddActions(session, transponder, idPrefix, deps) { const { t, getQuickAddState } = deps; const quickState = getQuickAddState(transponder); if (!quickState.transponder || (quickState.hasDriver && quickState.hasCar)) { return ""; } return `
${!quickState.hasDriver ? `` : ""} ${!quickState.hasCar ? `` : ""}
`; } function bindQuickAddActions(session, transponder, idPrefix, deps) { const { beginQuickAddDraft } = deps; document.getElementById(`${idPrefix}-add-driver`)?.addEventListener("click", () => { beginQuickAddDraft(session, "driver", transponder); }); document.getElementById(`${idPrefix}-add-car`)?.addEventListener("click", () => { beginQuickAddDraft(session, "car", transponder); }); } function renderQuickAddPanel(session, deps) { const { state, t, escapeHtml, getQuickAddDraft, getPreferredClassId } = deps; const quickAddDraft = getQuickAddDraft(); if (!quickAddDraft || !quickAddDraft.transponder) { return ""; } const classOptions = state.classes .map( (item) => `` ) .join(""); const isDriver = quickAddDraft.type === "driver"; return `

${t(isDriver ? "timing.quick_add_driver_title" : "timing.quick_add_car_title")}

${ isDriver ? `` : `
${t("timing.quick_add_hint")}
` }
`; } function renderLeaderboardModal(session, row, deps) { const { t, escapeHtml, formatLap, formatRaceClock, renderTable, getCompetitorPassings, renderQuickAddActions } = deps; const passings = getCompetitorPassings(session, row); return ` `; } function renderLeaderboard(rows, deps) { const { t, escapeHtml, renderTable, getManualCorrectionSummary, formatLap, formatTeamActiveMemberLabel } = deps; if (!rows.length) { return `

${t("timing.no_laps")}

`; } return renderTable( [ t("table.pos"), t("table.driver"), t("table.car"), t("table.transponder"), t("table.laps"), t("table.result"), t("table.last_lap"), t("table.best_lap"), t("table.leader_gap"), t("table.ahead_gap"), t("table.own_delta"), t("table.split"), t("table.pit"), "", ], rows.map((row, idx) => { const posClass = idx === 0 ? "pos-1" : idx === 1 ? "pos-2" : idx === 2 ? "pos-3" : ""; return ` ${idx + 1}
${escapeHtml(row.displayName || row.driverName)}
${row.teamId ? `
${t("overlay.active_member")}: ${escapeHtml(formatTeamActiveMemberLabel(row))}
` : ""} ${getManualCorrectionSummary(row) ? `
${escapeHtml(getManualCorrectionSummary(row))}
` : ""} ${row.invalidPending ? `
${escapeHtml(row.invalidLabel)}${row.invalidLapMs ? ` • ${formatLap(row.invalidLapMs)}` : ""}
` : ""} ${escapeHtml(row.subLabel || row.carName)} ${escapeHtml(row.transponder)} ${row.laps} ${escapeHtml(row.resultDisplay)} ${formatLap(row.lastLapMs)} ${formatLap(row.bestLapMs)} ${escapeHtml(row.leaderGap || row.gap || "-")} ${escapeHtml(row.gapAhead || "-")} ${escapeHtml(row.lapDelta || "-")} ${formatLap(row.lastSplitMs)} ${row.pitActive ? `${t("timing.pit_active")}` : `${Number(row.pitStops || 0)} / ${formatLap(row.totalPitMs)}`} `; }) ); } function renderRecentPassings(session, deps) { const { t, escapeHtml, renderTable, ensureSessionResult, formatLap, getPassingValidationLabel, isCountedPassing, renderQuickAddActions } = deps; if (!session) { return `

${t("timing.no_session_selected")}

`; } const result = ensureSessionResult(session.id); const items = result.passings.slice(-20).reverse(); if (!items.length) { return `

${t("timing.no_passings")}

`; } return renderTable( [t("table.time"), t("table.transponder"), t("table.driver"), t("table.car"), t("table.last_lap"), t("table.status"), ""], items.map((p, index) => { return ` ${new Date(p.timestamp).toLocaleTimeString()} ${escapeHtml(p.transponder)} ${escapeHtml(p.teamName ? `${p.teamName} • ${p.driverName || t("common.unknown_driver")}` : p.driverName || t("common.unknown_driver"))} ${escapeHtml(p.carName || p.subLabel || "-")} ${formatLap(p.lapMs)} ${escapeHtml(getPassingValidationLabel(p))} ${renderQuickAddActions(session, p.transponder, `recentPassing-${index}`)} `; }) ); } export function renderTimingView(deps) { const { state, dom, t, escapeHtml, formatLap, formatCountdown, formatElapsedClock, renderTable, uid, getActiveSession, ensureSessionResult, buildLeaderboard, getSessionTiming, getVisiblePassings, getEventName, getSessionTypeLabel, getStatusLabel, normalizeStartMode, getStartModeLabel, renderPositionGrid, connectDecoder, disconnectDecoder, applyCompetitorCorrection, invalidateCompetitorLastLap, restoreCompetitorLastInvalidLap, ensureAudioContext, validateTrackSessionForStart, announceRaceStarted, announceRaceFinished, saveState, updateHeaderState, renderView, normalizeDriver, normalizeCar, processDecoderMessage, getSelectedLeaderboardKey, setSelectedLeaderboardKey, getQuickAddDraft, setQuickAddDraft, getPassingValidationLabel, isCountedPassing, getCompetitorPassings, getManualCorrectionSummary, formatTeamActiveMemberLabel, formatRaceClock, renderSessionChannelAnalysis, setLastFinishAnnouncementSessionId, lastOverlayLeaderKeyBySession, lastOverlayTop3BySession, overlayEvents, setOverlayEvents, } = deps; const helperDeps = { state, t, escapeHtml, renderTable, formatLap, formatRaceClock, getCompetitorPassings, getQuickAddDraft, getPassingValidationLabel, isCountedPassing, getManualCorrectionSummary, formatTeamActiveMemberLabel, }; helperDeps.getPreferredClassId = (session) => getPreferredClassId(session, { state }); helperDeps.getQuickAddState = (transponder) => getQuickAddState(transponder, { state }); helperDeps.setQuickAddDraft = setQuickAddDraft; helperDeps.renderView = renderView; helperDeps.beginQuickAddDraft = (session, type, transponder) => beginQuickAddDraft(session, type, transponder, helperDeps); helperDeps.renderQuickAddActions = (session, transponder, idPrefix) => renderQuickAddActions(session, transponder, idPrefix, helperDeps); const active = getActiveSession(); const result = active ? ensureSessionResult(active.id) : null; const leaderboard = active ? buildLeaderboard(active) : []; const sessionTiming = active ? getSessionTiming(active) : null; const clockLabel = active && sessionTiming?.followUpActive ? t("timing.follow_up") : active && sessionTiming?.untimed ? t("timing.elapsed") : t("timing.remaining"); const clockValue = sessionTiming?.followUpActive ? formatCountdown(sessionTiming?.followUpRemainingMs ?? 0) : sessionTiming?.untimed ? formatElapsedClock(sessionTiming?.elapsedMs ?? 0) : formatCountdown(sessionTiming?.remainingMs ?? 0); const showFinishedBanner = Boolean(active && active.status === "finished" && active.finishedByTimer); const showFollowUpBanner = Boolean(active && sessionTiming?.followUpActive); const selectedRow = leaderboard.find((row) => row.key === getSelectedLeaderboardKey()) || null; const channelAnalysis = active ? renderSessionChannelAnalysis(active) : ""; if (getSelectedLeaderboardKey() && !selectedRow) { setSelectedLeaderboardKey(null); } dom.view.innerHTML = `

${t("timing.decoder_connection")}

${t("timing.status")} ${t("timing.main_decoder")}: ${state.decoder.connected ? t("timing.connected") : t("timing.disconnected")} ${t("timing.last_message")}: ${state.decoder.lastMessageAt ? new Date(state.decoder.lastMessageAt).toLocaleString() : "-"} ${state.settings.splitDecoderEnabled ? `${t("timing.split_decoder")}: ${state.decoder.splitConnected ? t("timing.connected") : t("timing.disconnected")}${t("timing.last_message")}: ${state.decoder.splitLastMessageAt ? new Date(state.decoder.splitLastMessageAt).toLocaleString() : "-"}` : ""} ${state.settings.pitDecoderEnabled ? `${t("timing.pit_entry_decoder")}: ${state.decoder.pitEntryConnected ? t("timing.connected") : t("timing.disconnected")}${t("timing.last_message")}: ${state.decoder.pitEntryLastMessageAt ? new Date(state.decoder.pitEntryLastMessageAt).toLocaleString() : "-"}${t("timing.pit_exit_decoder")}: ${state.decoder.pitExitConnected ? t("timing.connected") : t("timing.disconnected")}${t("timing.last_message")}: ${state.decoder.pitExitLastMessageAt ? new Date(state.decoder.pitExitLastMessageAt).toLocaleString() : "-"}` : ""}

${escapeHtml(state.decoder.lastError || "")}

${escapeHtml(state.decoder.splitLastError || state.decoder.pitEntryLastError || state.decoder.pitExitLastError || "")}

${t("timing.control")}

${ active ? `
${escapeHtml(active.name)} ${escapeHtml(getSessionTypeLabel(active.type))} ${escapeHtml(getEventName(active.eventId))} ${escapeHtml(getStatusLabel(active.status))}
${clockLabel}${clockValue}
${t("timing.started")}${active.startedAt ? new Date(active.startedAt).toLocaleTimeString() : "-"}
${t("table.start_mode")}${escapeHtml(getStartModeLabel(active.startMode))}
${t("timing.seeding_mode")}${active.seedBestLapCount > 0 ? `${active.seedBestLapCount}` : "-"}
${t("timing.total_passings")}${getVisiblePassings(result).length}
${ active.type === "free_practice" ? `

${t("events.free_practice_note")}

` : active.type === "open_practice" ? `

${t("events.open_practice_note")}

` : "" }` : `

${t("timing.no_active")}

` } ${showFollowUpBanner ? `

${t("timing.follow_up_active")}

` : ""} ${showFinishedBanner ? `

${t("timing.race_finished")}

` : ""} ${active && normalizeStartMode(active.startMode) === "position" ? renderPositionGrid(active) : ""}
${active ? renderQuickAddPanel(active, helperDeps) : ""}

${t("timing.speaker_panel")}

${t("timing.speaker_panel_hint")}

${renderSpeakerToggle("speakerPassingCueEnabled", "settings.speaker_passing_cue", { state, t })} ${renderSpeakerToggle("speakerLeaderCueEnabled", "settings.speaker_leader_cue", { state, t })} ${renderSpeakerToggle("speakerBestLapCueEnabled", "settings.speaker_bestlap_cue", { state, t })} ${renderSpeakerToggle("speakerTop3CueEnabled", "settings.speaker_top3_cue", { state, t })} ${renderSpeakerToggle("speakerSessionStartCueEnabled", "settings.speaker_start_cue", { state, t })} ${renderSpeakerToggle("speakerFinishCueEnabled", "settings.speaker_finish_cue", { state, t })}

${t("timing.leaderboard")}

${renderLeaderboard(leaderboard, helperDeps)}
${channelAnalysis ? `

${t("timing.channel_analysis")}

${channelAnalysis}
` : ""}

${t("timing.recent_passings")}

${renderRecentPassings(active, { ...helperDeps, ensureSessionResult, renderQuickAddActions: (session, transponder, idPrefix) => renderQuickAddActions(session, transponder, idPrefix, helperDeps) })}
${selectedRow && active ? renderLeaderboardModal(active, selectedRow, { ...helperDeps, renderQuickAddActions: (session, transponder, idPrefix) => renderQuickAddActions(session, transponder, idPrefix, helperDeps) }) : ""} `; document.getElementById("timingConnect")?.addEventListener("click", () => { const input = document.getElementById("timingWsUrl"); if (input && input.value.trim()) { state.settings.wsUrl = input.value.trim(); } state.settings.splitDecoderEnabled = Boolean(document.getElementById("timingSplitEnabled")?.checked); state.settings.splitWsUrl = String(document.getElementById("timingSplitWsUrl")?.value || "").trim(); state.settings.pitDecoderEnabled = Boolean(document.getElementById("timingPitEnabled")?.checked); state.settings.pitEntryWsUrl = String(document.getElementById("timingPitEntryWsUrl")?.value || "").trim(); state.settings.pitExitWsUrl = String(document.getElementById("timingPitExitWsUrl")?.value || "").trim(); saveState(); connectDecoder(); }); document.getElementById("timingDisconnect")?.addEventListener("click", disconnectDecoder); leaderboard.forEach((row) => { document.getElementById(`leaderboard-detail-${row.key}`)?.addEventListener("click", () => { setSelectedLeaderboardKey(row.key); renderView(); }); }); if (active && selectedRow) { bindQuickAddActions(active, selectedRow.transponder, "leaderboardModal", helperDeps); document.getElementById("corrLapPlus")?.addEventListener("click", () => { applyCompetitorCorrection(active, selectedRow, { lapDelta: 1 }); renderView(); }); document.getElementById("corrLapMinus")?.addEventListener("click", () => { applyCompetitorCorrection(active, selectedRow, { lapDelta: -1 }); renderView(); }); document.getElementById("corrSecPlus")?.addEventListener("click", () => { applyCompetitorCorrection(active, selectedRow, { timeMsDelta: 1000 }); renderView(); }); document.getElementById("corr5SecPlus")?.addEventListener("click", () => { applyCompetitorCorrection(active, selectedRow, { timeMsDelta: 5000 }); renderView(); }); document.getElementById("corrSecMinus")?.addEventListener("click", () => { applyCompetitorCorrection(active, selectedRow, { timeMsDelta: -1000 }); renderView(); }); document.getElementById("corrInvalidateLast")?.addEventListener("click", () => { invalidateCompetitorLastLap(active, selectedRow); renderView(); }); document.getElementById("corrRestoreInvalid")?.addEventListener("click", () => { restoreCompetitorLastInvalidLap(active, selectedRow); renderView(); }); document.getElementById("corrReset")?.addEventListener("click", () => { applyCompetitorCorrection(active, selectedRow, { reset: true }); renderView(); }); } if (active) { ensureSessionResult(active.id) .passings.slice(-20) .reverse() .forEach((passing, index) => { bindQuickAddActions(active, passing.transponder, `recentPassing-${index}`, helperDeps); }); } document.getElementById("quickAddCancel")?.addEventListener("click", () => { setQuickAddDraft(null); renderView(); }); document.getElementById("quickAddForm")?.addEventListener("submit", (event) => { event.preventDefault(); const quickAddDraft = getQuickAddDraft(); if (!active || !quickAddDraft) { return; } const form = new FormData(event.currentTarget); const name = String(form.get("name") || "").trim(); if (!name) { return; } const transponder = String(form.get("transponder") || "").trim(); const brand = String(form.get("brand") || "").trim(); if (!transponder) { return; } if (quickAddDraft.type === "driver") { if (!state.drivers.some((item) => String(item.transponder || "").trim() === transponder)) { state.drivers.push( normalizeDriver({ id: uid("driver"), name, classId: String(form.get("classId") || getPreferredClassId(active, { state })), brand, transponder, }) ); } } else if (!state.cars.some((item) => String(item.transponder || "").trim() === transponder)) { state.cars.push( normalizeCar({ id: uid("car"), name, brand, transponder, }) ); } setQuickAddDraft(null); saveState(); renderView(); }); document.getElementById("leaderboardModalClose")?.addEventListener("click", () => { setSelectedLeaderboardKey(null); renderView(); }); document.getElementById("leaderboardModalOverlay")?.addEventListener("click", (event) => { if (event.target?.id === "leaderboardModalOverlay") { setSelectedLeaderboardKey(null); renderView(); } }); document.getElementById("timingSimPass")?.addEventListener("click", () => { const tp = prompt(t("timing.prompt_transponder"), "232323"); if (!tp) { return; } processDecoderMessage({ msg: "PASSING", transponder: tp, rtc_time: new Date().toISOString(), strength: 0, resend: false, loop_id: "sim", }); }); document.getElementById("setActiveSession")?.addEventListener("click", () => { const select = document.getElementById("activeSessionSelect"); if (!select || !select.value) { return; } state.activeSessionId = select.value; saveState(); updateHeaderState(); renderView(); }); document.getElementById("startSession")?.addEventListener("click", () => { const session = getActiveSession(); if (!session) { return; } ensureAudioContext(); if (session.mode === "track") { const trackValidation = validateTrackSessionForStart(session); if (!trackValidation.ok) { alert(trackValidation.message); return; } } session.status = "running"; session.startedAt = Date.now(); session.endedAt = null; session.finishedByTimer = false; session.followUpStartedAt = null; setLastFinishAnnouncementSessionId(null); lastOverlayLeaderKeyBySession[session.id] = null; lastOverlayTop3BySession[session.id] = []; setOverlayEvents([]); ensureSessionResult(session.id); announceRaceStarted(session); saveState(); updateHeaderState(); renderView(); }); document.getElementById("stopSession")?.addEventListener("click", () => { const session = getActiveSession(); if (!session) { return; } session.status = "finished"; session.endedAt = Date.now(); session.finishedByTimer = false; session.followUpStartedAt = null; setLastFinishAnnouncementSessionId(session.id); announceRaceFinished(); saveState(); updateHeaderState(); renderView(); }); document.getElementById("resetSession")?.addEventListener("click", () => { const session = getActiveSession(); if (!session) { return; } if (!confirm(t("timing.clear_confirm"))) { return; } delete state.resultsBySession[session.id]; session.followUpStartedAt = null; setLastFinishAnnouncementSessionId(null); delete lastOverlayLeaderKeyBySession[session.id]; delete lastOverlayTop3BySession[session.id]; setOverlayEvents([]); saveState(); renderView(); }); document.querySelectorAll("[data-speaker-setting]").forEach((node) => { node.addEventListener("change", (event) => { const input = event.currentTarget; if (!(input instanceof HTMLInputElement)) { return; } state.settings[input.dataset.speakerSetting] = input.checked; saveState(); }); }); } export function renderJudgingView(deps) { const { dom, t, escapeHtml, formatLap, renderTable, getActiveSession, ensureSessionResult, buildLeaderboard, getSessionTypeLabel, getJudgeFilteredRows, getJudgeFilteredLog, getCompetitorPassings, isCountedPassing, getPassingValidationLabel, undoJudgingAdjustment, applyCompetitorCorrection, invalidateCompetitorLastLap, restoreCompetitorLastInvalidLap, renderView, getSelectedJudgeKey, setSelectedJudgeKey, getJudgingCompetitorFilter, setJudgingCompetitorFilter, getJudgingLogFilter, setJudgingLogFilter, } = deps; const active = getActiveSession(); if (!active) { dom.view.innerHTML = `

${t("judging.title")}

${t("judging.no_active_session")}

`; return; } const result = ensureSessionResult(active.id); const leaderboard = buildLeaderboard(active); const filteredRows = getJudgeFilteredRows(leaderboard, getJudgingCompetitorFilter()); if (getSelectedJudgeKey() && !filteredRows.some((row) => row.key === getSelectedJudgeKey())) { setSelectedJudgeKey(null); } if (!getSelectedJudgeKey() && filteredRows.length) { setSelectedJudgeKey(filteredRows[0].key); } const selectedRow = leaderboard.find((row) => row.key === getSelectedJudgeKey()) || null; const selectedPassings = selectedRow ? getCompetitorPassings(active, selectedRow, { includeInvalid: true }) : []; const actionLog = getJudgeFilteredLog(Array.isArray(result.adjustments) ? result.adjustments.slice(-50).reverse() : [], getJudgingLogFilter()); const latestUndoable = (result.adjustments || []).slice().reverse().find((entry) => !entry.undoneAt && entry.undo); dom.view.innerHTML = `

${t("judging.title")}

${escapeHtml(active.name)}

${t("judging.active_session")}: ${escapeHtml(active.name)} • ${escapeHtml(getSessionTypeLabel(active.type))}

${t("judging.select_competitor")}

${renderTable( [t("table.pos"), t("table.driver"), t("table.result"), t("table.best_lap"), ""], filteredRows.map( (row, index) => ` ${index + 1}
${escapeHtml(row.displayName || row.driverName)}
${escapeHtml(row.subLabel || row.transponder || "-")}
${escapeHtml(row.resultDisplay || "-")} ${formatLap(row.bestLapMs)} ` ) )}

${t("judging.manual_actions")}

${ selectedRow ? `

${escapeHtml(selectedRow.displayName || selectedRow.driverName)} • ${escapeHtml(selectedRow.subLabel || selectedRow.carName || "-")}

${t("table.laps")}: ${selectedRow.laps}

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

${t("table.last_lap")}: ${formatLap(selectedRow.lastLapMs)}

${t("timing.lap_history")}

${ selectedPassings.length ? renderTable( [t("table.lap"), t("table.last_lap"), t("table.status")], selectedPassings.map( (passing, index) => ` ${index + 1} ${formatLap(passing.lapMs)} ${escapeHtml(getPassingValidationLabel(passing))} ` ) ) : `

${t("timing.no_lap_history")}

` }
` : `

${t("judging.selected_none")}

` }

${t("judging.action_log")}

${ actionLog.length ? renderTable( [t("table.time"), t("table.driver"), t("events.actions"), t("table.status"), ""], actionLog.map( (entry) => ` ${new Date(entry.ts).toLocaleTimeString()} ${escapeHtml(entry.displayName || "-")} ${escapeHtml(entry.action || "-")} ${escapeHtml(entry.detail || "-")}${entry.undoneAt ? ` • ${escapeHtml(t("judging.filter_log_undo"))}` : ""} ${entry.undo && !entry.undoneAt ? `` : ""} ` ) ) : `

${t("judging.no_action_log")}

` }
`; leaderboard.forEach((row) => { document.getElementById(`judge-select-${row.key}`)?.addEventListener("click", () => { setSelectedJudgeKey(row.key); renderJudgingView(deps); }); }); document.getElementById("judgingCompetitorFilter")?.addEventListener("change", (event) => { const input = event.currentTarget; if (!(input instanceof HTMLSelectElement)) { return; } setJudgingCompetitorFilter(input.value); renderJudgingView(deps); }); document.getElementById("judgingLogFilter")?.addEventListener("change", (event) => { const input = event.currentTarget; if (!(input instanceof HTMLSelectElement)) { return; } setJudgingLogFilter(input.value); renderJudgingView(deps); }); document.getElementById("judgingExportLog")?.addEventListener("click", () => { const rows = (result.adjustments || []).map((entry) => ({ time: new Date(entry.ts).toISOString(), competitor: entry.displayName || "-", action: entry.action || "-", detail: entry.detail || "-", category: entry.category || "-", undoneAt: entry.undoneAt ? new Date(entry.undoneAt).toISOString() : "", })); const blob = new Blob([JSON.stringify({ session: active.name, items: rows }, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = `${active.name.replaceAll(/\s+/g, "_")}_judging_log.json`; link.click(); URL.revokeObjectURL(url); }); document.getElementById("judgingUndoLast")?.addEventListener("click", () => { if (!latestUndoable) { alert(t("judging.no_undo")); return; } undoJudgingAdjustment(active, latestUndoable.id); renderJudgingView(deps); }); actionLog.forEach((entry) => { if (!entry.undo || entry.undoneAt) { return; } document.getElementById(`judge-undo-${entry.id}`)?.addEventListener("click", () => { undoJudgingAdjustment(active, entry.id); renderJudgingView(deps); }); }); if (!selectedRow) { return; } document.getElementById("judgeLapPlus")?.addEventListener("click", () => { applyCompetitorCorrection(active, selectedRow, { lapDelta: 1 }); renderJudgingView(deps); }); document.getElementById("judgeLapMinus")?.addEventListener("click", () => { applyCompetitorCorrection(active, selectedRow, { lapDelta: -1 }); renderJudgingView(deps); }); document.getElementById("judgeSecPlus")?.addEventListener("click", () => { applyCompetitorCorrection(active, selectedRow, { timeMsDelta: 1000 }); renderJudgingView(deps); }); document.getElementById("judgeFivePlus")?.addEventListener("click", () => { applyCompetitorCorrection(active, selectedRow, { timeMsDelta: 5000 }); renderJudgingView(deps); }); document.getElementById("judgeSecMinus")?.addEventListener("click", () => { applyCompetitorCorrection(active, selectedRow, { timeMsDelta: -1000 }); renderJudgingView(deps); }); document.getElementById("judgeInvalidate")?.addEventListener("click", () => { invalidateCompetitorLastLap(active, selectedRow); renderJudgingView(deps); }); document.getElementById("judgeRestore")?.addEventListener("click", () => { restoreCompetitorLastInvalidLap(active, selectedRow); renderJudgingView(deps); }); document.getElementById("judgeReset")?.addEventListener("click", () => { applyCompetitorCorrection(active, selectedRow, { reset: true }); renderJudgingView(deps); }); }