// TODO(sync-refactor): move sync logic into src/sync.js
import { appState } from "./state.js";
import { formatUTCTime } from "./utils.js";
import { VIDEO_FPS } from "./constants.js";
function getTimingColor(diffMs) {
if (diffMs >= 48 && diffMs <= 52) {
return "#00FF00"; // Bright Green (Perfect)
} else if (diffMs >= 40 && diffMs <= 60) {
return "#98FB98"; // Pale Green (Good)
} else if ((diffMs >= 30 && diffMs < 40) || (diffMs > 60 && diffMs <= 70)) {
return "#FFEB3B"; // Yellow (Noticeable)
} else if ((diffMs >= 20 && diffMs < 30) || (diffMs > 70 && diffMs <= 100)) {
return "#FFA500"; // Orange (Warning)
} else if (diffMs > 300) {
return "#c339ffff"; // Dark Violet (Extreme > 300ms)
} else if (diffMs > 150) {
return "#8B0000"; // Dark Red (Severe > 150ms)
} else {
return "#FF4500"; // Red (Critical 100-150ms or < 20ms)
}
}
// --- DOM Element References --- //
export const startScreenModal = document.getElementById("start-screen-modal");
export const startDropZone = document.getElementById("start-drop-zone");
export const startLoadJsonBtn = document.getElementById("start-load-json-btn");
export const startLoadVideoBtn = document.getElementById("start-load-video-btn");
export const startClearCacheBtn = document.getElementById("start-clear-cache-btn");
export const startProgressContainer = document.getElementById("start-progress-container");
export const startProgressBar = document.getElementById("start-progress-bar");
export const startProgressText = document.getElementById("start-progress-text");
export const startUserManualBtn = document.getElementById("start-user-manual-btn");
export const startCodebaseBtn = document.getElementById("start-codebase-btn");
export const startChangelogBtn = document.getElementById("start-changelog-btn");
export const startThemeToggleBtn = document.getElementById("start-theme-toggle");
export const startThemeToggleDarkIcon = document.getElementById("start-theme-toggle-dark-icon");
export const startThemeToggleLightIcon = document.getElementById("start-theme-toggle-light-icon");
export const globalDragOverlay = document.getElementById("global-drag-overlay");
export const themeToggleBtn = document.getElementById("theme-toggle");
export const canvasContainer = document.getElementById("canvas-container");
export const canvasPlaceholder = document.getElementById("canvas-placeholder");
export const videoPlayer = document.getElementById("video-player");
export const videoPlaceholder = document.getElementById("video-placeholder");
export const loadJsonBtn = document.getElementById("load-json-btn");
export const loadVideoBtn = document.getElementById("load-video-btn");
export const loadCanBtn = document.getElementById("load-can-btn");
export const jsonFileInput = document.getElementById("json-file-input");
export const videoFileInput = document.getElementById("video-file-input");
export const canFileInput = document.getElementById("can-file-input");
export const playPauseBtn = document.getElementById("play-pause-btn");
export const stopBtn = document.getElementById("stop-btn");
export const timelineSlider = document.getElementById("timeline-slider");
export const frameCounter = document.getElementById("frame-counter");
export const offsetInput = document.getElementById("offset-input");
export const speedSlider = document.getElementById("speed-slider");
export const speedDisplay = document.getElementById("speed-display");
export const featureToggles = document.getElementById("feature-toggles");
export const toggleSnrColor = document.getElementById("toggle-snr-color");
export const toggleClusterColor = document.getElementById("toggle-cluster-color");
export const toggleInlierColor = document.getElementById("toggle-inlier-color");
export const toggleStationaryColor = document.getElementById("toggle-stationary-color");
export const toggleVelocity = document.getElementById("toggle-velocity");
export const toggleTracks = document.getElementById("toggle-tracks");
export const toggleEgoSpeed = document.getElementById("toggle-ego-speed");
export const toggleFrameNorm = document.getElementById("toggle-frame-norm");
export const toggleDebugOverlay = document.getElementById("toggle-debug-overlay");
export const egoSpeedDisplay = document.getElementById("ego-speed-display");
export const canSpeedDisplay = document.getElementById("can-speed-display");
export const debugOverlay = document.getElementById("debug-overlay");
export const toggleDebug2Overlay = document.getElementById("toggle-debug2-overlay");
export const snrMinInput = document.getElementById("snr-min-input");
export const snrMaxInput = document.getElementById("snr-max-input");
export const applySnrBtn = document.getElementById("apply-snr-btn");
export const autoOffsetIndicator = document.getElementById("auto-offset-indicator");
export const clearCacheBtn = document.getElementById("clear-cache-btn");
export const speedGraphContainer = document.getElementById("speed-graph-container");
export const speedGraphPlaceholder = document.getElementById("speed-graph-placeholder");
export const modalContainer = document.getElementById("modal-container");
export const modalOverlay = document.getElementById("modal-overlay");
export const modalContent = document.getElementById("modal-content");
export const modalText = document.getElementById("modal-text");
export const modalOkBtn = document.getElementById("modal-ok-btn");
export const modalCancelBtn = document.getElementById("modal-cancel-btn");
export const toggleCloseUp = document.getElementById("toggle-close-up");
export const togglePredictedPos = document.getElementById("toggle-predicted-pos");
export const toggleCovariance = document.getElementById("toggle-covariance");
export const toggleVehicleDimensions = document.getElementById("toggle-vehicle-dimensions");
export const modalProgressContainer = document.getElementById("modal-progress-container");
export const modalProgressBar = document.getElementById("modal-progress-bar");
export const modalProgressText = document.getElementById("modal-progress-text");
export const timelineTooltip = document.getElementById("timeline-tooltip");
export const radarInfoOverlay = document.getElementById("radar-info-overlay");
export const videoInfoOverlay = document.getElementById("video-info-overlay");
export const saveSessionBtn = document.getElementById("save-session-btn");
export const loadSessionBtn = document.getElementById("load-session-btn");
export const sessionFileInput = document.getElementById("session-file-input");
export const ttcModeDefault = document.getElementById("ttc-mode-default");
export const ttcModeCustom = document.getElementById("ttc-mode-custom");
export const customTtcPanel = document.getElementById("custom-ttc-panel");
export const ttcColorCritical = document.getElementById("ttc-color-critical");
export const ttcTimeCritical = document.getElementById("ttc-time-critical");
export const ttcColorHigh = document.getElementById("ttc-color-high");
export const ttcTimeHigh = document.getElementById("ttc-time-high");
export const ttcColorMedium = document.getElementById("ttc-color-medium");
export const ttcTimeMedium = document.getElementById("ttc-time-medium");
export const ttcColorLow = document.getElementById("ttc-color-low");
export const collapsibleMenu = document.getElementById("collapsible-menu");
export const toggleMenuBtn = document.getElementById("toggle-menu-btn");
export const fullscreenBtn = document.getElementById("fullscreen-btn");
export const mainContent = document.querySelector("main");
export const closeMenuBtn = document.getElementById("close-menu-btn");
export const fullscreenEnterIcon = document.getElementById("fullscreen-enter-icon");
export const fullscreenExitIcon = document.getElementById("fullscreen-exit-icon");
export const menuScrim = document.getElementById("menu-scrim");
export const toggleConfirmedOnly = document.getElementById("toggle-confirmed-only");
export const explorerBtn = document.getElementById("explorer-btn");
export const shortcutsBtn = document.getElementById("shortcuts-btn");
export const shortcutsModal = document.getElementById("shortcuts-modal");
export const shortcutsModalCloseBtn = document.getElementById("shortcuts-modal-close-btn");
export const userManualBtn = document.getElementById("user-manual-btn");
export const guideModal = document.getElementById("guide-modal");
export const guideModalCloseBtn = document.getElementById("guide-modal-close-btn");
export const codebaseBtn = document.getElementById("codebase-btn");
export const codebaseModal = document.getElementById("codebase-modal");
export const codebaseModalCloseBtn = document.getElementById("codebase-modal-close-btn");
export const changelogBtn = document.getElementById("changelog-btn");
export const changelogModal = document.getElementById("changelog-modal");
export const changelogModalCloseBtn = document.getElementById("changelog-modal-close-btn");
export const rangeSlider = document.getElementById("range-slider");
export const rangeValueDisplay = document.getElementById("range-value-display");
//----------------------Reset UI for New file Load----------------------//
// Resets the UI to make sure everything is clean before new files load.
// @param {boolean} isNewVideo - If true, the video player will be reset. If false, existing video is preserved.
export function resetUIForNewLoad(isNewVideo = true) {
console.log(`Resetting UI for new file load. New Video: ${isNewVideo}`);
// Hide feature toggles
featureToggles.classList.add("hidden");
// Reset the FPS counter state to prevent incorrect calculations on reload
appState.fps = 0;
appState.lastOverlayUpdateTime = 0;
// --- Conditional Video Reset ---
if (isNewVideo || !videoPlayer.src) {
// Reset video UI: Show placeholder, hide player, clear source
videoPlaceholder.classList.remove('hidden');
videoPlayer.classList.add('hidden');
videoPlayer.src = ''; // Clear the video source
videoInfoOverlay.classList.add('hidden');
} else {
// Preserve video UI: Ensure player is visible, placeholder hidden
videoPlaceholder.classList.add('hidden');
videoPlayer.classList.remove('hidden');
// Do NOT clear videoPlayer.src
videoInfoOverlay.classList.remove('hidden');
}
// Show canvas placeholder (will be hidden later if data loads)
canvasPlaceholder.style.display = 'flex';
// Always hide radar overlay initially
radarInfoOverlay.classList.add('hidden');
// Reset offset indicator state
autoOffsetIndicator.classList.add("hidden");
autoOffsetIndicator.textContent = "";
autoOffsetIndicator.className = "text-xs font-bold ml-2 hidden"; // Reset classes
// Remove the p5 sketches completely
if (appState.p5_instance) {
appState.p5_instance.remove();
appState.p5_instance = null;
}
if (appState.rawP5_instance) {
appState.rawP5_instance.remove();
appState.rawP5_instance = null;
}
if (appState.zoomSketchInstance) {
appState.zoomSketchInstance.remove();
appState.zoomSketchInstance = null;
}
if (appState.speedGraphInstance) {
appState.speedGraphInstance.remove();
appState.speedGraphInstance = null;
}
// Reset the speed graph container
speedGraphPlaceholder.classList.remove('hidden');
}
//----------------------CAN DISPLAY UPDATE Function----------------------//
// Updates the CAN speed display based on the current media time.
//----------------------DEBUG OVERLAY UPDATE Function----------------------//
// Updates the debug overlay with various synchronization and time information.
export function updateDebugOverlay(currentMediaTime) {
// Check the state of both debug toggles
const isDebug1Visible = toggleDebugOverlay.checked;
const isDebug2Visible = toggleDebug2Overlay.checked;
// If neither is checked, hide the overlay and stop
if (!isDebug1Visible && !isDebug2Visible) {
debugOverlay.classList.add("hidden"); // Hide debug overlay
return;
}
// If at least one is checked, show the overlay
debugOverlay.classList.remove("hidden"); // Show debug overlay.
let content = [];
// --- Logic for the original debug overlay ---
if (isDebug1Visible) {
content.push(`--- Basic Info ---`);
const baseTimeMs = appState.videoStartDate ? appState.videoStartDate.getTime() : (appState.radarStartTimeMs || 0);
const videoAbsoluteTimeMs = baseTimeMs + currentMediaTime * 1000;
let timeString = `Media Time (s): ${currentMediaTime.toFixed(2)}`; // Two decimal places
if (videoPlayer && !isNaN(videoPlayer.duration) && videoPlayer.duration > 0) {
timeString += ` / ${videoPlayer.duration.toFixed(2)}`; // Add total duration with two decimal places
}
content.push(timeString);
content.push(`Video Frame: ${Math.floor(currentMediaTime * VIDEO_FPS)}`);
content.push(
`Vid Abs Time: ${new Date(videoAbsoluteTimeMs)
.toISOString()
.split("T")[1]
.replace("Z", "")}`
); // Format and display video absolute time
if (
appState.vizData &&
appState.vizData.radarFrames[appState.currentFrame]
) {
content.push(`Radar Frame: ${appState.currentFrame + 1}`);
const frameData = appState.vizData.radarFrames[appState.currentFrame];
const frameTime = frameData.timestampMs;
const baseTimeMs = appState.videoStartDate ? appState.videoStartDate.getTime() : (appState.radarStartTimeMs || 0);
content.push(
`Radar Abs Time: ${new Date(baseTimeMs + frameTime)
.toISOString()
.split("T")[1]
.replace("Z", "")}`
); // Format and display radar absolute time
}
}
// --- Logic for the new advanced debug overlay ---
if (isDebug2Visible) {
content.push(`--- Sync Diagnostics ---`);
if (
appState.vizData &&
appState.vizData.radarFrames[appState.currentFrame]
) {
const currentRadarFrame =
appState.vizData.radarFrames[appState.currentFrame];
// The correct drift is the difference between the video's actual time and the pre-calculated "baked-in" sync time for the current radar frame.
const driftMs = (currentMediaTime - currentRadarFrame.videoSyncedTime) * 1000;
// Style the drift value to be green if sync is good, and red if it's off.
const driftColor = Math.abs(driftMs) > 50 ? "#FF6347" : "#98FB98"; // Tomato red or Pale green
content.push(`Video Time (s): ${currentMediaTime.toFixed(3)}`); // Display current video time
content.push(`Target Sync Time (s): ${currentRadarFrame.videoSyncedTime.toFixed(3)}`);
content.push(`Drift (ms): ${driftMs.toFixed(0)}`);
const videoStart = appState.videoStartDate ? appState.videoStartDate.toISOString() : new Date(0).toISOString();
content.push(`Video Start Time: ${videoStart}`);
content.push(`Radar Start Time: ${new Date(appState.radarStartTimeMs || 0).toISOString()}`);
content.push(`Calculated Offset (ms): ${offsetInput.value}`); // Display calculated offset.
const renderTime = appState.lastFrameRenderTime;
// Color is green if render time is under 33ms (~30fps budget), otherwise red
const renderTimeColor = renderTime > 33 ? "#FF6347" : "#98FB98";
content.push(`Frame Render Time: ${renderTime.toFixed(1)}ms`);
const videoRenderTime = appState.videoFrameRenderTime;
// Color is green if render time is under 34ms (~30fps), otherwise red
const videoRenderTimeColor = videoRenderTime > 34 ? "#FF6347" : "#98FB98";
content.push(`Video Frame Time: ${videoRenderTime.toFixed(1)}ms`);
} else {
content.push("Load video and radar data to see sync info."); // Prompt to load data.
}
}
debugOverlay.innerHTML = content.join("
"); // Update debug overlay content.
}
// This function checks the state of the color toggles and returns the active mode.
function getCurrentColorMode() {
if (toggleSnrColor.checked) return "Color by SNR (1)";
if (toggleClusterColor.checked) return "Cluster Mode (2)";
if (toggleInlierColor.checked) return "Color by Inlier (3)";
if (toggleStationaryColor.checked) return "Color by Stationary (4)";
return "Default"; // The default mode when no specific color toggle is checked
}
// Cache for DOM elements to avoid querySelector/getElementById every frame
let overlayCache = null;
let videoOverlayCache = null;
// Cache for conditional rendering
let lastDrawnFrame = -1;
let lastDrawnScale = -1;
export function updatePersistentOverlays(currentMediaTime) {
// If the advanced debug overlay is visible, hide the persistent overlays and exit.
if (toggleDebug2Overlay.checked) {
radarInfoOverlay.classList.add("hidden");
videoInfoOverlay.classList.add("hidden");
return;
}
// If we don't have the necessary data, hide the overlays and exit.
if (!appState.vizData) {
radarInfoOverlay.classList.add("hidden");
videoInfoOverlay.classList.add("hidden");
return;
}
// Otherwise, make sure they are visible.
radarInfoOverlay.classList.remove("hidden");
videoInfoOverlay.classList.remove("hidden");
// --- Update Radar Persistent Overlay ---
const currentRadarFrame = appState.vizData.radarFrames[appState.currentFrame];
const frameData = appState.vizData.radarFrames[appState.currentFrame];
const motionState = frameData.motionState;
if (currentRadarFrame) {
const absRadarTime = new Date(
appState.radarStartTimeMs + currentRadarFrame.timestamp
);
const driftMs = (currentMediaTime - currentRadarFrame.videoSyncedTime) * 1000;
const driftColor = Math.abs(driftMs) > 50 ? "#FF6347" : "#98FB98"; // Tomato or Pale Green
const colorMode = getCurrentColorMode();
const fps = appState.fps;
const fpsColor = fps >= 58 && fps <= 62 ? "#98FB98" : "#FF6347"; // Pale Green or Tomato
const interFrameTime = currentRadarFrame.interFrameTime;
const iftColor = getTimingColor(interFrameTime);
// --- OPTIMIZATION: One-time DOM Setup & Caching ---
if (!overlayCache) {
radarInfoOverlay.innerHTML = `