Browse Source

feat(ui): Refactor Zoom Sketch to floating panel with auto-hide UX

- Extracted drag/resize logic from Data Explorer into reusable `ui.js` utility.
- Migrated Zoom Sketch from GridStack into a free-floating, draggable, and resizable `z-30` overlay (default size 900x455).
- Restored and optimized auto-hide timing lifecycle for Close-Up mode:
  - 0-5s: Clean freeze for data inspection without hovering.
  - 5-8s: Fading visual countdown warning prior to panel closure.
- Implemented state flag `zoomPanelExplicitlyClosed` guaranteeing the "X" button correctly masks rapid re-triggers.
- Bound 'g' toggle shortcut to aggressively wipe/close panels when exiting God Mode entirely.
- Added visual "Mouse pointer Out of Bounds" overlay when cursor exits main radar canvas.
refactor/sync-centralize
RUSHIL AMBARISH KADU 2 months ago
parent
commit
18a7c5640e
  1. 226
      steps/index.html
  2. 94
      steps/src/dataExplorer.js
  3. 82
      steps/src/p5/radarSketch.js
  4. 28
      steps/src/p5/zoomSketch.js
  5. 3
      steps/src/state.js
  6. 127
      steps/src/ui.js

226
steps/index.html

@ -10,11 +10,11 @@
<!-- <script src="https://cdn.tailwindcss.com"></script> --> <!-- <script src="https://cdn.tailwindcss.com"></script> -->
<script> <script>
// Silence Tailwind CDN production warning // Silence Tailwind CDN production warning
(function() {
(function () {
const suppress = (msg) => typeof msg === 'string' && (msg.includes('cdn.tailwindcss.com') || msg.includes('Tailwind CSS in production')); const suppress = (msg) => typeof msg === 'string' && (msg.includes('cdn.tailwindcss.com') || msg.includes('Tailwind CSS in production'));
['log', 'info', 'warn', 'error'].forEach(method => { ['log', 'info', 'warn', 'error'].forEach(method => {
const original = console[method]; const original = console[method];
console[method] = function() {
console[method] = function () {
if (arguments[0] && suppress(arguments[0])) return; if (arguments[0] && suppress(arguments[0])) return;
original.apply(console, arguments); original.apply(console, arguments);
}; };
@ -23,7 +23,7 @@
</script> </script>
<script src="./vendor/tailwind-cdn.js"></script> <script src="./vendor/tailwind-cdn.js"></script>
<script> <script>
!window.tailwind && (function() {
!window.tailwind && (function () {
var s = document.createElement('script'); var s = document.createElement('script');
s.src = 'https://cdn.tailwindcss.com'; s.src = 'https://cdn.tailwindcss.com';
document.head.appendChild(s); document.head.appendChild(s);
@ -33,7 +33,7 @@
<!-- <script src="https://unpkg.com/oboe@2.1.5/dist/oboe-browser.min.js"></script> --> <!-- <script src="https://unpkg.com/oboe@2.1.5/dist/oboe-browser.min.js"></script> -->
<script src="./vendor/oboe.min.js"></script> <script src="./vendor/oboe.min.js"></script>
<script> <script>
!window.oboe && (function() {
!window.oboe && (function () {
var s = document.createElement('script'); var s = document.createElement('script');
s.src = 'https://unpkg.com/oboe@2.1.5/dist/oboe-browser.min.js'; s.src = 'https://unpkg.com/oboe@2.1.5/dist/oboe-browser.min.js';
document.head.appendChild(s); document.head.appendChild(s);
@ -43,7 +43,7 @@
<!-- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> --> <!-- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> -->
<script src="./vendor/chart.min.js"></script> <script src="./vendor/chart.min.js"></script>
<script> <script>
!window.Chart && (function() {
!window.Chart && (function () {
var s = document.createElement('script'); var s = document.createElement('script');
s.src = 'https://cdn.jsdelivr.net/npm/chart.js'; s.src = 'https://cdn.jsdelivr.net/npm/chart.js';
document.head.appendChild(s); document.head.appendChild(s);
@ -54,7 +54,7 @@
<!-- <script src="https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js"></script> --> <!-- <script src="https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js"></script> -->
<script src="./vendor/ag-grid-community.min.js"></script> <script src="./vendor/ag-grid-community.min.js"></script>
<script> <script>
!window.agGrid && (function() {
!window.agGrid && (function () {
var s = document.createElement('script'); var s = document.createElement('script');
s.src = 'https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js'; s.src = 'https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js';
document.head.appendChild(s); document.head.appendChild(s);
@ -64,17 +64,17 @@
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script> --> <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script> -->
<script src="./vendor/p5.js"></script> <script src="./vendor/p5.js"></script>
<script> <script>
!window.p5 && (function() {
!window.p5 && (function () {
var s = document.createElement('script'); var s = document.createElement('script');
s.src = 'https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js'; s.src = 'https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js';
document.head.appendChild(s); document.head.appendChild(s);
})(); })();
</script> </script>
<link rel="stylesheet" href="./vendor/gridstack.min.css"/>
<link rel="stylesheet" href="./vendor/gridstack.min.css" />
<script src="./vendor/gridstack-all.js"></script> <script src="./vendor/gridstack-all.js"></script>
<script> <script>
!window.GridStack && (function() {
!window.GridStack && (function () {
// If local GridStack for some reason didn't load, try the CDN // If local GridStack for some reason didn't load, try the CDN
var s = document.createElement('script'); var s = document.createElement('script');
s.src = 'https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.1.2/gridstack-all.js'; s.src = 'https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.1.2/gridstack-all.js';
@ -254,7 +254,8 @@
<body class="bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200 flex flex-col min-h-screen"> <body class="bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200 flex flex-col min-h-screen">
<!-- START SCREEN MODAL --> <!-- START SCREEN MODAL -->
<div id="start-screen-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-gray-100 dark:bg-gray-900 transition-opacity duration-300">
<div id="start-screen-modal"
class="fixed inset-0 z-50 flex items-center justify-center bg-gray-100 dark:bg-gray-900 transition-opacity duration-300">
<!-- Header buttons on start screen --> <!-- Header buttons on start screen -->
<div class="absolute top-4 right-4 flex items-center gap-2"> <div class="absolute top-4 right-4 flex items-center gap-2">
@ -270,49 +271,72 @@
class="bg-cyan-100 dark:bg-cyan-700 text-cyan-800 dark:text-cyan-100 border border-cyan-300 dark:border-cyan-600 shadow-sm hover:bg-cyan-200 dark:hover:bg-cyan-600 active:scale-95 active:shadow-inner font-medium rounded-lg text-xs px-4 py-2 transition-all text-center"> class="bg-cyan-100 dark:bg-cyan-700 text-cyan-800 dark:text-cyan-100 border border-cyan-300 dark:border-cyan-600 shadow-sm hover:bg-cyan-200 dark:hover:bg-cyan-600 active:scale-95 active:shadow-inner font-medium rounded-lg text-xs px-4 py-2 transition-all text-center">
What's<br>New? What's<br>New?
</button> </button>
<button id="start-theme-toggle" type="button" class="bg-indigo-900 dark:bg-amber-100 text-indigo-100 dark:text-amber-800 border border-indigo-700 dark:border-amber-300 shadow-sm hover:bg-indigo-800 dark:hover:bg-amber-200 active:scale-95 active:shadow-inner flex flex-row items-center gap-2 rounded-lg text-xs px-4 py-3.5 transition-all">
<button id="start-theme-toggle" type="button"
class="bg-indigo-900 dark:bg-amber-100 text-indigo-100 dark:text-amber-800 border border-indigo-700 dark:border-amber-300 shadow-sm hover:bg-indigo-800 dark:hover:bg-amber-200 active:scale-95 active:shadow-inner flex flex-row items-center gap-2 rounded-lg text-xs px-4 py-3.5 transition-all">
<svg id="start-theme-toggle-dark-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <svg id="start-theme-toggle-dark-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path> <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
</svg> </svg>
<svg id="start-theme-toggle-light-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <svg id="start-theme-toggle-light-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm2 0a1 1 0 112 0v-1a1 1 0 00-2 0v1zm-14 0a1 1 0 112 0v-1a1 1 0 00-2 0v1zm8 6a1 1 0 100 2 1 1 0 000-2zm5.657-11.657a1 1 0 010 1.414l-1.414 1.414a1 1 0 11-1.414-1.414l1.414-1.414a1 1 0 011.414 0zm-11.314 0a1 1 0 011.414 0l1.414 1.414a1 1 0 11-1.414 1.414l-1.414-1.414a1 1 0 010-1.414zm11.314 11.314a1 1 0 01-1.414 0l-1.414-1.414a1 1 0 011.414-1.414l1.414 1.414a1 1 0 010 1.414zm-11.314 0a1 1 0 010-1.414l1.414-1.414a1 1 0 011.414 1.414l-1.414 1.414a1 1 0 01-1.414 0z" fill-rule="evenodd" clip-rule="evenodd"></path>
<path
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm2 0a1 1 0 112 0v-1a1 1 0 00-2 0v1zm-14 0a1 1 0 112 0v-1a1 1 0 00-2 0v1zm8 6a1 1 0 100 2 1 1 0 000-2zm5.657-11.657a1 1 0 010 1.414l-1.414 1.414a1 1 0 11-1.414-1.414l1.414-1.414a1 1 0 011.414 0zm-11.314 0a1 1 0 011.414 0l1.414 1.414a1 1 0 11-1.414 1.414l-1.414-1.414a1 1 0 010-1.414zm11.314 11.314a1 1 0 01-1.414 0l-1.414-1.414a1 1 0 011.414-1.414l1.414 1.414a1 1 0 010 1.414zm-11.314 0a1 1 0 010-1.414l1.414-1.414a1 1 0 011.414 1.414l-1.414 1.414a1 1 0 01-1.414 0z"
fill-rule="evenodd" clip-rule="evenodd"></path>
</svg> </svg>
</button> </button>
</div> </div>
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-[0_0_50px_rgba(0,0,0,0.2)] p-10 w-full max-w-4xl text-center border border-gray-200 dark:border-gray-700 relative z-10">
<div
class="bg-white dark:bg-gray-800 rounded-2xl shadow-[0_0_50px_rgba(0,0,0,0.2)] p-10 w-full max-w-4xl text-center border border-gray-200 dark:border-gray-700 relative z-10">
<h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-2 tracking-tight">Data Synchronizer</h1> <h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-2 tracking-tight">Data Synchronizer</h1>
<p class="text-gray-500 dark:text-gray-400 mb-8 text-lg">Provide radar dataset and video parameters to initialize workspace</p>
<div id="start-drop-zone" class="border-4 border-dashed border-gray-300 dark:border-gray-600 rounded-xl p-20 mb-8 hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-gray-700 transition-colors cursor-pointer group">
<svg class="mx-auto h-16 w-16 text-gray-400 group-hover:text-blue-500 transition-colors mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
<p class="text-gray-500 dark:text-gray-400 mb-8 text-lg">Provide radar dataset and video parameters to initialize
workspace</p>
<div id="start-drop-zone"
class="border-4 border-dashed border-gray-300 dark:border-gray-600 rounded-xl p-20 mb-8 hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-gray-700 transition-colors cursor-pointer group">
<svg class="mx-auto h-16 w-16 text-gray-400 group-hover:text-blue-500 transition-colors mb-4" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg> </svg>
<p class="text-xl font-medium text-gray-700 dark:text-gray-300">Drag & Drop files here</p> <p class="text-xl font-medium text-gray-700 dark:text-gray-300">Drag & Drop files here</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-2">Requires .json (and optionally video) format</p> <p class="text-sm text-gray-500 dark:text-gray-400 mt-2">Requires .json (and optionally video) format</p>
</div> </div>
<div class="flex items-center justify-center gap-6"> <div class="flex items-center justify-center gap-6">
<button id="start-load-json-btn" class="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-all text-lg font-semibold w-48 shadow-lg active:scale-95 active:shadow-inner flex items-center justify-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
<button id="start-load-json-btn"
class="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-all text-lg font-semibold w-48 shadow-lg active:scale-95 active:shadow-inner flex items-center justify-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">
</path>
</svg>
Select JSON Select JSON
</button> </button>
<button id="start-load-video-btn" class="bg-green-600 text-white px-6 py-3 rounded-lg hover:bg-green-700 transition-all text-lg font-semibold w-48 shadow-lg active:scale-95 active:shadow-inner flex items-center justify-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
<button id="start-load-video-btn"
class="bg-green-600 text-white px-6 py-3 rounded-lg hover:bg-green-700 transition-all text-lg font-semibold w-48 shadow-lg active:scale-95 active:shadow-inner flex items-center justify-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z">
</path>
</svg>
Select Video Select Video
</button> </button>
</div> </div>
<div class="mt-8 text-sm text-gray-500 dark:text-gray-400"> <div class="mt-8 text-sm text-gray-500 dark:text-gray-400">
<button id="start-clear-cache-btn" class="hover:text-red-500 dark:hover:text-red-400 transition-colors uppercase tracking-wider text-xs font-bold">Clear Cached Session</button>
<button id="start-clear-cache-btn"
class="hover:text-red-500 dark:hover:text-red-400 transition-colors uppercase tracking-wider text-xs font-bold">Clear
Cached Session</button>
</div> </div>
<!-- INTEGRATED LOADER UI (Hidden by default) --> <!-- INTEGRATED LOADER UI (Hidden by default) -->
<div id="start-progress-container" class="hidden mt-6 w-full max-w-md mx-auto"> <div id="start-progress-container" class="hidden mt-6 w-full max-w-md mx-auto">
<div class="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700"> <div class="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
<div id="start-progress-bar" class="bg-blue-600 h-2.5 rounded-full transition-all duration-300" style="width: 0%"></div>
<div id="start-progress-bar" class="bg-blue-600 h-2.5 rounded-full transition-all duration-300"
style="width: 0%"></div>
</div>
<div id="start-progress-text"
class="text-sm font-medium text-gray-700 dark:text-gray-300 mt-2 text-center animate-pulse">Initializing...
</div> </div>
<div id="start-progress-text" class="text-sm font-medium text-gray-700 dark:text-gray-300 mt-2 text-center animate-pulse">Initializing...</div>
</div> </div>
</div> </div>
@ -329,7 +353,8 @@
<button id="fullscreen-btn" type="button" <button id="fullscreen-btn" type="button"
class="bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 shadow-sm hover:bg-gray-200 dark:hover:bg-gray-600 active:scale-95 active:shadow-inner font-medium rounded-lg text-xs px-4 py-2 transition-all"> class="bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 shadow-sm hover:bg-gray-200 dark:hover:bg-gray-600 active:scale-95 active:shadow-inner font-medium rounded-lg text-xs px-4 py-2 transition-all">
FULLSCREEN<br> FULLSCREEN<br>
<span class="text-xs font-mono bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-200 px-1.5 py-0.5 rounded">(F11)
<span
class="text-xs font-mono bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-200 px-1.5 py-0.5 rounded">(F11)
</span> </span>
</button> </button>
<button id="explorer-btn" type="button" <button id="explorer-btn" type="button"
@ -356,12 +381,15 @@
class="bg-cyan-100 dark:bg-cyan-700 text-cyan-800 dark:text-cyan-100 border border-cyan-300 dark:border-cyan-600 shadow-sm hover:bg-cyan-200 dark:hover:bg-cyan-600 active:scale-95 active:shadow-inner font-medium rounded-lg text-xs px-4 py-2 transition-all text-center"> class="bg-cyan-100 dark:bg-cyan-700 text-cyan-800 dark:text-cyan-100 border border-cyan-300 dark:border-cyan-600 shadow-sm hover:bg-cyan-200 dark:hover:bg-cyan-600 active:scale-95 active:shadow-inner font-medium rounded-lg text-xs px-4 py-2 transition-all text-center">
What's<br>New? What's<br>New?
</button> </button>
<button id="theme-toggle" type="button" class="bg-indigo-900 dark:bg-amber-100 text-indigo-100 dark:text-amber-800 border border-indigo-700 dark:border-amber-300 shadow-sm hover:bg-indigo-800 dark:hover:bg-amber-200 active:scale-95 active:shadow-inner flex flex-row items-center gap-2 rounded-lg text-xs px-4 py-3.5 transition-all">
<button id="theme-toggle" type="button"
class="bg-indigo-900 dark:bg-amber-100 text-indigo-100 dark:text-amber-800 border border-indigo-700 dark:border-amber-300 shadow-sm hover:bg-indigo-800 dark:hover:bg-amber-200 active:scale-95 active:shadow-inner flex flex-row items-center gap-2 rounded-lg text-xs px-4 py-3.5 transition-all">
<svg id="theme-toggle-dark-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <svg id="theme-toggle-dark-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path> <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
</svg> </svg>
<svg id="theme-toggle-light-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <svg id="theme-toggle-light-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm2 0a1 1 0 112 0v-1a1 1 0 00-2 0v1zm-14 0a1 1 0 112 0v-1a1 1 0 00-2 0v1zm8 6a1 1 0 100 2 1 1 0 000-2zm5.657-11.657a1 1 0 010 1.414l-1.414 1.414a1 1 0 11-1.414-1.414l1.414-1.414a1 1 0 011.414 0zm-11.314 0a1 1 0 011.414 0l1.414 1.414a1 1 0 11-1.414 1.414l-1.414-1.414a1 1 0 010-1.414zm11.314 11.314a1 1 0 01-1.414 0l-1.414-1.414a1 1 0 011.414-1.414l1.414 1.414a1 1 0 010 1.414zm-11.314 0a1 1 0 010-1.414l1.414-1.414a1 1 0 011.414 1.414l-1.414 1.414a1 1 0 01-1.414 0z" fill-rule="evenodd" clip-rule="evenodd"></path>
<path
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm2 0a1 1 0 112 0v-1a1 1 0 00-2 0v1zm-14 0a1 1 0 112 0v-1a1 1 0 00-2 0v1zm8 6a1 1 0 100 2 1 1 0 000-2zm5.657-11.657a1 1 0 010 1.414l-1.414 1.414a1 1 0 11-1.414-1.414l1.414-1.414a1 1 0 011.414 0zm-11.314 0a1 1 0 011.414 0l1.414 1.414a1 1 0 11-1.414 1.414l-1.414-1.414a1 1 0 010-1.414zm11.314 11.314a1 1 0 01-1.414 0l-1.414-1.414a1 1 0 011.414-1.414l1.414 1.414a1 1 0 010 1.414zm-11.314 0a1 1 0 010-1.414l1.414-1.414a1 1 0 011.414 1.414l-1.414 1.414a1 1 0 01-1.414 0z"
fill-rule="evenodd" clip-rule="evenodd"></path>
</svg> </button> </svg> </button>
</div> </div>
</header> </header>
@ -414,8 +442,9 @@
(P)</label> (P)</label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox" id="toggle-covariance" <label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox" id="toggle-covariance"
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" /> Show Covariance</label> class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" /> Show Covariance</label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox" id="toggle-vehicle-dimensions"
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" checked /> Show Dimensions</label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox"
id="toggle-vehicle-dimensions" class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500"
checked /> Show Dimensions</label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox" <label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox"
id="toggle-confirmed-only" class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" id="toggle-confirmed-only" class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500"
checked /> checked />
@ -472,10 +501,6 @@
</div> </div>
</aside> </aside>
<div id="zoom-panel"
class="hidden fixed bottom-11 right-5 h-[50%] w-1/2 bg-white dark:bg-gray-800 z-20 shadow-2xl border-l-2 border-t-2 border-gray-300 dark:border-gray-600">
<div id="zoom-canvas-container" class="w-full h-full"></div>
</div>
<!-- Open Menu Button --> <!-- Open Menu Button -->
<button id="toggle-menu-btn" <button id="toggle-menu-btn"
class="fixed top-20 left-3 z-20 bg-white dark:bg-gray-700 p-2 rounded-md shadow-lg hover:bg-gray-100 dark:hover:bg-gray-600 transition-all"> class="fixed top-20 left-3 z-20 bg-white dark:bg-gray-700 p-2 rounded-md shadow-lg hover:bg-gray-100 dark:hover:bg-gray-600 transition-all">
@ -493,26 +518,33 @@
<div class="grid-stack grid-stack-12"> <div class="grid-stack grid-stack-12">
<!-- Radar Panel --> <!-- Radar Panel -->
<div class="grid-stack-item shadow-md rounded-lg" gs-x="0" gs-y="0" gs-w="6" gs-h="12"> <div class="grid-stack-item shadow-md rounded-lg" gs-x="0" gs-y="0" gs-w="6" gs-h="12">
<div class="grid-stack-item-content bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg overflow-visible flex flex-col relative group">
<div class="absolute top-0 left-0 w-full h-4 cursor-grab active:cursor-grabbing bg-transparent hover:bg-gray-300/30 z-[60] transition-colors rounded-t-lg" title="Drag to move panel"></div>
<div
class="grid-stack-item-content bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg overflow-visible flex flex-col relative group">
<div
class="absolute top-0 left-0 w-full h-4 cursor-grab active:cursor-grabbing bg-transparent hover:bg-gray-300/30 z-[60] transition-colors rounded-t-lg"
title="Drag to move panel"></div>
<div class="relative w-full h-full flex-grow"> <div class="relative w-full h-full flex-grow">
<div id="canvas-container" class="w-full h-full flex items-center justify-center relative"> <div id="canvas-container" class="w-full h-full flex items-center justify-center relative">
<p id="canvas-placeholder" class="hidden text-gray-500 dark:text-gray-400 text-lg">Load JSON data to start visualization</p>
<p id="canvas-placeholder" class="hidden text-gray-500 dark:text-gray-400 text-lg">Load JSON data to start
visualization</p>
<!-- Range Slider (Vertical) --> <!-- Range Slider (Vertical) -->
<div class="absolute bottom-8 left-2 flex flex-col items-center gap-2 z-20 group"> <div class="absolute bottom-8 left-2 flex flex-col items-center gap-2 z-20 group">
<span id="range-value-display" class="bg-black bg-opacity-60 text-white text-[10px] px-1.5 py-0.5 rounded font-mono">80m</span>
<span id="range-value-display"
class="bg-black bg-opacity-60 text-white text-[10px] px-1.5 py-0.5 rounded font-mono">80m</span>
<input type="range" id="range-slider" min="40" max="200" value="80" step="10" <input type="range" id="range-slider" min="40" max="200" value="80" step="10"
class="h-32 cursor-pointer accent-blue-600" class="h-32 cursor-pointer accent-blue-600"
style="writing-mode: vertical-lr; direction: rtl; width: 8px;" /> style="writing-mode: vertical-lr; direction: rtl; width: 8px;" />
<span class="text-[10px] font-bold text-gray-400 uppercase tracking-tighter" style="writing-mode: vertical-lr; transform: rotate(180deg);">Range</span>
<span class="text-[10px] font-bold text-gray-400 uppercase tracking-tighter"
style="writing-mode: vertical-lr; transform: rotate(180deg);">Range</span>
</div> </div>
</div> </div>
<div id="radar-info-overlay" <div id="radar-info-overlay"
class="absolute top-4 left-0 right-0 z-10 bg-black bg-opacity-60 text-white font-mono text-xs p-2 rounded-md hidden pointer-events-none"> class="absolute top-4 left-0 right-0 z-10 bg-black bg-opacity-60 text-white font-mono text-xs p-2 rounded-md hidden pointer-events-none">
</div> </div>
<div class="absolute bottom-2 left-1/2 -translate-x-1/2 flex items-center justify-center gap-4 z-20 pointer-events-none">
<div
class="absolute bottom-2 left-1/2 -translate-x-1/2 flex items-center justify-center gap-4 z-20 pointer-events-none">
<div id="ego-speed-display" <div id="ego-speed-display"
class="bg-black bg-opacity-60 text-white text-sm px-3 py-1.5 rounded-md hidden font-mono"></div> class="bg-black bg-opacity-60 text-white text-sm px-3 py-1.5 rounded-md hidden font-mono"></div>
<div id="can-speed-display" <div id="can-speed-display"
@ -524,8 +556,11 @@
<!-- Video Panel --> <!-- Video Panel -->
<div class="grid-stack-item shadow-md rounded-lg" gs-x="6" gs-y="0" gs-w="6" gs-h="8"> <div class="grid-stack-item shadow-md rounded-lg" gs-x="6" gs-y="0" gs-w="6" gs-h="8">
<div class="grid-stack-item-content bg-black rounded-lg overflow-hidden flex items-center justify-center relative group">
<div class="absolute top-0 left-0 w-full h-4 cursor-grab active:cursor-grabbing bg-transparent hover:bg-white/20 z-[60] transition-colors rounded-t-lg" title="Drag to move panel"></div>
<div
class="grid-stack-item-content bg-black rounded-lg overflow-hidden flex items-center justify-center relative group">
<div
class="absolute top-0 left-0 w-full h-4 cursor-grab active:cursor-grabbing bg-transparent hover:bg-white/20 z-[60] transition-colors rounded-t-lg"
title="Drag to move panel"></div>
<video id="video-player" class="w-full h-full object-contain hidden" muted playsinline></video> <video id="video-player" class="w-full h-full object-contain hidden" muted playsinline></video>
<p id="video-placeholder" class="hidden text-gray-500 text-lg">Load a video file</p> <p id="video-placeholder" class="hidden text-gray-500 text-lg">Load a video file</p>
@ -533,21 +568,57 @@
class="absolute top-4 left-2 z-[20] bg-black bg-opacity-60 text-white font-mono text-xs p-2 rounded-md hidden pointer-events-none"> class="absolute top-4 left-2 z-[20] bg-black bg-opacity-60 text-white font-mono text-xs p-2 rounded-md hidden pointer-events-none">
</div> </div>
<div id="debug-overlay" <div id="debug-overlay"
class="absolute top-0 left-0 bg-black bg-opacity-60 text-white p-2 font-mono text-xs hidden w-full z-[20] pointer-events-none"></div>
class="absolute top-0 left-0 bg-black bg-opacity-60 text-white p-2 font-mono text-xs hidden w-full z-[20] pointer-events-none">
</div>
</div> </div>
</div> </div>
<!-- SpeedGraph Panel --> <!-- SpeedGraph Panel -->
<div class="grid-stack-item shadow-md rounded-lg" gs-x="6" gs-y="8" gs-w="6" gs-h="4"> <div class="grid-stack-item shadow-md rounded-lg" gs-x="6" gs-y="8" gs-w="6" gs-h="4">
<div class="grid-stack-item-content bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg overflow-hidden flex items-center justify-center relative group">
<div class="absolute top-0 left-0 w-full h-4 cursor-grab active:cursor-grabbing bg-transparent hover:bg-gray-300/30 z-[60] transition-colors rounded-t-lg" title="Drag to move panel"></div>
<div
class="grid-stack-item-content bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg overflow-hidden flex items-center justify-center relative group">
<div
class="absolute top-0 left-0 w-full h-4 cursor-grab active:cursor-grabbing bg-transparent hover:bg-gray-300/30 z-[60] transition-colors rounded-t-lg"
title="Drag to move panel"></div>
<div id="speed-graph-container" class="w-full h-full flex items-center justify-center p-1 relative"> <div id="speed-graph-container" class="w-full h-full flex items-center justify-center p-1 relative">
<p id="speed-graph-placeholder" class="text-gray-500 dark:text-gray-400 text-lg text-center absolute pointer-events-none">Load JSON & Video to see speed graph</p>
<p id="speed-graph-placeholder"
class="text-gray-500 dark:text-gray-400 text-lg text-center absolute pointer-events-none">Load JSON &
Video to see speed graph</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- START: Floating Zoom Panel -->
<div id="zoom-panel"
class="hidden fixed bottom-11 right-5 bg-white dark:bg-gray-800 shadow-2xl rounded-lg z-30 flex flex-col border dark:border-gray-600"
style="width: 900px; height: 455px;">
<div id="zoom-panel-header"
class="flex items-center justify-between p-2 border-b dark:border-gray-700 cursor-move bg-gray-100 dark:bg-gray-700 rounded-t-lg">
<h2 class="text-sm font-bold text-gray-500 uppercase tracking-wider ml-2 pointer-events-none">God Mode</h2>
<button id="close-zoom-btn"
class="text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg p-1 px-2">&times;</button>
</div>
<div class="resizer resizer-t"></div>
<div class="resizer resizer-r"></div>
<div class="resizer resizer-b"></div>
<div class="resizer resizer-l"></div>
<div class="resizer resizer-tl"></div>
<div class="resizer resizer-tr"></div>
<div class="resizer resizer-br"></div>
<div class="resizer resizer-bl"></div>
<div id="zoom-canvas-container"
class="flex-grow relative flex items-center justify-center overflow-hidden rounded-b-lg">
</div>
</div>
<!-- END: Floating Zoom Panel -->
<div id="data-explorer-panel" <div id="data-explorer-panel"
class="hidden fixed bottom-24 right-4 bg-white dark:bg-gray-800 shadow-2xl rounded-lg z-30 flex flex-col w-full max-w-2xl h-1/2 border dark:border-gray-600"> class="hidden fixed bottom-24 right-4 bg-white dark:bg-gray-800 shadow-2xl rounded-lg z-30 flex flex-col w-full max-w-2xl h-1/2 border dark:border-gray-600">
@ -651,11 +722,14 @@
</footer> </footer>
<!-- SHORTCUTS MODAL --> <!-- SHORTCUTS MODAL -->
<div id="shortcuts-modal" class="fixed inset-0 z-50 flex items-center justify-center hidden bg-black bg-opacity-80 backdrop-blur-sm p-4">
<div class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl w-full max-w-5xl max-h-[90vh] overflow-y-auto relative flex flex-col">
<div id="shortcuts-modal"
class="fixed inset-0 z-50 flex items-center justify-center hidden bg-black bg-opacity-80 backdrop-blur-sm p-4">
<div
class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl w-full max-w-5xl max-h-[90vh] overflow-y-auto relative flex flex-col">
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-900 z-10">
<div
class="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-900 z-10">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="text-3xl">⌨️</span> <span class="text-3xl">⌨️</span>
<div> <div>
@ -663,7 +737,8 @@
<p class="text-sm text-gray-500 dark:text-gray-400">Press <kbd>ESC</kbd> or click outside to close</p> <p class="text-sm text-gray-500 dark:text-gray-400">Press <kbd>ESC</kbd> or click outside to close</p>
</div> </div>
</div> </div>
<button id="shortcuts-modal-close-btn" class="px-6 py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 font-semibold text-lg transition-colors">
<button id="shortcuts-modal-close-btn"
class="px-6 py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 font-semibold text-lg transition-colors">
Skip Skip
</button> </button>
</div> </div>
@ -673,7 +748,9 @@
<!-- Playback --> <!-- Playback -->
<div class="feature-card p-6 bg-gray-50 dark:bg-gray-800"> <div class="feature-card p-6 bg-gray-50 dark:bg-gray-800">
<h3 class="mb-4 text-lg font-bold text-blue-600 dark:text-blue-400 border-b pb-2 border-gray-200 dark:border-gray-700">Playback & Navigation</h3>
<h3
class="mb-4 text-lg font-bold text-blue-600 dark:text-blue-400 border-b pb-2 border-gray-200 dark:border-gray-700">
Playback & Navigation</h3>
<ul class="space-y-3 text-sm text-gray-700 dark:text-gray-300"> <ul class="space-y-3 text-sm text-gray-700 dark:text-gray-300">
<li class="flex justify-between items-center"><span>Play / Pause</span> <kbd>Space</kbd></li> <li class="flex justify-between items-center"><span>Play / Pause</span> <kbd>Space</kbd></li>
<li class="flex justify-between items-center"><span>Next Frame</span> <kbd></kbd></li> <li class="flex justify-between items-center"><span>Next Frame</span> <kbd></kbd></li>
@ -686,7 +763,9 @@
<!-- View & Toggles --> <!-- View & Toggles -->
<div class="feature-card p-6 bg-gray-50 dark:bg-gray-800"> <div class="feature-card p-6 bg-gray-50 dark:bg-gray-800">
<h3 class="mb-4 text-lg font-bold text-emerald-600 dark:text-emerald-400 border-b pb-2 border-gray-200 dark:border-gray-700">View & UI Toggles</h3>
<h3
class="mb-4 text-lg font-bold text-emerald-600 dark:text-emerald-400 border-b pb-2 border-gray-200 dark:border-gray-700">
View & UI Toggles</h3>
<ul class="space-y-3 text-sm text-gray-700 dark:text-gray-300"> <ul class="space-y-3 text-sm text-gray-700 dark:text-gray-300">
<li class="flex justify-between items-center"><span>Sidebar Menu</span> <kbd>M</kbd></li> <li class="flex justify-between items-center"><span>Sidebar Menu</span> <kbd>M</kbd></li>
<li class="flex justify-between items-center"><span>Data Explorer</span> <kbd>I</kbd></li> <li class="flex justify-between items-center"><span>Data Explorer</span> <kbd>I</kbd></li>
@ -699,7 +778,9 @@
<!-- Data Display --> <!-- Data Display -->
<div class="feature-card p-6 bg-gray-50 dark:bg-gray-800"> <div class="feature-card p-6 bg-gray-50 dark:bg-gray-800">
<h3 class="mb-4 text-lg font-bold text-pink-600 dark:text-pink-400 border-b pb-2 border-gray-200 dark:border-gray-700">Data Visualization</h3>
<h3
class="mb-4 text-lg font-bold text-pink-600 dark:text-pink-400 border-b pb-2 border-gray-200 dark:border-gray-700">
Data Visualization</h3>
<ul class="space-y-3 text-sm text-gray-700 dark:text-gray-300"> <ul class="space-y-3 text-sm text-gray-700 dark:text-gray-300">
<li class="flex justify-between items-center"><span>Toggle Tracks</span> <kbd>T</kbd></li> <li class="flex justify-between items-center"><span>Toggle Tracks</span> <kbd>T</kbd></li>
<li class="flex justify-between items-center"><span>Object Details</span> <kbd>D</kbd></li> <li class="flex justify-between items-center"><span>Object Details</span> <kbd>D</kbd></li>
@ -715,7 +796,8 @@
</div> </div>
<!-- Footer Tip --> <!-- Footer Tip -->
<div class="p-6 bg-gray-50 dark:bg-gray-800 text-center border-t border-gray-200 dark:border-gray-700 rounded-b-2xl">
<div
class="p-6 bg-gray-50 dark:bg-gray-800 text-center border-t border-gray-200 dark:border-gray-700 rounded-b-2xl">
<p class="text-sm text-gray-500">Pro Tip: You can also press <kbd>K</kbd> anytime to toggle this screen.</p> <p class="text-sm text-gray-500">Pro Tip: You can also press <kbd>K</kbd> anytime to toggle this screen.</p>
</div> </div>
@ -723,11 +805,13 @@
</div> </div>
<!-- USER GUIDE MODAL --> <!-- USER GUIDE MODAL -->
<div id="guide-modal" class="fixed inset-0 z-50 flex items-center justify-center hidden bg-black bg-opacity-80 backdrop-blur-sm p-4">
<div id="guide-modal"
class="fixed inset-0 z-50 flex items-center justify-center hidden bg-black bg-opacity-80 backdrop-blur-sm p-4">
<div class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl w-full h-full mx-8 my-4 relative flex flex-col"> <div class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl w-full h-full mx-8 my-4 relative flex flex-col">
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between px-6 py-1 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-900 z-10 rounded-t-2xl">
<div
class="flex items-center justify-between px-6 py-1 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-900 z-10 rounded-t-2xl">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="text-3xl">📚</span> <span class="text-3xl">📚</span>
<div> <div>
@ -739,16 +823,19 @@
</div> </div>
<!-- Iframe Content --> <!-- Iframe Content -->
<iframe id="user-manual-iframe" src="annex/User_Manual.html?v=3.3.0" class="flex-grow w-full border-0 rounded-b-2xl" style="min-height: 400px;"></iframe>
<iframe id="user-manual-iframe" src="annex/User_Manual.html?v=3.3.0"
class="flex-grow w-full border-0 rounded-b-2xl" style="min-height: 400px;"></iframe>
</div> </div>
</div> </div>
<!-- CODEBASE OVERVIEW MODAL --> <!-- CODEBASE OVERVIEW MODAL -->
<div id="codebase-modal" class="fixed inset-0 z-50 flex items-center justify-center hidden bg-black bg-opacity-80 backdrop-blur-sm p-4">
<div id="codebase-modal"
class="fixed inset-0 z-50 flex items-center justify-center hidden bg-black bg-opacity-80 backdrop-blur-sm p-4">
<div class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl w-full h-full mx-8 my-4 relative flex flex-col"> <div class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl w-full h-full mx-8 my-4 relative flex flex-col">
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between px-6 py-1 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-900 z-10 rounded-t-2xl">
<div
class="flex items-center justify-between px-6 py-1 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-900 z-10 rounded-t-2xl">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="text-3xl"></span> <span class="text-3xl"></span>
<div> <div>
@ -760,16 +847,19 @@
</div> </div>
<!-- Iframe Content --> <!-- Iframe Content -->
<iframe id="codebase-iframe" src="annex/code-base-overview.html?v=3.3.0" class="flex-grow w-full border-0 rounded-b-2xl" style="min-height: 400px;"></iframe>
<iframe id="codebase-iframe" src="annex/code-base-overview.html?v=3.3.0"
class="flex-grow w-full border-0 rounded-b-2xl" style="min-height: 400px;"></iframe>
</div> </div>
</div> </div>
<!-- CHANGELOG MODAL --> <!-- CHANGELOG MODAL -->
<div id="changelog-modal" class="fixed inset-0 z-50 flex items-center justify-center hidden bg-black bg-opacity-80 backdrop-blur-sm p-4">
<div id="changelog-modal"
class="fixed inset-0 z-50 flex items-center justify-center hidden bg-black bg-opacity-80 backdrop-blur-sm p-4">
<div class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl w-full h-full mx-8 my-4 relative flex flex-col"> <div class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl w-full h-full mx-8 my-4 relative flex flex-col">
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between px-6 py-1 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-900 z-10 rounded-t-2xl">
<div
class="flex items-center justify-between px-6 py-1 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-900 z-10 rounded-t-2xl">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="text-3xl">🚀</span> <span class="text-3xl">🚀</span>
<div> <div>
@ -781,7 +871,8 @@
</div> </div>
<!-- Iframe Content --> <!-- Iframe Content -->
<iframe id="changelog-iframe" src="annex/Changelog_3.3.0.html" class="flex-grow w-full border-0 rounded-b-2xl" style="min-height: 400px;"></iframe>
<iframe id="changelog-iframe" src="annex/Changelog_3.3.0.html" class="flex-grow w-full border-0 rounded-b-2xl"
style="min-height: 400px;"></iframe>
</div> </div>
</div> </div>
<div id="modal-container" class="fixed inset-0 z-50 flex items-center justify-center hidden"> <div id="modal-container" class="fixed inset-0 z-50 flex items-center justify-center hidden">
@ -808,10 +899,13 @@
<input type="file" id="session-file-input" class="hidden" accept=".json" /> <input type="file" id="session-file-input" class="hidden" accept=".json" />
<!-- Global drag-and-drop overlay --> <!-- Global drag-and-drop overlay -->
<div id="global-drag-overlay" class="fixed inset-0 z-[60] flex items-center justify-center bg-blue-600/30 backdrop-blur-sm border-8 border-dashed border-blue-500 rounded-3xl m-4 pointer-events-none opacity-0 transition-opacity duration-300">
<div class="bg-blue-600 text-white px-10 py-5 rounded-2xl shadow-2xl flex items-center gap-6 border-4 border-blue-400">
<div id="global-drag-overlay"
class="fixed inset-0 z-[60] flex items-center justify-center bg-blue-600/30 backdrop-blur-sm border-8 border-dashed border-blue-500 rounded-3xl m-4 pointer-events-none opacity-0 transition-opacity duration-300">
<div
class="bg-blue-600 text-white px-10 py-5 rounded-2xl shadow-2xl flex items-center gap-6 border-4 border-blue-400">
<svg class="h-16 w-16 animate-bounce" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg class="h-16 w-16 animate-bounce" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg> </svg>
<div class="text-left"> <div class="text-left">
<span class="text-4xl font-extrabold block">Drop to Load Data</span> <span class="text-4xl font-extrabold block">Drop to Load Data</span>

94
steps/src/dataExplorer.js

@ -2,6 +2,7 @@
import { appState } from './state.js'; import { appState } from './state.js';
import { throttle } from './utils.js'; import { throttle } from './utils.js';
import { makeDraggableAndResizable } from './ui.js';
import { import {
canvasContainer, canvasContainer,
explorerBtn, explorerBtn,
@ -268,97 +269,6 @@ export function throttledUpdateExplorer() {
} }
// --- END: New Robust Update Logic --- // --- END: New Robust Update Logic ---
// --- START: Resizable and Draggable Panel Logic ---
function makePanelInteractive(panel) {
const header = document.getElementById('data-explorer-header');
const resizers = panel.querySelectorAll('.resizer');
const minWidth = 400;
const minHeight = 300;
let original_width = 0;
let original_height = 0;
let original_x = 0;
let original_y = 0;
let original_mouse_x = 0;
let original_mouse_y = 0;
// --- Dragging Logic ---
header.addEventListener('mousedown', (e) => {
e.preventDefault();
original_x = panel.offsetLeft;
original_y = panel.offsetTop;
original_mouse_x = e.pageX;
original_mouse_y = e.pageY;
document.body.classList.add('dragging');
window.addEventListener('mousemove', dragPanel);
window.addEventListener('mouseup', stopDrag);
});
function dragPanel(e) {
const dx = e.pageX - original_mouse_x;
const dy = e.pageY - original_mouse_y;
panel.style.left = `${original_x + dx}px`;
panel.style.top = `${original_y + dy}px`;
}
function stopDrag() {
document.body.classList.remove('dragging');
window.removeEventListener('mousemove', dragPanel);
window.removeEventListener('mouseup', stopDrag);
}
// --- Resizing Logic ---
resizers.forEach(resizer => {
resizer.addEventListener('mousedown', (e) => {
e.preventDefault();
original_width = parseFloat(getComputedStyle(panel, null).getPropertyValue('width').replace('px', ''));
original_height = parseFloat(getComputedStyle(panel, null).getPropertyValue('height').replace('px', ''));
original_x = panel.getBoundingClientRect().left;
original_y = panel.getBoundingClientRect().top;
original_mouse_x = e.pageX;
original_mouse_y = e.pageY;
const resizeFunc = (event) => resizePanel(event, resizer.classList);
document.body.classList.add('resizing');
window.addEventListener('mousemove', resizeFunc);
window.addEventListener('mouseup', () => {
document.body.classList.remove('resizing');
window.removeEventListener('mousemove', resizeFunc);
});
});
});
function resizePanel(e, direction) {
// --- START: Fix for Resizing Logic ---
// The logic is updated to handle corners correctly by checking for 't', 'b', 'l', 'r' substrings.
// This allows a corner like 'resizer-br' to trigger both bottom and right resizing logic.
if (direction.toString().includes('r')) { // Check for right edge
const width = original_width + (e.pageX - original_mouse_x);
if (width > minWidth) panel.style.width = `${width}px`;
}
if (direction.toString().includes('b')) { // Check for bottom edge
const height = original_height + (e.pageY - original_mouse_y);
if (height > minHeight) panel.style.height = `${height}px`;
}
if (direction.toString().includes('l')) { // Check for left edge
const newWidth = original_width - (e.pageX - original_mouse_x);
if (newWidth > minWidth) {
panel.style.width = `${newWidth}px`;
panel.style.left = `${original_x + (e.pageX - original_mouse_x)}px`;
}
}
if (direction.toString().includes('t')) { // Check for top edge
const newHeight = original_height - (e.pageY - original_mouse_y);
if (newHeight > minHeight) {
panel.style.height = `${newHeight}px`;
panel.style.top = `${original_y + (e.pageY - original_mouse_y)}px`;
}
}
// --- END: Fix for Resizing Logic ---
}
}
function initializePanelPosition(panel) { function initializePanelPosition(panel) {
// Remove Tailwind classes that conflict with dynamic positioning/sizing // Remove Tailwind classes that conflict with dynamic positioning/sizing
panel.classList.remove('bottom-24', 'right-4', 'w-full', 'max-w-2xl', 'h-1/2'); panel.classList.remove('bottom-24', 'right-4', 'w-full', 'max-w-2xl', 'h-1/2');
@ -388,7 +298,7 @@ export function initializeDataExplorer() {
// --- START: Make panel interactive --- // --- START: Make panel interactive ---
initializePanelPosition(panel); initializePanelPosition(panel);
makePanelInteractive(panel);
makeDraggableAndResizable(panel, document.getElementById('data-explorer-header'));
// --- END: Make panel interactive --- // --- END: Make panel interactive ---
// --- Wire up all event listeners --- // --- Wire up all event listeners ---

82
steps/src/p5/radarSketch.js

@ -409,6 +409,12 @@ export const radarSketch = function (p) {
// --- END: Frame-Rate Independent Smoothing --- // --- END: Frame-Rate Independent Smoothing ---
// Use the smoothed coordinates for all subsequent zoom-related calculations. // Use the smoothed coordinates for all subsequent zoom-related calculations.
// Store current transformed mouse coordinates (un-scaled back by original zoom factor)
// because zoomedMouseX is transformed by zoom and translation.
// Easiest is to check actual raw cursor coords on the canvas.
appState.isMouseOutOfBounds = p.mouseX < 0 || p.mouseX > p.width || p.mouseY < 0 || p.mouseY > p.height;
const hoveredItems = handleCloseUpDisplay(p, plotScales, smoothedMouseX, smoothedMouseY); const hoveredItems = handleCloseUpDisplay(p, plotScales, smoothedMouseX, smoothedMouseY);
// --- END: Mouse Smoothing Logic --- // --- END: Mouse Smoothing Logic ---
@ -451,18 +457,21 @@ export const radarSketch = function (p) {
p.ellipse(smoothedMouseX, smoothedMouseY, hoverRadius * 2, hoverRadius * 2); // Use smoothed values p.ellipse(smoothedMouseX, smoothedMouseY, hoverRadius * 2, hoverRadius * 2); // Use smoothed values
p.pop(); p.pop();
// --- END: Draw Zoom Area Rectangle & Debug Circle --- // --- END: Draw Zoom Area Rectangle & Debug Circle ---
if (hoveredItems.length > 0) { if (hoveredItems.length > 0) {
// If we are hovering, cancel any existing countdown.
if (appState.zoomHideDelayTimeout) {
clearTimeout(appState.zoomHideDelayTimeout); clearTimeout(appState.zoomHideDelayTimeout);
appState.zoomHideDelayTimeout = null; appState.zoomHideDelayTimeout = null;
}
if (appState.zoomCountdownInterval) {
clearInterval(appState.zoomCountdownInterval); clearInterval(appState.zoomCountdownInterval);
appState.zoomCountdownInterval = null; appState.zoomCountdownInterval = null;
}
appState.zoomCountdown = null; appState.zoomCountdown = null;
if (zoomPanel.style.display !== "block") {
zoomPanel.style.display = "block";
if (zoomPanel && zoomPanel.classList.contains("hidden") && !appState.zoomPanelExplicitlyClosed) {
zoomPanel.classList.remove("hidden");
} }
if ( if (
appState.zoomSketchInstance && appState.zoomSketchInstance &&
appState.zoomSketchInstance.updateAndDraw appState.zoomSketchInstance.updateAndDraw
@ -474,11 +483,30 @@ export const radarSketch = function (p) {
plotScales plotScales
); );
} }
} else if (zoomPanel.style.display === "block") {
} else {
// --- START: FIX for Grace Period Freeze --- // --- START: FIX for Grace Period Freeze ---
// If NOT hovering, but the panel is still visible, we must continue
// to update the zoom sketch so it follows the mouse.
// If NOT hovering, we must continue to update the zoom sketch
// so it follows the mouse.
// We pass an empty array for hoveredItems, so no tooltip is drawn. // We pass an empty array for hoveredItems, so no tooltip is drawn.
// Auto-hide the panel after 5 seconds of inactivity with countdown
if (zoomPanel && !zoomPanel.classList.contains("hidden")) {
if (!appState.zoomHideDelayTimeout && !appState.zoomCountdownInterval) {
appState.zoomHideDelayTimeout = setTimeout(() => {
appState.zoomHideDelayTimeout = null;
appState.zoomCountdown = 3;
appState.zoomCountdownInterval = setInterval(() => {
appState.zoomCountdown--;
if (appState.zoomCountdown <= 0) {
clearInterval(appState.zoomCountdownInterval);
appState.zoomCountdownInterval = null;
appState.zoomCountdown = null;
zoomPanel.classList.add("hidden");
}
}, 1000);
}, 5000);
}
}
if ( if (
appState.zoomSketchInstance && appState.zoomSketchInstance &&
appState.zoomSketchInstance.updateAndDraw appState.zoomSketchInstance.updateAndDraw
@ -491,40 +519,11 @@ export const radarSketch = function (p) {
); );
} }
// --- END: FIX for Grace Period Freeze --- // --- END: FIX for Grace Period Freeze ---
// 2. If a "hide" timer isn't already running, start one.
if (!appState.zoomHideDelayTimeout && !appState.zoomCountdownInterval) {
// Start a 2-second delay before the countdown begins.
appState.zoomHideDelayTimeout = setTimeout(() => {
appState.zoomHideDelayTimeout = null; // Clear the delay timer ID
// Now, start the actual 3-second countdown interval.
appState.zoomCountdown = Math.floor(COOLING_PERIOD_MS / 1000);
appState.zoomCountdownInterval = setInterval(() => {
appState.zoomCountdown--;
if (appState.zoomCountdown <= 0) {
// When countdown finishes, hide panel and clear interval.
clearInterval(appState.zoomCountdownInterval);
appState.zoomCountdownInterval = null;
appState.zoomCountdown = null;
zoomPanel.style.display = "none";
} else {
// Force a redraw of the zoom sketch to show the new countdown value.
// This call is still needed inside the interval to update the countdown text.
if (appState.zoomSketchInstance && appState.zoomSketchInstance.updateAndDraw) {
// Pass empty hoveredItems to show the countdown text.
appState.zoomSketchInstance.updateAndDraw(
smoothedMouseX,
smoothedMouseY,
[],
plotScales);
}
}
}, 1000);
}, 1000); // 1000ms = 1 second delay
}
} }
} else { } else {
// --- START: Cleanup Logic ---
// When zoom mode is turned off, ensure all timers are cleared.
p.cursor(p.AUTO); // Reset cursor when exiting god mode
// Clear timers when explicitly turned off
if (appState.zoomHideDelayTimeout) { if (appState.zoomHideDelayTimeout) {
clearTimeout(appState.zoomHideDelayTimeout); clearTimeout(appState.zoomHideDelayTimeout);
appState.zoomHideDelayTimeout = null; appState.zoomHideDelayTimeout = null;
@ -533,9 +532,8 @@ export const radarSketch = function (p) {
clearInterval(appState.zoomCountdownInterval); clearInterval(appState.zoomCountdownInterval);
appState.zoomCountdownInterval = null; appState.zoomCountdownInterval = null;
} }
// --- END: Cleanup Logic ---
p.cursor(p.AUTO); // Reset cursor when exiting god mode
zoomPanel.style.display = "none";
appState.zoomCountdown = null;
isFirstFrame = true; // Reset for the next time zoom mode is enabled isFirstFrame = true; // Reset for the next time zoom mode is enabled
} }
// --- Legend Drawing --- // --- Legend Drawing ---

28
steps/src/p5/zoomSketch.js

@ -454,8 +454,34 @@ export const zoomSketch = function (p) {
p.pop(); p.pop();
// --- END: DRAW TITLE OVERLAY --- // --- END: DRAW TITLE OVERLAY ---
// --- START: Draw Countdown Overlay ---
// --- START: Draw Out of Bounds Overlay ---
if (appState.isMouseOutOfBounds && appState.isCloseUpMode) {
p.push();
// Semi-transparent black background
p.fill(0, 0, 0, 150);
p.noStroke();
p.rectMode(p.CENTER);
p.rect(p.width / 2, p.height / 2, p.width, p.height);
// Draw the text
p.fill(255, 100, 100); // Red warning color
p.textAlign(p.CENTER, p.CENTER);
p.textSize(18);
p.textStyle(p.BOLD);
const yOffsetOffset = (appState.zoomCountdown !== null && appState.zoomCountdown > 0) ? 20 : 0;
p.text("Mouse pointer Out of Bounds", p.width / 2, p.height / 2 - yOffsetOffset);
if (appState.zoomCountdown !== null && appState.zoomCountdown > 0) { if (appState.zoomCountdown !== null && appState.zoomCountdown > 0) {
p.fill(255);
p.textStyle(p.NORMAL);
p.text(`Closing in ${appState.zoomCountdown}...`, p.width / 2, p.height / 2 + 20);
}
p.pop();
}
// --- END: Draw Out of Bounds Overlay ---
// --- START: Draw Countdown Overlay ---
else if (appState.zoomCountdown !== null && appState.zoomCountdown > 0) {
p.push(); p.push();
// Semi-transparent black background for readability // Semi-transparent black background for readability
p.fill(0, 0, 0, 150); p.fill(0, 0, 0, 150);

3
steps/src/state.js

@ -13,6 +13,9 @@ export const appState = {
isRawOnlyMode: false, // <-- ADD THIS LINE isRawOnlyMode: false, // <-- ADD THIS LINE
videoReadyByFallback: false, // True if video resolved via loadedmetadata timeout videoReadyByFallback: false, // True if video resolved via loadedmetadata timeout
videoMissing: false, // True if user opts to continue without a video videoMissing: false, // True if user opts to continue without a video
isMouseOutOfBounds: false, // True when the mouse is outside the radar sketch canvas
zoomPanelExplicitlyClosed: false, // True if user clicked X on the zoom panel during zoom mode
gridStackInstance: null, // Holds reference to the main GridStack instance
// Stores the parsed visualization data (radar frames, tracks, etc.) // Stores the parsed visualization data (radar frames, tracks, etc.)

127
steps/src/ui.js

@ -50,6 +50,102 @@ import {
startChangelogBtn, startChangelogBtn,
} from "./dom.js"; } from "./dom.js";
// --- START: Resizable and Draggable Panel Logic ---
export function makeDraggableAndResizable(panel, header, minWidth = 400, minHeight = 300) {
if (!panel || !header) return;
const resizers = panel.querySelectorAll('.resizer');
let original_width = 0;
let original_height = 0;
let original_x = 0;
let original_y = 0;
let original_mouse_x = 0;
let original_mouse_y = 0;
// --- Dragging Logic ---
header.addEventListener('mousedown', (e) => {
// Prevent drag if clicking buttons
if (e.target.tagName === 'BUTTON' || e.target.closest('button')) return;
e.preventDefault();
// Ensure panel floats on top
panel.style.zIndex = 100;
original_x = panel.offsetLeft;
original_y = panel.offsetTop;
original_mouse_x = e.pageX;
original_mouse_y = e.pageY;
document.body.classList.add('dragging');
window.addEventListener('mousemove', dragPanel);
window.addEventListener('mouseup', stopDrag);
});
function dragPanel(e) {
const dx = e.pageX - original_mouse_x;
const dy = e.pageY - original_mouse_y;
panel.style.left = `${original_x + dx}px`;
panel.style.top = `${original_y + dy}px`;
}
function stopDrag() {
document.body.classList.remove('dragging');
window.removeEventListener('mousemove', dragPanel);
window.removeEventListener('mouseup', stopDrag);
}
// --- Resizing Logic ---
resizers.forEach(resizer => {
resizer.addEventListener('mousedown', (e) => {
e.preventDefault();
panel.style.zIndex = 100;
original_width = parseFloat(getComputedStyle(panel, null).getPropertyValue('width').replace('px', ''));
original_height = parseFloat(getComputedStyle(panel, null).getPropertyValue('height').replace('px', ''));
original_x = panel.getBoundingClientRect().left;
original_y = panel.getBoundingClientRect().top;
original_mouse_x = e.pageX;
original_mouse_y = e.pageY;
const resizeFunc = (event) => resizePanel(event, resizer.classList);
document.body.classList.add('resizing');
window.addEventListener('mousemove', resizeFunc);
window.addEventListener('mouseup', () => {
document.body.classList.remove('resizing');
window.removeEventListener('mousemove', resizeFunc);
if (panel.id === 'zoom-panel' && appState.zoomSketchInstance) {
appState.zoomSketchInstance.handleResize();
}
});
});
});
function resizePanel(e, direction) {
if (direction.toString().includes('r')) {
const width = original_width + (e.pageX - original_mouse_x);
if (width > minWidth) panel.style.width = `${width}px`;
}
if (direction.toString().includes('b')) {
const height = original_height + (e.pageY - original_mouse_y);
if (height > minHeight) panel.style.height = `${height}px`;
}
if (direction.toString().includes('l')) {
const newWidth = original_width - (e.pageX - original_mouse_x);
if (newWidth > minWidth) {
panel.style.width = `${newWidth}px`;
panel.style.left = `${original_x + (e.pageX - original_mouse_x)}px`;
}
}
if (direction.toString().includes('t')) {
const newHeight = original_height - (e.pageY - original_mouse_y);
if (newHeight > minHeight) {
panel.style.height = `${newHeight}px`;
panel.style.top = `${original_y + (e.pageY - original_mouse_y)}px`;
}
}
}
}
// --- END: Resizable and Draggable Panel Logic ---
function toggleMenu(show) { function toggleMenu(show) {
if (show) { if (show) {
collapsibleMenu.classList.remove("-translate-x-full"); collapsibleMenu.classList.remove("-translate-x-full");
@ -121,15 +217,31 @@ function handleColorToggles(e) {
export function initUIEventListeners() { export function initUIEventListeners() {
// --- Initialize GridStack --- // --- Initialize GridStack ---
if (typeof GridStack !== 'undefined') { if (typeof GridStack !== 'undefined') {
GridStack.init({
appState.gridStackInstance = GridStack.init({
margin: 10, margin: 10,
cellHeight: '6vh', // Radar is 12h=72vh, Video is 8h=48vh, Graph is 4h=24vh
cellHeight: '6vh',
disableOneColumnMode: true, disableOneColumnMode: true,
animate: true, animate: true,
handle: '.grid-stack-item-content > .cursor-grab', // use the specific drag handle we added
handle: '.grid-stack-item-content > .cursor-grab',
}); });
} }
// --- Initialize Floating Zoom Panel ---
const zoomPanel = document.getElementById("zoom-panel");
const zoomHeader = document.getElementById("zoom-panel-header");
const closeZoomBtn = document.getElementById("close-zoom-btn");
if (zoomPanel && zoomHeader) {
makeDraggableAndResizable(zoomPanel, zoomHeader, 300, 200);
if (closeZoomBtn) {
closeZoomBtn.addEventListener("click", () => {
zoomPanel.classList.add("hidden");
appState.zoomPanelExplicitlyClosed = true;
});
}
}
// --- Shortcuts Modal --- // --- Shortcuts Modal ---
shortcutsBtn.addEventListener("click", (e) => { shortcutsBtn.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
@ -285,6 +397,15 @@ export function initUIEventListeners() {
toggleCloseUp.addEventListener("change", () => { toggleCloseUp.addEventListener("change", () => {
appState.isCloseUpMode = toggleCloseUp.checked; appState.isCloseUpMode = toggleCloseUp.checked;
appState.zoomPanelExplicitlyClosed = false; // Reset the close flag so it can reappear
// Auto-hide the panel when the user disables Close-Up mode (e.g. by pressing 'g')
if (!appState.isCloseUpMode) {
const zoomPanel = document.getElementById("zoom-panel");
if (zoomPanel && !zoomPanel.classList.contains("hidden")) {
zoomPanel.classList.add("hidden");
}
}
if (appState.isCloseUpMode && appState.isPlaying) { if (appState.isCloseUpMode && appState.isPlaying) {
// If entering close-up mode while playing, automatically pause. // If entering close-up mode while playing, automatically pause.
pausePlayback(); pausePlayback();

Loading…
Cancel
Save