Raj Bhalerao commited on
Commit
bb34c97
·
1 Parent(s): c08a20c

Release: Dark mode lights out..

Browse files
backend/server.py CHANGED
@@ -3,6 +3,7 @@ import json
3
  import uuid
4
  import asyncio
5
  import tempfile
 
6
  from pathlib import Path
7
 
8
  import cv2
@@ -45,10 +46,20 @@ def index():
45
  async def upload(file: UploadFile = File(...)):
46
  video_id = str(uuid.uuid4())[:8]
47
  path = UPLOAD_DIR / f"{video_id}.mp4"
48
- with open(path, "wb") as f:
49
- f.write(await file.read())
50
- videos[video_id] = str(path)
51
- return {"video_id": video_id}
 
 
 
 
 
 
 
 
 
 
52
 
53
 
54
  @app.get("/config/{video_id}")
 
3
  import uuid
4
  import asyncio
5
  import tempfile
6
+ import shutil
7
  from pathlib import Path
8
 
9
  import cv2
 
46
  async def upload(file: UploadFile = File(...)):
47
  video_id = str(uuid.uuid4())[:8]
48
  path = UPLOAD_DIR / f"{video_id}.mp4"
49
+
50
+ print(f"[BACKEND] Received upload request: {file.filename}")
51
+ try:
52
+ with open(path, "wb") as f:
53
+ shutil.copyfileobj(file.file, f)
54
+
55
+ file_size = os.path.getsize(path)
56
+ print(f"[BACKEND] Successfully stored: {path} ({file_size} bytes)")
57
+
58
+ videos[video_id] = str(path)
59
+ return {"video_id": video_id}
60
+ except Exception as e:
61
+ print(f"[BACKEND] Upload failed: {str(e)}")
62
+ return Response(content=str(e), status_code=500)
63
 
64
 
65
  @app.get("/config/{video_id}")
frontend/initial.html CHANGED
@@ -11,18 +11,11 @@
11
  <link
12
  href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700;800;900&display=swap"
13
  rel="stylesheet">
14
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
15
  <style>
16
  body {
17
  font-family: 'Montserrat', sans-serif;
18
- }
19
-
20
- .font-montserrat {
21
- font-family: 'Montserrat', sans-serif;
22
- }
23
-
24
- .mono-font {
25
- font-family: 'JetBrains Mono', monospace;
26
  }
27
 
28
  .fade-in {
@@ -41,153 +34,140 @@
41
  }
42
  }
43
 
44
- .bg-glow {
45
- position: absolute;
46
- width: 600px;
47
- height: 600px;
48
- background: radial-gradient(circle, rgba(241, 245, 249, 1) 0%, rgba(255, 255, 255, 0) 70%);
49
- top: 50%;
50
- left: 0;
51
- transform: translateY(-50%);
52
- z-index: -1;
53
- pointer-events: none;
 
 
 
 
 
 
 
 
54
  }
55
  </style>
56
  </head>
57
 
58
  <body
59
- class="bg-white text-slate-900 min-h-screen w-full overflow-x-hidden flex flex-col items-center selection:bg-black selection:text-white relative z-0">
60
-
61
- <div class="bg-glow"></div>
62
 
63
- <header class="mt-8 flex flex-col items-center flex-shrink-0 w-full z-10">
64
- <img src="uf_logo.png" alt="UrbanFlow Logo" class="h-44 md:h-52 w-auto object-contain mb-3">
65
  </header>
66
 
67
  <main
68
  class="flex-1 w-full max-w-[90rem] mx-auto grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-20 px-10 py-6 items-center z-10">
69
 
70
  <div class="lg:col-span-7 flex flex-col justify-center xl:pl-10 pb-10 lg:pb-0">
71
- <h1
72
- class="text-5xl xl:text-[4.5rem] font-montserrat font-extrabold mb-4 leading-[1.1] text-slate-900 tracking-tight">
73
  Automated <br>Vision Intelligence
74
  </h1>
75
- <p
76
- class="font-montserrat font-bold mb-8 text-sm uppercase tracking-[0.2em] text-slate-400 flex items-center">
77
- <span class="bg-slate-100 text-slate-600 px-3 py-1 rounded-full text-[10px] mr-3">v1.0 CORE</span>
78
- Powered by High-Density Vision Metrics
79
  </p>
80
- <ul class="space-y-4 xl:space-y-5 text-base xl:text-lg font-montserrat font-medium text-slate-700">
81
- <li class="flex items-center"><i class="fa-solid fa-check text-black mr-5 text-xl"></i> Real-time
82
  spatial detection and tracking</li>
83
- <li class="flex items-center"><i class="fa-solid fa-check text-black mr-5 text-xl"></i> Multi-class
84
  object categorization</li>
85
- <li class="flex items-center"><i class="fa-solid fa-check text-black mr-5 text-xl"></i> Bidirectional
86
  movement analysis</li>
87
- <li class="flex items-center"><i class="fa-solid fa-check text-black mr-5 text-xl"></i> High-performance
88
  multi-object tracking</li>
89
- <li class="flex items-center"><i class="fa-solid fa-check text-black mr-5 text-xl"></i> Intelligent
90
  processing optimization</li>
91
- <li class="flex items-center"><i class="fa-solid fa-check text-black mr-5 text-xl"></i> Comprehensive
92
- analytics reporting</li>
93
  </ul>
94
  </div>
95
 
96
  <div
97
  class="lg:col-span-5 flex flex-col justify-center w-full max-w-[32rem] mx-auto min-h-[450px] mb-12 lg:mb-0">
98
 
99
- <!-- STEP: Module Select -->
100
- <div id="step-modules" class="w-full flex flex-col fade-in justify-center items-center">
101
- <h2 class="text-3xl font-montserrat font-bold mb-2 text-slate-900 text-center">UrbanFlow
102
- </h2>
103
- <p class="text-[13px] font-montserrat font-medium text-slate-400 mb-10 text-center">Active Analytical
104
- Environment: Traffic Dynamics</p>
105
 
106
  <div class="flex justify-center w-full">
107
  <div onclick="showStep('upload')"
108
- class="group relative bg-white border-2 border-slate-900 rounded-[2rem] p-8 cursor-pointer hover:shadow-2xl hover:-translate-y-1 transition-all duration-300 text-center max-w-sm w-full">
109
  <div
110
- class="absolute top-4 right-6 bg-slate-900 text-white text-[9px] font-bold px-2.5 py-1 rounded-full uppercase tracking-wider">
111
  Active</div>
112
- <i class="fa-solid fa-car-side text-4xl text-slate-900 mb-4 block mx-auto"></i>
113
- <h3 class="font-montserrat font-bold text-lg mb-2 leading-tight">Traffic <br>Dynamics</h3>
114
- <p class="text-[10px] text-slate-500 font-montserrat font-medium leading-relaxed">Detect, track,
115
- and analyze
116
- vehicles in
117
- real-world environments using state-of-the-art vision models.</p>
118
  </div>
119
  </div>
120
  </div>
121
 
122
  <!-- STEP: Upload -->
123
- <div id="step-upload" class="hidden w-full flex flex-col fade-in justify-center">
124
  <button onclick="showStep('modules')"
125
- class="text-slate-400 hover:text-black transition flex items-center text-xs font-bold uppercase tracking-widest mb-6 w-fit">
126
  <i class="fa-solid fa-arrow-left mr-2"></i> Back to Suite
127
  </button>
128
- <h2 class="text-3xl font-montserrat font-bold mb-2 text-slate-900 text-center">Define Media Source
129
- </h2>
130
- <p class="text-[13px] font-montserrat font-medium text-slate-400 mb-8 text-center">Provide the target
131
- video footage to
132
- configure the
133
- analytical pipeline.</p>
134
-
135
- <div id="dropzone"
136
- class="border border-dashed border-slate-300 rounded-[2rem] p-12 flex flex-col items-center justify-center cursor-pointer hover:border-black hover:bg-slate-50 transition-all duration-300 group">
137
  <i
138
- class="fa-solid fa-arrow-up-from-bracket text-4xl text-slate-300 group-hover:text-black transition mb-5"></i>
139
- <span class="font-montserrat font-semibold text-slate-800 text-lg mb-2 text-center">Select or drop
140
- media file to proceed</span>
141
- <span class="text-[10px] font-bold uppercase tracking-widest text-slate-400 text-center">Accepted
142
- formats: MP4, AVI, MOV</span>
143
- <input id="file-input" type="file" accept="video/*" class="hidden">
144
  </div>
145
 
146
  <div id="upload-progress-container" class="hidden mt-10 w-full">
147
- <div
148
- class="flex justify-between text-[11px] font-montserrat font-bold uppercase tracking-widest mb-3 text-slate-900">
149
  <span id="upload-text">Uploading...</span>
150
  <span id="upload-percentage">0%</span>
151
  </div>
152
- <div class="w-full h-1 bg-slate-100 rounded-full overflow-hidden">
153
  <div id="upload-bar"
154
- class="h-full bg-black w-0 transition-all duration-75 ease-linear rounded-full"></div>
155
  </div>
156
  </div>
157
  </div>
158
 
159
-
160
-
161
- <!-- STEP: Draw Line -->
162
- <div id="step-draw" class="hidden w-full flex flex-col fade-in justify-center">
163
- <h2 class="text-3xl font-montserrat font-bold mb-2 text-slate-900 text-center">Define Boundary</h2>
164
- <p
165
- class="text-[11px] font-montserrat font-bold uppercase tracking-widest text-slate-400 mb-6 text-center">
166
- Click two
167
- points to establish counting threshold</p>
168
 
169
  <div
170
- class="relative w-full aspect-video bg-slate-900 rounded-3xl overflow-hidden cursor-crosshair mb-6 shadow-inner">
171
  <img id="frame-img" class="absolute inset-0 w-full h-full object-contain" style="display:none;">
172
  <div id="frame-placeholder"
173
- class="absolute inset-0 flex flex-col items-center justify-center text-slate-500 pointer-events-none select-none">
174
  <i class="fa-solid fa-video text-4xl mb-3 opacity-30"></i>
175
- <span
176
- class="font-montserrat font-semibold text-[10px] uppercase tracking-widest opacity-50">Media
177
- Frame Preview</span>
178
  </div>
179
  <canvas id="drawing-canvas" class="absolute inset-0 w-full h-full"></canvas>
180
  </div>
181
 
182
  <div class="flex space-x-3">
183
  <button onclick="resetCanvas()"
184
- class="w-1/3 py-3.5 bg-white border border-slate-200 text-slate-800 rounded-full font-montserrat font-semibold hover:border-black hover:text-black transition-all text-sm xl:text-base">
185
- Reset
186
- </button>
187
  <button id="btn-proceed" onclick="startRun()"
188
- class="w-2/3 py-3.5 bg-black text-white rounded-full font-montserrat font-medium hover:bg-slate-800 transition-all text-center text-sm xl:text-base">
189
- Continue &nbsp;&rarr;
190
- </button>
191
  </div>
192
  </div>
193
 
@@ -200,34 +180,47 @@
200
 
201
  function showStep(name) {
202
  ['modules', 'upload', 'draw'].forEach(s => {
203
- document.getElementById('step-' + s).classList.add('hidden');
 
204
  });
205
- document.getElementById('step-' + name).classList.remove('hidden');
 
206
 
 
 
 
 
207
  if (name === 'draw') loadFirstFrame();
208
  }
209
 
210
- // Upload
211
  const dropzone = document.getElementById('dropzone');
212
  const fileInput = document.getElementById('file-input');
213
 
214
- dropzone.addEventListener('click', () => fileInput.click());
215
- dropzone.addEventListener('dragover', e => { e.preventDefault(); dropzone.classList.add('border-black', 'bg-slate-50'); });
216
- dropzone.addEventListener('dragleave', () => dropzone.classList.remove('border-black', 'bg-slate-50'));
217
- dropzone.addEventListener('drop', e => {
218
- e.preventDefault();
219
- dropzone.classList.remove('border-black', 'bg-slate-50');
220
- if (e.dataTransfer.files.length) uploadFile(e.dataTransfer.files[0]);
221
- });
222
- fileInput.addEventListener('change', () => { if (fileInput.files.length) uploadFile(fileInput.files[0]); });
 
 
 
 
 
 
223
 
224
  function uploadFile(file) {
225
- dropzone.classList.add('hidden');
226
  const prog = document.getElementById('upload-progress-container');
227
  const bar = document.getElementById('upload-bar');
228
  const pct = document.getElementById('upload-percentage');
229
  const txt = document.getElementById('upload-text');
230
- prog.classList.remove('hidden');
 
 
231
 
232
  const form = new FormData();
233
  form.append('file', file);
@@ -243,7 +236,19 @@
243
  }
244
  };
245
 
 
 
 
 
 
 
246
  xhr.onload = () => {
 
 
 
 
 
 
247
  const res = JSON.parse(xhr.responseText);
248
  videoId = res.video_id;
249
  txt.innerText = 'Extracting Metadata...';
@@ -257,16 +262,19 @@
257
  runConfig.conf = 0.12;
258
  runConfig.iou = 0.60;
259
  txt.innerText = 'Initialization Complete';
260
- setTimeout(() => showStep('draw'), 400);
 
 
 
 
 
 
261
  });
262
  };
263
-
264
  xhr.send(form);
265
  }
266
 
267
-
268
-
269
- // Draw line
270
  const canvas = document.getElementById('drawing-canvas');
271
  const ctx = canvas.getContext('2d');
272
  let points = [];
@@ -285,32 +293,27 @@
285
  }
286
 
287
  function initCanvas() {
288
- canvas.width = canvas.offsetWidth;
289
- canvas.height = canvas.offsetHeight;
 
 
290
  }
291
 
292
- window.addEventListener('resize', () => {
293
- if (!document.getElementById('step-draw').classList.contains('hidden')) {
294
- initCanvas();
295
- points.forEach(p => drawDot(p.cx, p.cy));
 
 
 
 
 
 
 
 
296
  if (points.length === 2) drawLine();
297
- }
298
- });
299
-
300
- canvas.addEventListener('mousedown', e => {
301
- if (points.length >= 2) return;
302
-
303
- const rect = canvas.getBoundingClientRect();
304
- const cx = e.clientX - rect.left;
305
- const cy = e.clientY - rect.top;
306
-
307
- const rx = cx / canvas.width * imgNatW;
308
- const ry = cy / canvas.height * imgNatH;
309
-
310
- points.push({ cx, cy, rx: Math.round(rx), ry: Math.round(ry) });
311
- drawDot(cx, cy);
312
- if (points.length === 2) drawLine();
313
- });
314
 
315
  function drawDot(x, y) {
316
  ctx.beginPath();
@@ -338,15 +341,12 @@
338
 
339
  function startRun() {
340
  if (points.length < 2) return;
341
-
342
  const line = [[points[0].rx, points[0].ry], [points[1].rx, points[1].ry]];
343
-
344
  sessionStorage.setItem('funky_run', JSON.stringify({
345
  video_id: videoId,
346
  line: line,
347
  config: runConfig
348
  }));
349
-
350
  window.location.href = 'vehicles.html';
351
  }
352
  </script>
 
11
  <link
12
  href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700;800;900&display=swap"
13
  rel="stylesheet">
 
14
  <style>
15
  body {
16
  font-family: 'Montserrat', sans-serif;
17
+ background-color: #000000;
18
+ color: #ffffff;
 
 
 
 
 
 
19
  }
20
 
21
  .fade-in {
 
34
  }
35
  }
36
 
37
+ /* Executive Overrides */
38
+ .traffic-dynamics-card {
39
+ background-color: #000000 !important;
40
+ border: 2px solid #ffffff !important;
41
+ }
42
+
43
+ #dropzone {
44
+ transition: all 0.2s ease;
45
+ }
46
+
47
+ #dropzone:hover {
48
+ border-color: #ffffff !important;
49
+ background-color: #0a0a0a !important;
50
+ }
51
+
52
+ .core-badge {
53
+ background-color: #888888 !important;
54
+ color: #000000 !important;
55
  }
56
  </style>
57
  </head>
58
 
59
  <body
60
+ class="bg-black text-white min-h-screen w-full flex flex-col items-center selection:bg-white selection:text-black">
 
 
61
 
62
+ <header class="mt-16 flex flex-col items-center flex-shrink-0 w-full z-10">
63
+ <img src="uf_logo_b.png" alt="UrbanFlow Logo" class="h-44 md:h-52 w-auto object-contain mb-3">
64
  </header>
65
 
66
  <main
67
  class="flex-1 w-full max-w-[90rem] mx-auto grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-20 px-10 py-6 items-center z-10">
68
 
69
  <div class="lg:col-span-7 flex flex-col justify-center xl:pl-10 pb-10 lg:pb-0">
70
+ <h1 class="text-5xl xl:text-[4.5rem] font-extrabold mb-4 leading-[1.1] text-white tracking-tight">
 
71
  Automated <br>Vision Intelligence
72
  </h1>
73
+ <p class="font-bold mb-8 text-sm uppercase tracking-[0.2em] text-neutral-500 flex items-center">
74
+ <span class="core-badge px-3 py-1 rounded-full text-[10px] mr-3">v1.0 CORE</span>
75
+ Powered by Deep Learning Inference
 
76
  </p>
77
+ <ul class="space-y-4 xl:space-y-5 text-base xl:text-lg font-medium text-neutral-400">
78
+ <li class="flex items-center"><i class="fa-solid fa-check text-white mr-5 text-xl"></i> Real-time
79
  spatial detection and tracking</li>
80
+ <li class="flex items-center"><i class="fa-solid fa-check text-white mr-5 text-xl"></i> Multi-class
81
  object categorization</li>
82
+ <li class="flex items-center"><i class="fa-solid fa-check text-white mr-5 text-xl"></i> Bidirectional
83
  movement analysis</li>
84
+ <li class="flex items-center"><i class="fa-solid fa-check text-white mr-5 text-xl"></i> High-performance
85
  multi-object tracking</li>
86
+ <li class="flex items-center"><i class="fa-solid fa-check text-white mr-5 text-xl"></i> Intelligent
87
  processing optimization</li>
 
 
88
  </ul>
89
  </div>
90
 
91
  <div
92
  class="lg:col-span-5 flex flex-col justify-center w-full max-w-[32rem] mx-auto min-h-[450px] mb-12 lg:mb-0">
93
 
94
+ <!-- STEP: Modules -->
95
+ <div id="step-modules" class="w-full flex flex-col fade-in">
96
+ <h2 class="text-3xl font-bold mb-2 text-white text-center">UrbanFlow</h2>
97
+ <p class="text-[13px] font-medium text-neutral-500 mb-8 text-center">Select an analytical pipeline to
98
+ proceed.</p>
 
99
 
100
  <div class="flex justify-center w-full">
101
  <div onclick="showStep('upload')"
102
+ class="group relative border-2 border-white rounded-[2rem] p-8 cursor-pointer hover:-translate-y-1 transition-all duration-300 text-center max-w-sm w-full traffic-dynamics-card">
103
  <div
104
+ class="absolute top-4 right-6 bg-white text-black text-[9px] font-bold px-2.5 py-1 rounded-full uppercase tracking-wider">
105
  Active</div>
106
+ <i class="fa-solid fa-car-side text-4xl text-white mb-4 block mx-auto"></i>
107
+ <h3 class="font-bold text-lg mb-2 leading-tight text-white">Traffic <br>Dynamics</h3>
108
+ <p class="text-[10px] text-neutral-500 font-medium leading-relaxed">Detect, track, and analyze
109
+ vehicles in real-world environments.</p>
 
 
110
  </div>
111
  </div>
112
  </div>
113
 
114
  <!-- STEP: Upload -->
115
+ <div id="step-upload" class="hidden w-full flex flex-col fade-in">
116
  <button onclick="showStep('modules')"
117
+ class="text-neutral-500 hover:text-white transition flex items-center text-xs font-bold uppercase tracking-widest mb-6 w-fit">
118
  <i class="fa-solid fa-arrow-left mr-2"></i> Back to Suite
119
  </button>
120
+ <h2 class="text-3xl font-bold mb-2 text-white text-center">Define Media Source</h2>
121
+ <p class="text-[13px] font-medium text-neutral-500 mb-8 text-center">Provide the target video footage to
122
+ initialize the pipeline.</p>
123
+
124
+ <input id="file-input" type="file" accept="video/*" class="hidden">
125
+ <div id="dropzone" onclick="document.getElementById('file-input').click()"
126
+ class="border border-dashed border-neutral-700 rounded-[2rem] p-12 flex flex-col items-center justify-center cursor-pointer transition-all duration-300 group">
 
 
127
  <i
128
+ class="fa-solid fa-arrow-up-from-bracket text-4xl text-neutral-700 group-hover:text-white transition mb-5"></i>
129
+ <span class="font-semibold text-neutral-300 text-lg mb-2 text-center">Select or drop media
130
+ file</span>
131
+ <span class="text-[10px] font-bold uppercase tracking-widest text-neutral-600 text-center">MP4, AVI,
132
+ MOV Accepted</span>
 
133
  </div>
134
 
135
  <div id="upload-progress-container" class="hidden mt-10 w-full">
136
+ <div class="flex justify-between text-[11px] font-bold uppercase tracking-widest mb-3 text-white">
 
137
  <span id="upload-text">Uploading...</span>
138
  <span id="upload-percentage">0%</span>
139
  </div>
140
+ <div class="w-full h-1 bg-neutral-900 rounded-full overflow-hidden">
141
  <div id="upload-bar"
142
+ class="h-full bg-[#666666] w-0 transition-all duration-75 ease-linear rounded-full"></div>
143
  </div>
144
  </div>
145
  </div>
146
 
147
+ <!-- STEP: Draw -->
148
+ <div id="step-draw" class="hidden w-full flex flex-col fade-in">
149
+ <h2 class="text-3xl font-bold mb-2 text-white text-center">Define Boundary</h2>
150
+ <p class="text-[11px] font-bold uppercase tracking-widest text-neutral-500 mb-6 text-center">Establish
151
+ counting threshold points</p>
 
 
 
 
152
 
153
  <div
154
+ class="relative w-full aspect-video bg-neutral-950 rounded-3xl overflow-hidden cursor-crosshair mb-6">
155
  <img id="frame-img" class="absolute inset-0 w-full h-full object-contain" style="display:none;">
156
  <div id="frame-placeholder"
157
+ class="absolute inset-0 flex flex-col items-center justify-center text-neutral-800 pointer-events-none">
158
  <i class="fa-solid fa-video text-4xl mb-3 opacity-30"></i>
159
+ <span class="font-bold text-[10px] uppercase tracking-widest opacity-50">Media Frame
160
+ Preview</span>
 
161
  </div>
162
  <canvas id="drawing-canvas" class="absolute inset-0 w-full h-full"></canvas>
163
  </div>
164
 
165
  <div class="flex space-x-3">
166
  <button onclick="resetCanvas()"
167
+ class="w-1/3 py-3.5 bg-neutral-900 border border-neutral-800 text-white rounded-full font-bold hover:bg-neutral-800 transition-all text-sm">Reset</button>
 
 
168
  <button id="btn-proceed" onclick="startRun()"
169
+ class="w-2/3 py-3.5 bg-[#666666] text-black rounded-full font-bold transition-all text-center text-sm">Continue
170
+ &nbsp;&rarr;</button>
 
171
  </div>
172
  </div>
173
 
 
180
 
181
  function showStep(name) {
182
  ['modules', 'upload', 'draw'].forEach(s => {
183
+ const el = document.getElementById('step-' + s);
184
+ if (el) el.classList.add('hidden');
185
  });
186
+ const target = document.getElementById('step-' + name);
187
+ if (target) target.classList.remove('hidden');
188
 
189
+ if (name === 'upload') {
190
+ document.getElementById('upload-progress-container').classList.add('hidden');
191
+ document.getElementById('dropzone').classList.remove('hidden');
192
+ }
193
  if (name === 'draw') loadFirstFrame();
194
  }
195
 
 
196
  const dropzone = document.getElementById('dropzone');
197
  const fileInput = document.getElementById('file-input');
198
 
199
+ if (fileInput) {
200
+ fileInput.addEventListener('change', () => {
201
+ if (fileInput.files.length) uploadFile(fileInput.files[0]);
202
+ });
203
+ }
204
+
205
+ if (dropzone) {
206
+ dropzone.addEventListener('dragover', e => { e.preventDefault(); dropzone.classList.add('border-white', 'bg-neutral-950'); });
207
+ dropzone.addEventListener('dragleave', () => dropzone.classList.remove('border-white', 'bg-neutral-950'));
208
+ dropzone.addEventListener('drop', e => {
209
+ e.preventDefault();
210
+ dropzone.classList.remove('border-white', 'bg-neutral-950');
211
+ if (e.dataTransfer.files.length) uploadFile(e.dataTransfer.files[0]);
212
+ });
213
+ }
214
 
215
  function uploadFile(file) {
216
+ const dropzoneEl = document.getElementById('dropzone');
217
  const prog = document.getElementById('upload-progress-container');
218
  const bar = document.getElementById('upload-bar');
219
  const pct = document.getElementById('upload-percentage');
220
  const txt = document.getElementById('upload-text');
221
+
222
+ if (dropzoneEl) dropzoneEl.classList.add('hidden');
223
+ if (prog) prog.classList.remove('hidden');
224
 
225
  const form = new FormData();
226
  form.append('file', file);
 
236
  }
237
  };
238
 
239
+ xhr.onerror = () => {
240
+ txt.innerText = 'Error: Network failure';
241
+ txt.classList.add('text-red-500');
242
+ fileInput.value = '';
243
+ };
244
+
245
  xhr.onload = () => {
246
+ if (xhr.status !== 200) {
247
+ txt.innerText = 'Error: ' + xhr.status;
248
+ txt.classList.add('text-red-500');
249
+ fileInput.value = '';
250
+ return;
251
+ }
252
  const res = JSON.parse(xhr.responseText);
253
  videoId = res.video_id;
254
  txt.innerText = 'Extracting Metadata...';
 
262
  runConfig.conf = 0.12;
263
  runConfig.iou = 0.60;
264
  txt.innerText = 'Initialization Complete';
265
+ fileInput.value = '';
266
+ setTimeout(() => showStep('draw'), 800);
267
+ })
268
+ .catch(e => {
269
+ txt.innerText = 'Metadata Failed';
270
+ txt.classList.add('text-red-500');
271
+ fileInput.value = '';
272
  });
273
  };
 
274
  xhr.send(form);
275
  }
276
 
277
+ // Draw Canvas Logic
 
 
278
  const canvas = document.getElementById('drawing-canvas');
279
  const ctx = canvas.getContext('2d');
280
  let points = [];
 
293
  }
294
 
295
  function initCanvas() {
296
+ if (canvas) {
297
+ canvas.width = canvas.offsetWidth;
298
+ canvas.height = canvas.offsetHeight;
299
+ }
300
  }
301
 
302
+ window.addEventListener('resize', initCanvas);
303
+
304
+ if (canvas) {
305
+ canvas.addEventListener('mousedown', e => {
306
+ if (points.length >= 2) return;
307
+ const rect = canvas.getBoundingClientRect();
308
+ const cx = e.clientX - rect.left;
309
+ const cy = e.clientY - rect.top;
310
+ const rx = (cx / canvas.width) * imgNatW;
311
+ const ry = (cy / canvas.height) * imgNatH;
312
+ points.push({ cx, cy, rx: Math.round(rx), ry: Math.round(ry) });
313
+ drawDot(cx, cy);
314
  if (points.length === 2) drawLine();
315
+ });
316
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
 
318
  function drawDot(x, y) {
319
  ctx.beginPath();
 
341
 
342
  function startRun() {
343
  if (points.length < 2) return;
 
344
  const line = [[points[0].rx, points[0].ry], [points[1].rx, points[1].ry]];
 
345
  sessionStorage.setItem('funky_run', JSON.stringify({
346
  video_id: videoId,
347
  line: line,
348
  config: runConfig
349
  }));
 
350
  window.location.href = 'vehicles.html';
351
  }
352
  </script>
frontend/{uf_logo.png → uf_logo_b.png} RENAMED
File without changes
frontend/uf_logo_w.png ADDED

Git LFS Details

  • SHA256: 61bde29d9fccc2bba0c1de61ce36c630062558453d540a395a22c1aa5e51dcc1
  • Pointer size: 132 Bytes
  • Size of remote file: 4.09 MB
frontend/vehicles.html CHANGED
@@ -14,8 +14,22 @@
14
  rel="stylesheet">
15
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
16
  <style>
 
 
 
 
 
 
 
 
 
 
 
17
  body {
18
  font-family: 'Montserrat', sans-serif;
 
 
 
19
  }
20
 
21
  .mono-font {
@@ -23,22 +37,21 @@
23
  }
24
 
25
  ::-webkit-scrollbar {
26
- width: 5px;
27
- height: 5px;
28
  }
29
 
30
  ::-webkit-scrollbar-track {
31
- background: #f1f5f9;
32
- border-radius: 4px;
33
  }
34
 
35
  ::-webkit-scrollbar-thumb {
36
- background: #cbd5e1;
37
  border-radius: 4px;
38
  }
39
 
40
  ::-webkit-scrollbar-thumb:hover {
41
- background: #94a3b8;
42
  }
43
 
44
  .info-wrap {
@@ -52,195 +65,238 @@
52
  display: inline-flex;
53
  align-items: center;
54
  justify-content: center;
55
- width: 15px;
56
- height: 15px;
57
  border-radius: 50%;
58
- background: #94a3b8;
59
- color: #fff;
60
- font-size: 8px;
61
- cursor: default;
62
- flex-shrink: 0;
63
- transition: background 0.15s;
64
  }
65
 
66
- .info-wrap:hover .info-btn {
67
- background: #64748b;
68
  }
69
 
70
  .info-tip {
71
  display: none;
72
  position: fixed;
73
  z-index: 9999;
74
- background: #0f172a;
75
- color: #e2e8f0;
76
- font-size: 11px;
77
- font-weight: 400;
78
  line-height: 1.4;
79
  padding: 8px 12px;
80
- border-radius: 8px;
81
- max-width: 280px;
82
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
83
- white-space: normal;
84
  pointer-events: none;
85
  }
86
 
87
- .info-wrap:hover .info-tip {
88
- display: block;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  }
90
 
91
- /* Settings controls */
92
  .toggle-track {
93
- width: 36px;
94
- height: 20px;
95
- border-radius: 10px;
96
- background: #cbd5e1;
97
  position: relative;
98
  cursor: pointer;
99
- transition: background 0.2s;
100
- flex-shrink: 0;
101
  }
102
 
103
  .toggle-track.active {
104
- background: #0f172a;
105
  }
106
 
107
  .toggle-thumb {
108
- width: 16px;
109
- height: 16px;
110
  border-radius: 50%;
111
- background: #fff;
112
  position: absolute;
113
  top: 2px;
114
  left: 2px;
115
- transition: transform 0.2s;
116
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
117
  }
118
 
119
  .toggle-track.active .toggle-thumb {
120
- transform: translateX(16px);
121
- }
122
-
123
- .toggle-track.locked {
124
- opacity: 0.4;
125
- cursor: not-allowed;
126
- pointer-events: none;
127
  }
128
 
129
  .custom-select {
130
  appearance: none;
131
- background: #f8fafc;
132
- border: 1px solid #e2e8f0;
133
- border-radius: 8px;
134
- padding: 6px 32px 6px 12px;
135
- font-size: 12px;
136
  font-weight: 600;
137
- color: #1e293b;
138
- cursor: pointer;
139
  outline: none;
140
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%2364748b'%3E%3Cpath fill-rule='evenodd' d='M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z'/%3E%3C/svg%3E");
141
  background-repeat: no-repeat;
142
- background-position: right 10px center;
143
- background-size: 14px;
144
- transition: border-color 0.15s;
145
- }
146
-
147
- .custom-select:hover {
148
- border-color: #94a3b8;
149
- }
150
-
151
- .custom-select:disabled {
152
- opacity: 0.4;
153
- cursor: not-allowed;
154
  }
155
 
156
  .s-stepper {
157
  display: inline-flex;
158
- align-items: center;
159
- border: 1px solid #e2e8f0;
160
- border-radius: 10px;
161
- background: #f8fafc;
162
  overflow: hidden;
163
  }
164
 
165
  .s-stepper button {
166
- background: none;
167
- border: none;
168
- padding: 4px 10px;
169
- cursor: pointer;
170
- color: #64748b;
171
- font-size: 14px;
172
- line-height: 1;
173
  }
174
 
175
  .s-stepper button:hover {
176
- background: #e2e8f0;
177
- }
178
-
179
- .s-stepper button:disabled {
180
- opacity: 0.25;
181
- cursor: not-allowed;
182
  }
183
 
184
  .s-stepper .s-val {
185
- min-width: 48px;
186
  text-align: center;
187
  font-family: 'JetBrains Mono', monospace;
188
- font-size: 13px;
189
  font-weight: 700;
190
- color: #0f172a;
191
  padding: 4px 0;
 
 
192
  }
193
 
194
  .s-row {
195
  display: flex;
196
  align-items: center;
197
  justify-content: space-between;
198
- padding: 10px 0;
199
- border-bottom: 1px solid #f1f5f9;
200
  }
201
 
202
  .s-row:last-child {
203
  border-bottom: none;
204
  }
205
 
 
 
 
 
 
 
 
 
206
  .s-row.disabled {
207
- opacity: 0.45;
208
- pointer-events: none;
 
 
 
 
 
209
  }
210
  </style>
211
  </head>
212
 
213
- <body class="bg-[#F3F4F8] text-slate-900 h-screen w-screen overflow-hidden flex">
214
 
215
  <!-- Sidebar -->
216
  <aside class="w-60 bg-white shadow-xl flex flex-col z-20 flex-shrink-0 border-r border-slate-200 relative">
217
- <div class="h-28 bg-white flex items-center justify-center px-4 my-2 border-b border-slate-100 flex-shrink-0">
218
- <img src="uf_logo.png" alt="UrbanFlow Logo" class="h-24 w-auto object-contain">
219
  </div>
220
  <nav class="flex-1 px-4 py-4 space-y-1.5 overflow-y-auto text-sm">
221
  <a onclick="switchTab('about')" id="nav-about"
222
- class="flex items-center px-4 py-2.5 text-slate-600 hover:bg-slate-50 hover:text-slate-900 rounded-lg transition cursor-pointer">
223
  <i class="fa-solid fa-circle-info w-6"></i> <span class="font-medium">About</span>
224
  </a>
225
  <a onclick="switchTab('overview')" id="nav-overview"
226
- class="flex items-center px-4 py-2.5 text-slate-600 hover:bg-slate-50 hover:text-slate-900 rounded-lg transition cursor-pointer">
227
  <i class="fa-solid fa-desktop w-6"></i> <span class="font-medium">Overview</span>
228
  </a>
229
  <a onclick="switchTab('run-details')" id="nav-run-details"
230
- class="flex items-center px-4 py-2.5 text-slate-600 hover:bg-slate-50 hover:text-slate-900 rounded-lg transition cursor-pointer">
231
  <i class="fa-solid fa-microchip w-6"></i> <span class="font-medium">Run</span>
232
  </a>
233
  <a onclick="switchTab('reports')" id="nav-reports"
234
- class="flex items-center px-4 py-2.5 text-slate-600 hover:bg-slate-50 hover:text-slate-900 rounded-lg transition cursor-pointer">
235
  <i class="fa-solid fa-file-lines w-6"></i> <span class="font-medium">Reports</span>
236
  </a>
237
  <a onclick="switchTab('settings')" id="nav-settings"
238
- class="flex items-center px-4 py-2.5 text-slate-600 hover:bg-slate-50 hover:text-slate-900 rounded-lg transition cursor-pointer">
239
  <i class="fa-solid fa-sliders w-6"></i> <span class="font-medium">Settings</span>
240
  </a>
241
  </nav>
242
- <div class="mt-auto border-t border-slate-100 p-4 flex items-center justify-center bg-white flex-shrink-0">
243
- <img src="uf_logo.png" alt="UrbanFlow Bottom Logo" class="h-20 w-auto object-contain opacity-80">
 
244
  </div>
245
  </aside>
246
 
@@ -260,7 +316,7 @@
260
  </p>
261
  </div>
262
 
263
- <div class="space-y-6 text-slate-600 leading-relaxed">
264
  <p>
265
  UrbanFlow is an advanced vision intelligence suite designed for real-time traffic dynamics
266
  analysis.
@@ -273,40 +329,50 @@
273
  into actionable intelligence, UrbanFlow enables data-driven decision making for safer,
274
  more efficient urban mobility.
275
  </p>
276
- <div class="grid grid-cols-2 gap-8 pt-6 border-t border-slate-50">
277
  <div class="space-y-4">
278
- <h4 class="font-bold text-slate-800 text-sm uppercase tracking-wider">Core Capabilities</h4>
279
- <ul class="text-xs space-y-2">
280
- <li class="flex items-center gap-2"><i
281
- class="fa-solid fa-circle text-[6px] text-slate-400"></i> Real-time Spatial
282
- Detection</li>
283
- <li class="flex items-center gap-2"><i
284
- class="fa-solid fa-circle text-[6px] text-slate-400"></i> Multi-class Object
285
- Tracking</li>
286
- <li class="flex items-center gap-2"><i
287
- class="fa-solid fa-circle text-[6px] text-slate-400"></i> Bidirectional Flow
288
- Analysis</li>
289
- <li class="flex items-center gap-2"><i
290
- class="fa-solid fa-circle text-[6px] text-slate-400"></i> High-Density Metrics
291
- Capture</li>
 
 
 
 
 
292
  </ul>
293
  </div>
294
  <div class="space-y-4">
295
- <h4 class="font-bold text-slate-800 text-sm uppercase tracking-wider">Intelligence Framework
 
296
  </h4>
297
- <ul class="text-xs space-y-2">
298
- <li class="flex items-center gap-2"><i
299
- class="fa-solid fa-circle text-[6px] text-slate-400"></i> OpenVINO Performance
300
- Optimization</li>
301
- <li class="flex items-center gap-2"><i
302
- class="fa-solid fa-circle text-[6px] text-slate-400"></i> ByteTrack Temporal
303
- Assignment</li>
304
- <li class="flex items-center gap-2"><i
305
- class="fa-solid fa-circle text-[6px] text-slate-400"></i> Sub-millimeter
306
- Coordinate Logic</li>
307
- <li class="flex items-center gap-2"><i
308
- class="fa-solid fa-circle text-[6px] text-slate-400"></i> Enterprise MIS
309
- Compliance</li>
 
 
 
 
310
  </ul>
311
  </div>
312
  </div>
@@ -319,14 +385,14 @@
319
  <div
320
  class="bg-white rounded-xl px-6 py-4 border border-slate-200 shadow-sm flex items-center justify-between flex-shrink-0">
321
  <div class="flex items-center space-x-4 flex-1 mr-6">
322
- <span class="text-[11px] font-black text-slate-900 uppercase tracking-wider whitespace-nowrap"
323
  id="proc-label">Waiting</span>
324
- <div class="flex-1 h-2 bg-slate-200 rounded-full overflow-hidden relative">
325
- <div id="proc-bar" class="h-full bg-slate-900 rounded-full transition-all duration-500 ease-out"
326
  style="width: 0%"></div>
327
  </div>
328
  </div>
329
- <div class="flex items-center space-x-6 text-xs font-bold text-slate-900 whitespace-nowrap">
330
  <span id="proc-frames">0 / 0 Frames</span>
331
  <span id="proc-pct">0%</span>
332
  </div>
@@ -576,6 +642,14 @@
576
  <option value="corporate">Corporate</option>
577
  </select>
578
  </div>
 
 
 
 
 
 
 
 
579
  <div class="mt-3">
580
  <div class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-2">Palette
581
  Preview</div>
@@ -587,8 +661,8 @@
587
  <!-- Start Button -->
588
  <div class="col-span-3 pb-4" id="settings-start-wrap">
589
  <button id="btn-start-processing" onclick="startProcessingFromSettings()"
590
- class="w-full py-4 bg-slate-900 text-white font-medium text-sm rounded-full hover:bg-slate-800 active:scale-[0.99] transition flex items-center justify-center gap-2 shadow-lg">
591
- <span>Process &nbsp;&rarr;</span>
592
  </button>
593
  </div>
594
 
@@ -600,15 +674,18 @@
600
  </main>
601
 
602
  <script>
 
603
  // =========== Tooltip ===========
604
- // Position tooltip near the icon using fixed positioning to avoid clipping
605
  document.addEventListener('mouseover', e => {
606
  const wrap = e.target.closest('.info-wrap');
607
  if (!wrap) return;
608
  const tip = wrap.querySelector('.info-tip');
609
  if (!tip) return;
 
 
610
  const rect = wrap.getBoundingClientRect();
611
- const tipH = 60;
612
  if (rect.bottom + tipH + 10 > window.innerHeight) {
613
  tip.style.top = (rect.top - tipH - 6) + 'px';
614
  } else {
@@ -617,6 +694,36 @@
617
  tip.style.left = Math.min(rect.left, window.innerWidth - 300) + 'px';
618
  });
619
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
620
  // =========== Tab switching ===========
621
  function switchTab(tab) {
622
  ['about', 'overview', 'run-details', 'reports', 'settings'].forEach(t => {
@@ -625,11 +732,11 @@
625
  if (el) el.classList.toggle('hidden', tab !== t);
626
  if (nav) {
627
  if (tab === t) {
628
- nav.classList.add('bg-slate-900', 'text-white', 'shadow-md');
629
- nav.classList.remove('text-slate-600', 'hover:bg-slate-50', 'hover:text-slate-900');
630
  } else {
631
- nav.classList.remove('bg-slate-900', 'text-white', 'shadow-md');
632
- nav.classList.add('text-slate-600', 'hover:bg-slate-50', 'hover:text-slate-900');
633
  }
634
  }
635
  });
@@ -638,20 +745,20 @@
638
  // =========== Run Details helpers ===========
639
  function detailRow(label, value, extra) {
640
  extra = extra || '';
641
- return `<div class="flex justify-between items-center border-b border-slate-50 pb-2">
642
  <span class="text-xs font-medium text-slate-500 mono-font">${label}</span>
643
- <span class="text-sm font-bold text-slate-800">${value}${extra}</span>
644
  </div>`;
645
  }
646
 
647
  function infoRow(label, value, tip, extra) {
648
  extra = extra || '';
649
- return `<div class="flex justify-between items-center border-b border-slate-50 pb-2 relative">
650
  <span class="text-xs font-medium text-slate-500 mono-font flex items-center">${label}
651
  <span class="info-wrap"><span class="info-btn"><i class="fa-solid fa-info"></i></span>
652
  <span class="info-tip">${tip}</span></span>
653
  </span>
654
- <span class="text-sm font-bold text-slate-800">${value}${extra}</span>
655
  </div>`;
656
  }
657
 
@@ -672,14 +779,14 @@
672
 
673
  const cpuPct = Math.min(100, Math.round((c.cpu_score / 10) * 100));
674
  document.getElementById('panel-perf').innerHTML =
675
- `<div class="flex justify-between items-center border-b border-slate-50 pb-2 relative">
676
  <span class="text-xs font-medium text-slate-500 mono-font flex items-center">cpu_score
677
  <span class="info-wrap"><span class="info-btn"><i class="fa-solid fa-info"></i></span>
678
  <span class="info-tip">Available CPU core count used for throughput estimation.</span></span>
679
  </span>
680
  <div class="flex items-center">
681
- <span class="text-sm font-bold text-slate-800 mr-2">${c.cpu_score}</span>
682
- <div class="w-16 h-1.5 bg-slate-100 rounded-full overflow-hidden">
683
  <div class="h-full bg-emerald-500" style="width:${cpuPct}%"></div>
684
  </div>
685
  </div>
@@ -688,9 +795,9 @@
688
  infoRow('effective_fps', c.effective_fps_est, 'Adjusted throughput accounting for frame stride.', ' <span class="text-xs text-slate-400 font-normal">fps</span>');
689
 
690
  document.getElementById('panel-model').innerHTML =
691
- `<div class="flex justify-between items-center border-b border-slate-50 pb-2">
692
  <span class="text-xs font-medium text-slate-500 mono-font">model</span>
693
- <a href="https://huggingface.co/Perception365/VehicleNet-Y26s" target="_blank" class="text-sm font-bold text-slate-800 mono-font hover:text-slate-600 transition underline underline-offset-4 decoration-slate-200">Perception365/VehicleNet-Y26s</a>
694
  </div>` +
695
  detailRow('task', 'detect') +
696
  detailRow('format', 'OpenVINO') +
@@ -720,7 +827,13 @@
720
 
721
  // =========== Charts ===========
722
  Chart.defaults.font.family = "'Montserrat', sans-serif";
723
- Chart.defaults.color = '#64748b';
 
 
 
 
 
 
724
 
725
  let MODEL_CLASSES = {};
726
  let BUSINESS_MAP = {};
@@ -742,8 +855,8 @@
742
  responsive: true, maintainAspectRatio: false,
743
  plugins: { legend: { display: false } },
744
  scales: {
745
- x: { grid: { display: false }, ticks: { maxTicksLimit: 10, font: { size: 10 } }, title: { display: true, text: 'Frame Index', font: { size: 11, weight: '600' }, color: '#475569' } },
746
- y: { grid: { color: '#e2e8f0' }, beginAtZero: true, ticks: { font: { size: 10 } }, title: { display: true, text: 'Active Vehicles', font: { size: 11, weight: '600' }, color: '#475569' } }
747
  },
748
  animation: { duration: 0 }
749
  }
@@ -755,7 +868,7 @@
755
  labels: ['Incoming', 'Outgoing'], datasets: [{
756
  data: [0, 0],
757
  backgroundColor: [activePalette.doughIn, activePalette.doughOut],
758
- borderColor: '#ffffff',
759
  borderWidth: 3,
760
  hoverOffset: 6
761
  }]
@@ -777,8 +890,8 @@
777
  responsive: true, maintainAspectRatio: false,
778
  plugins: { legend: { display: false } },
779
  scales: {
780
- x: { grid: { display: false }, ticks: { font: { size: 10, weight: '500' } } },
781
- y: { grid: { color: '#e2e8f0' }, beginAtZero: true, ticks: { font: { size: 10 } }, title: { display: true, text: 'Total Vehicle Count', font: { size: 11, weight: '600' }, color: '#475569' } }
782
  },
783
  animation: { duration: 0 }
784
  }
@@ -786,13 +899,13 @@
786
 
787
  const flowChart = new Chart(document.getElementById('flowChart').getContext('2d'), {
788
  type: 'bar',
789
- data: { labels: [], datasets: [{ data: [], backgroundColor: activePalette.flow, borderColor: '#ffffff', borderWidth: 1.5, barPercentage: 1.0, categoryPercentage: 1.0 }] },
790
  options: {
791
  responsive: true, maintainAspectRatio: false,
792
  plugins: { legend: { display: false } },
793
  scales: {
794
- x: { grid: { display: false }, ticks: { maxTicksLimit: 10, font: { size: 10 } }, title: { display: true, text: 'Time (seconds)', font: { size: 11, weight: '600' }, color: '#475569' } },
795
- y: { grid: { color: '#e2e8f0' }, beginAtZero: true, ticks: { font: { size: 10 } }, title: { display: true, text: 'Vehicles Crossed', font: { size: 11, weight: '600' }, color: '#475569' } }
796
  },
797
  animation: { duration: 0 }
798
  }
@@ -839,9 +952,9 @@
839
  const el = document.getElementById('live-palette-preview');
840
  if (el) {
841
  el.innerHTML = colors.map(c =>
842
- `<div class="flex-1 rounded-lg overflow-hidden border border-slate-100">
843
  <div class="h-6" style="background:${c.color}"></div>
844
- <div class="text-[8px] font-bold text-slate-400 text-center py-1 bg-slate-50">${c.label}</div>
845
  </div>`
846
  ).join('');
847
  }
@@ -915,13 +1028,13 @@
915
  const pct = totalAll > 0 ? ((total / totalAll) * 100).toFixed(1) : '0.0';
916
 
917
  const row = document.createElement('div');
918
- row.className = 'flex items-center justify-between text-xs py-2 border-b border-slate-50';
919
  row.innerHTML = `
920
- <div class="w-[30%] font-bold text-slate-800 truncate" title="${MODEL_CLASSES[id]}">${MODEL_CLASSES[id]}</div>
921
  <div class="w-[20%] text-slate-500 text-[11px]">${total} total</div>
922
  <div class="w-[15%] text-slate-500 text-[11px]"><i class="fa-solid fa-arrow-down text-[9px] mr-1"></i>${inC}</div>
923
  <div class="w-[15%] text-slate-500 text-[11px]"><i class="fa-solid fa-arrow-up text-[9px] mr-1"></i>${outC}</div>
924
- <div class="w-[20%] text-right font-bold text-slate-900">${pct}%</div>
925
  `;
926
  container.appendChild(row);
927
  });
@@ -1025,11 +1138,11 @@
1025
  const badge = document.getElementById('results-status-badge');
1026
  if (badge) {
1027
  badge.innerText = 'Processing';
1028
- badge.className = 'px-2.5 py-1 bg-blue-100 text-blue-700 text-[10px] font-bold rounded-full uppercase tracking-tighter animate-pulse';
1029
  }
1030
  document.getElementById('run-results-content').innerHTML = `
1031
- <div class="flex flex-col items-center justify-center p-8 bg-slate-50/50 border border-slate-100 rounded-2xl col-span-3 text-slate-400">
1032
- <i class="fa-solid fa-spinner fa-spin text-2xl mb-3"></i>
1033
  <span class="text-xs font-semibold">Executing inference pipeline... results pending</span>
1034
  </div>`;
1035
 
@@ -1049,6 +1162,19 @@
1049
  }));
1050
  };
1051
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1052
  let lastUIUpdate = 0;
1053
 
1054
  ws.onmessage = e => {
@@ -1063,7 +1189,7 @@
1063
  const badge = document.getElementById('results-status-badge');
1064
  if (badge) {
1065
  badge.innerText = 'Completed';
1066
- badge.className = 'px-2.5 py-1 bg-green-100 text-green-700 text-[10px] font-bold rounded-full uppercase tracking-tighter';
1067
  }
1068
 
1069
  document.getElementById('run-results-content').innerHTML =
@@ -1129,37 +1255,37 @@
1129
  const isVideo = name.endsWith('.mp4');
1130
  const isPDF = name.endsWith('.pdf');
1131
  const card = document.createElement('div');
1132
- card.className = 'bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden';
1133
 
1134
  let previewHTML = '';
1135
  if (isVideo) {
1136
  previewHTML = `
1137
- <div class="flex flex-col items-center justify-center py-12 text-slate-300">
1138
- <i class="fa-solid fa-film text-6xl mb-4"></i>
1139
- <span class="text-xs font-bold uppercase tracking-widest text-slate-400">Video Ready for Local Analysis</span>
1140
  </div>`;
1141
  } else if (isPDF) {
1142
  previewHTML = `
1143
- <div class="flex flex-col items-center justify-center py-12 text-slate-300">
1144
- <i class="fa-solid fa-file-pdf text-6xl mb-4"></i>
1145
- <span class="text-xs font-bold uppercase tracking-widest text-slate-400">PDF Document</span>
1146
  </div>`;
1147
  } else {
1148
  previewHTML = `<img src="${url}" alt="${info.title}" class="max-w-full max-h-[320px] object-contain rounded">`;
1149
  }
1150
 
1151
  card.innerHTML = `
1152
- <div class="px-5 py-3 border-b border-slate-100 bg-slate-50/50 flex justify-between items-center">
1153
  <div>
1154
- <h3 class="font-bold text-slate-800 text-sm">${info.title}</h3>
1155
  <p class="text-[10px] text-slate-400 mt-0.5">${info.desc}</p>
1156
  </div>
1157
  <a href="${url}" download="${name}"
1158
- class="inline-flex items-center gap-1.5 px-3 py-1.5 bg-slate-900 text-white text-[10px] font-bold rounded-lg hover:bg-slate-700 transition uppercase tracking-wider">
1159
  <i class="fa-solid fa-download text-[9px]"></i> Download
1160
  </a>
1161
  </div>
1162
- <div class="p-4 flex items-center justify-center bg-slate-50/30">
1163
  ${previewHTML}
1164
  </div>
1165
  `;
 
14
  rel="stylesheet">
15
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
16
  <style>
17
+ :root {
18
+ --bg-primary: #000000;
19
+ --bg-secondary: #000000;
20
+ --text-primary: #ffffff;
21
+ --text-secondary: #aaaaaa;
22
+ --border-color: #1a1a1a;
23
+ --sidebar-bg: #000000;
24
+ --card-bg: #0a0a0a;
25
+ --input-bg: #111111;
26
+ }
27
+
28
  body {
29
  font-family: 'Montserrat', sans-serif;
30
+ background-color: var(--bg-primary);
31
+ color: var(--text-primary);
32
+ transition: background-color 0.3s ease;
33
  }
34
 
35
  .mono-font {
 
37
  }
38
 
39
  ::-webkit-scrollbar {
40
+ width: 4px;
41
+ height: 4px;
42
  }
43
 
44
  ::-webkit-scrollbar-track {
45
+ background: #000000;
 
46
  }
47
 
48
  ::-webkit-scrollbar-thumb {
49
+ background: #222222;
50
  border-radius: 4px;
51
  }
52
 
53
  ::-webkit-scrollbar-thumb:hover {
54
+ background: #333333;
55
  }
56
 
57
  .info-wrap {
 
65
  display: inline-flex;
66
  align-items: center;
67
  justify-content: center;
68
+ width: 14px;
69
+ height: 14px;
70
  border-radius: 50%;
71
+ background: #444444 !important;
72
+ color: #ffffff !important;
73
+ font-size: 7px;
74
+ cursor: pointer;
75
+ transition: all 0.2s ease;
 
76
  }
77
 
78
+ .info-btn:hover {
79
+ background: #666666 !important;
80
  }
81
 
82
  .info-tip {
83
  display: none;
84
  position: fixed;
85
  z-index: 9999;
86
+ background: #0a0a0a;
87
+ color: #aaaaaa;
88
+ font-size: 10px;
89
+ font-weight: 500;
90
  line-height: 1.4;
91
  padding: 8px 12px;
92
+ border-radius: 6px;
93
+ max-width: 240px;
94
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.8);
95
+ border: 1px solid #222222;
96
  pointer-events: none;
97
  }
98
 
99
+ /* Nav states */
100
+ .nav-item-active {
101
+ background-color: #111111 !important;
102
+ color: #ffffff !important;
103
+ border-left: 2px solid #ffffff !important;
104
+ }
105
+
106
+ .nav-item-inactive {
107
+ color: #666666 !important;
108
+ }
109
+
110
+ .nav-item-inactive:hover {
111
+ color: #ffffff !important;
112
+ background-color: #050505 !important;
113
+ }
114
+
115
+ /* Card Overrides */
116
+ .bg-white {
117
+ background-color: var(--card-bg) !important;
118
+ }
119
+
120
+ /* Card Overrides */
121
+ .bg-white {
122
+ background-color: var(--card-bg) !important;
123
+ }
124
+
125
+ .border-slate-200,
126
+ .border-slate-100,
127
+ .border-slate-50,
128
+ .border-neutral-800,
129
+ .border-neutral-900 {
130
+ border-color: var(--border-color) !important;
131
+ }
132
+
133
+ .bg-slate-50\/50,
134
+ .bg-slate-50,
135
+ .bg-slate-900,
136
+ .bg-neutral-900 {
137
+ background-color: #0c0c0c !important;
138
+ }
139
+
140
+ .text-slate-900,
141
+ .text-slate-800,
142
+ .text-slate-700,
143
+ .text-neutral-900 {
144
+ color: #ffffff !important;
145
+ }
146
+
147
+ .text-slate-600,
148
+ .text-slate-500,
149
+ .text-slate-400,
150
+ .text-neutral-500,
151
+ .text-neutral-400 {
152
+ color: #888888 !important;
153
+ }
154
+
155
+ .shadow-sm {
156
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.8) !important;
157
  }
158
 
159
+ /* Controls */
160
  .toggle-track {
161
+ width: 32px;
162
+ height: 18px;
163
+ border-radius: 9px;
164
+ background: #222222;
165
  position: relative;
166
  cursor: pointer;
 
 
167
  }
168
 
169
  .toggle-track.active {
170
+ background: #ffffff;
171
  }
172
 
173
  .toggle-thumb {
174
+ width: 14px;
175
+ height: 14px;
176
  border-radius: 50%;
177
+ background: #888888;
178
  position: absolute;
179
  top: 2px;
180
  left: 2px;
181
+ transition: all 0.2s ease;
 
182
  }
183
 
184
  .toggle-track.active .toggle-thumb {
185
+ transform: translateX(14px);
186
+ background: #000000;
 
 
 
 
 
187
  }
188
 
189
  .custom-select {
190
  appearance: none;
191
+ background-color: #111111;
192
+ border: 1px solid #222222;
193
+ border-radius: 6px;
194
+ padding: 4px 24px 4px 10px;
195
+ font-size: 11px;
196
  font-weight: 600;
197
+ color: #ffffff;
 
198
  outline: none;
199
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%23666666'%3E%3Cpath fill-rule='evenodd' d='M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z'/%3E%3C/svg%3E");
200
  background-repeat: no-repeat;
201
+ background-position: right 8px center;
202
+ background-size: 12px;
 
 
 
 
 
 
 
 
 
 
203
  }
204
 
205
  .s-stepper {
206
  display: inline-flex;
207
+ border: 1px solid #222222;
208
+ border-radius: 6px;
209
+ background: #111111;
 
210
  overflow: hidden;
211
  }
212
 
213
  .s-stepper button {
214
+ padding: 4px 8px;
215
+ color: #666666;
216
+ font-size: 12px;
 
 
 
 
217
  }
218
 
219
  .s-stepper button:hover {
220
+ background: #1a1a1a;
221
+ color: #ffffff;
 
 
 
 
222
  }
223
 
224
  .s-stepper .s-val {
225
+ min-width: 40px;
226
  text-align: center;
227
  font-family: 'JetBrains Mono', monospace;
228
+ font-size: 12px;
229
  font-weight: 700;
230
+ color: #ffffff;
231
  padding: 4px 0;
232
+ border-left: 1px solid #222222;
233
+ border-right: 1px solid #222222;
234
  }
235
 
236
  .s-row {
237
  display: flex;
238
  align-items: center;
239
  justify-content: space-between;
240
+ padding: 12px 0;
241
+ border-bottom: 1px solid #1a1a1a;
242
  }
243
 
244
  .s-row:last-child {
245
  border-bottom: none;
246
  }
247
 
248
+ #proc-bar {
249
+ background-color: #666666 !important;
250
+ }
251
+
252
+ #proc-label {
253
+ color: #ffffff !important;
254
+ }
255
+
256
  .s-row.disabled {
257
+ opacity: 0.4 !important;
258
+ pointer-events: none !important;
259
+ filter: grayscale(1);
260
+ }
261
+
262
+ #btn-start-processing {
263
+ font-family: 'Montserrat', sans-serif !important;
264
  }
265
  </style>
266
  </head>
267
 
268
+ <body class="bg-black text-white h-screen w-screen overflow-hidden flex">
269
 
270
  <!-- Sidebar -->
271
  <aside class="w-60 bg-white shadow-xl flex flex-col z-20 flex-shrink-0 border-r border-slate-200 relative">
272
+ <div class="h-28 bg-black flex items-center justify-center px-4 my-2 border-b border-slate-800 flex-shrink-0">
273
+ <img id="sidebar-logo-top" src="uf_logo_b.png" alt="UrbanFlow Logo" class="h-24 w-auto object-contain">
274
  </div>
275
  <nav class="flex-1 px-4 py-4 space-y-1.5 overflow-y-auto text-sm">
276
  <a onclick="switchTab('about')" id="nav-about"
277
+ class="flex items-center px-4 py-2.5 rounded-lg transition cursor-pointer nav-item-inactive">
278
  <i class="fa-solid fa-circle-info w-6"></i> <span class="font-medium">About</span>
279
  </a>
280
  <a onclick="switchTab('overview')" id="nav-overview"
281
+ class="flex items-center px-4 py-2.5 rounded-lg transition cursor-pointer nav-item-inactive">
282
  <i class="fa-solid fa-desktop w-6"></i> <span class="font-medium">Overview</span>
283
  </a>
284
  <a onclick="switchTab('run-details')" id="nav-run-details"
285
+ class="flex items-center px-4 py-2.5 rounded-lg transition cursor-pointer nav-item-inactive">
286
  <i class="fa-solid fa-microchip w-6"></i> <span class="font-medium">Run</span>
287
  </a>
288
  <a onclick="switchTab('reports')" id="nav-reports"
289
+ class="flex items-center px-4 py-2.5 rounded-lg transition cursor-pointer nav-item-inactive">
290
  <i class="fa-solid fa-file-lines w-6"></i> <span class="font-medium">Reports</span>
291
  </a>
292
  <a onclick="switchTab('settings')" id="nav-settings"
293
+ class="flex items-center px-4 py-2.5 rounded-lg transition cursor-pointer nav-item-inactive">
294
  <i class="fa-solid fa-sliders w-6"></i> <span class="font-medium">Settings</span>
295
  </a>
296
  </nav>
297
+ <div class="mt-auto border-t border-slate-800 p-4 flex items-center justify-center bg-black flex-shrink-0">
298
+ <img id="sidebar-logo-bottom" src="uf_logo_b.png" alt="UrbanFlow Bottom Logo"
299
+ class="h-20 w-auto object-contain opacity-80">
300
  </div>
301
  </aside>
302
 
 
316
  </p>
317
  </div>
318
 
319
+ <div class="space-y-6 text-slate-600 leading-relaxed text-sm">
320
  <p>
321
  UrbanFlow is an advanced vision intelligence suite designed for real-time traffic dynamics
322
  analysis.
 
329
  into actionable intelligence, UrbanFlow enables data-driven decision making for safer,
330
  more efficient urban mobility.
331
  </p>
332
+ <div class="grid grid-cols-2 gap-12 pt-8 border-t border-slate-50">
333
  <div class="space-y-4">
334
+ <h4 class="font-bold text-slate-800 text-[13px] uppercase tracking-wider">Core Capabilities
335
+ </h4>
336
+ <ul class="text-xs space-y-3 pl-1">
337
+ <li class="flex items-start gap-3"><i
338
+ class="fa-solid fa-circle text-[5px] mt-1.5 text-slate-400"></i>
339
+ <span>Real-time Spatial Detection</span>
340
+ </li>
341
+ <li class="flex items-start gap-3"><i
342
+ class="fa-solid fa-circle text-[5px] mt-1.5 text-slate-400"></i>
343
+ <span>Multi-class Object Tracking</span>
344
+ </li>
345
+ <li class="flex items-start gap-3"><i
346
+ class="fa-solid fa-circle text-[5px] mt-1.5 text-slate-400"></i>
347
+ <span>Bidirectional Flow Analysis</span>
348
+ </li>
349
+ <li class="flex items-start gap-3"><i
350
+ class="fa-solid fa-circle text-[5px] mt-1.5 text-slate-400"></i>
351
+ <span>High-Density Metrics Capture</span>
352
+ </li>
353
  </ul>
354
  </div>
355
  <div class="space-y-4">
356
+ <h4 class="font-bold text-slate-800 text-[13px] uppercase tracking-wider">Intelligence
357
+ Framework
358
  </h4>
359
+ <ul class="text-xs space-y-3 pl-1">
360
+ <li class="flex items-start gap-3"><i
361
+ class="fa-solid fa-circle text-[5px] mt-1.5 text-slate-400"></i>
362
+ <span>OpenVINO Performance Optimization</span>
363
+ </li>
364
+ <li class="flex items-start gap-3"><i
365
+ class="fa-solid fa-circle text-[5px] mt-1.5 text-slate-400"></i>
366
+ <span>ByteTrack Temporal Assignment</span>
367
+ </li>
368
+ <li class="flex items-start gap-3"><i
369
+ class="fa-solid fa-circle text-[5px] mt-1.5 text-slate-400"></i>
370
+ <span>Sub-millimeter Coordinate Logic</span>
371
+ </li>
372
+ <li class="flex items-start gap-3"><i
373
+ class="fa-solid fa-circle text-[5px] mt-1.5 text-slate-400"></i>
374
+ <span>Enterprise MIS Compliance</span>
375
+ </li>
376
  </ul>
377
  </div>
378
  </div>
 
385
  <div
386
  class="bg-white rounded-xl px-6 py-4 border border-slate-200 shadow-sm flex items-center justify-between flex-shrink-0">
387
  <div class="flex items-center space-x-4 flex-1 mr-6">
388
+ <span class="text-[11px] font-black text-white uppercase tracking-wider whitespace-nowrap"
389
  id="proc-label">Waiting</span>
390
+ <div class="flex-1 h-2 bg-[#111111] rounded-full overflow-hidden relative border border-[#1a1a1a]">
391
+ <div id="proc-bar" class="h-full bg-[#444444] rounded-full transition-all duration-500 ease-out"
392
  style="width: 0%"></div>
393
  </div>
394
  </div>
395
+ <div class="flex items-center space-x-6 text-xs font-bold text-white whitespace-nowrap">
396
  <span id="proc-frames">0 / 0 Frames</span>
397
  <span id="proc-pct">0%</span>
398
  </div>
 
642
  <option value="corporate">Corporate</option>
643
  </select>
644
  </div>
645
+ <div class="s-row hidden">
646
+ <div>
647
+ <div class="text-xs font-semibold text-slate-700">Interface Mode</div>
648
+ <div class="text-[10px] text-slate-400">Locked to Professional Dark</div>
649
+ </div>
650
+ <div class="text-xs font-bold text-white px-3 py-1 bg-slate-800 rounded-full">Dark Mode Only
651
+ </div>
652
+ </div>
653
  <div class="mt-3">
654
  <div class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-2">Palette
655
  Preview</div>
 
661
  <!-- Start Button -->
662
  <div class="col-span-3 pb-4" id="settings-start-wrap">
663
  <button id="btn-start-processing" onclick="startProcessingFromSettings()"
664
+ class="w-full py-4 bg-[#0a0a0a] border border-[#222222] text-white font-bold text-sm rounded-full hover:bg-[#111111] active:scale-[0.99] transition flex items-center justify-center gap-2 shadow-lg">
665
+ <span>Process <i class="fa-solid fa-arrow-right ml-2 text-[10px]"></i></span>
666
  </button>
667
  </div>
668
 
 
674
  </main>
675
 
676
  <script>
677
+ // =========== Theme Management ===========
678
  // =========== Tooltip ===========
679
+ // Position and toggle tooltip visibility
680
  document.addEventListener('mouseover', e => {
681
  const wrap = e.target.closest('.info-wrap');
682
  if (!wrap) return;
683
  const tip = wrap.querySelector('.info-tip');
684
  if (!tip) return;
685
+
686
+ tip.style.display = 'block';
687
  const rect = wrap.getBoundingClientRect();
688
+ const tipH = tip.offsetHeight || 60;
689
  if (rect.bottom + tipH + 10 > window.innerHeight) {
690
  tip.style.top = (rect.top - tipH - 6) + 'px';
691
  } else {
 
694
  tip.style.left = Math.min(rect.left, window.innerWidth - 300) + 'px';
695
  });
696
 
697
+ document.addEventListener('mouseout', e => {
698
+ const wrap = e.target.closest('.info-wrap');
699
+ if (!wrap) return;
700
+ const tip = wrap.querySelector('.info-tip');
701
+ if (tip) tip.style.display = 'none';
702
+ });
703
+
704
+ document.addEventListener('click', e => {
705
+ const btn = e.target.closest('.info-btn');
706
+ if (!btn) return;
707
+ const wrap = btn.closest('.info-wrap');
708
+ if (!wrap) return;
709
+ const tip = wrap.querySelector('.info-tip');
710
+ if (!tip) return;
711
+
712
+ const isVisible = tip.style.display === 'block';
713
+ tip.style.display = isVisible ? 'none' : 'block';
714
+
715
+ if (!isVisible) {
716
+ const rect = wrap.getBoundingClientRect();
717
+ const tipH = tip.offsetHeight || 60;
718
+ if (rect.bottom + tipH + 10 > window.innerHeight) {
719
+ tip.style.top = (rect.top - tipH - 6) + 'px';
720
+ } else {
721
+ tip.style.top = (rect.bottom + 6) + 'px';
722
+ }
723
+ tip.style.left = Math.min(rect.left, window.innerWidth - 300) + 'px';
724
+ }
725
+ });
726
+
727
  // =========== Tab switching ===========
728
  function switchTab(tab) {
729
  ['about', 'overview', 'run-details', 'reports', 'settings'].forEach(t => {
 
732
  if (el) el.classList.toggle('hidden', tab !== t);
733
  if (nav) {
734
  if (tab === t) {
735
+ nav.classList.add('nav-item-active');
736
+ nav.classList.remove('nav-item-inactive');
737
  } else {
738
+ nav.classList.remove('nav-item-active');
739
+ nav.classList.add('nav-item-inactive');
740
  }
741
  }
742
  });
 
745
  // =========== Run Details helpers ===========
746
  function detailRow(label, value, extra) {
747
  extra = extra || '';
748
+ return `<div class="flex justify-between items-center border-b border-slate-800 pb-2">
749
  <span class="text-xs font-medium text-slate-500 mono-font">${label}</span>
750
+ <span class="text-sm font-bold text-white">${value}${extra}</span>
751
  </div>`;
752
  }
753
 
754
  function infoRow(label, value, tip, extra) {
755
  extra = extra || '';
756
+ return `<div class="flex justify-between items-center border-b border-slate-800 pb-2 relative">
757
  <span class="text-xs font-medium text-slate-500 mono-font flex items-center">${label}
758
  <span class="info-wrap"><span class="info-btn"><i class="fa-solid fa-info"></i></span>
759
  <span class="info-tip">${tip}</span></span>
760
  </span>
761
+ <span class="text-sm font-bold text-white">${value}${extra}</span>
762
  </div>`;
763
  }
764
 
 
779
 
780
  const cpuPct = Math.min(100, Math.round((c.cpu_score / 10) * 100));
781
  document.getElementById('panel-perf').innerHTML =
782
+ `<div class="flex justify-between items-center border-b border-slate-800 pb-2 relative">
783
  <span class="text-xs font-medium text-slate-500 mono-font flex items-center">cpu_score
784
  <span class="info-wrap"><span class="info-btn"><i class="fa-solid fa-info"></i></span>
785
  <span class="info-tip">Available CPU core count used for throughput estimation.</span></span>
786
  </span>
787
  <div class="flex items-center">
788
+ <span class="text-sm font-bold text-white mr-2">${c.cpu_score}</span>
789
+ <div class="w-16 h-1.5 bg-slate-800 rounded-full overflow-hidden">
790
  <div class="h-full bg-emerald-500" style="width:${cpuPct}%"></div>
791
  </div>
792
  </div>
 
795
  infoRow('effective_fps', c.effective_fps_est, 'Adjusted throughput accounting for frame stride.', ' <span class="text-xs text-slate-400 font-normal">fps</span>');
796
 
797
  document.getElementById('panel-model').innerHTML =
798
+ `<div class="flex justify-between items-center border-b border-slate-800 pb-2">
799
  <span class="text-xs font-medium text-slate-500 mono-font">model</span>
800
+ <a href="https://huggingface.co/Perception365/VehicleNet-Y26s" target="_blank" class="text-sm font-bold text-white mono-font hover:text-slate-300 transition underline underline-offset-4 decoration-slate-700">Perception365/VehicleNet-Y26s</a>
801
  </div>` +
802
  detailRow('task', 'detect') +
803
  detailRow('format', 'OpenVINO') +
 
827
 
828
  // =========== Charts ===========
829
  Chart.defaults.font.family = "'Montserrat', sans-serif";
830
+ Chart.defaults.color = '#888888';
831
+ Chart.defaults.borderColor = '#222222';
832
+ Chart.defaults.plugins.tooltip.backgroundColor = '#0a0a0a';
833
+ Chart.defaults.plugins.tooltip.titleColor = '#ffffff';
834
+ Chart.defaults.plugins.tooltip.bodyColor = '#aaaaaa';
835
+ Chart.defaults.plugins.tooltip.borderColor = '#222222';
836
+ Chart.defaults.plugins.tooltip.borderWidth = 1;
837
 
838
  let MODEL_CLASSES = {};
839
  let BUSINESS_MAP = {};
 
855
  responsive: true, maintainAspectRatio: false,
856
  plugins: { legend: { display: false } },
857
  scales: {
858
+ x: { grid: { display: false }, ticks: { maxTicksLimit: 10, font: { size: 10 }, color: '#666666' }, title: { display: true, text: 'Frame Index', font: { size: 10, weight: '700' }, color: '#888888' } },
859
+ y: { grid: { color: '#333333' }, beginAtZero: true, ticks: { font: { size: 10 }, color: '#666666' }, title: { display: true, text: 'Active Vehicles', font: { size: 10, weight: '700' }, color: '#888888' } }
860
  },
861
  animation: { duration: 0 }
862
  }
 
868
  labels: ['Incoming', 'Outgoing'], datasets: [{
869
  data: [0, 0],
870
  backgroundColor: [activePalette.doughIn, activePalette.doughOut],
871
+ borderColor: '#0a0a0a',
872
  borderWidth: 3,
873
  hoverOffset: 6
874
  }]
 
890
  responsive: true, maintainAspectRatio: false,
891
  plugins: { legend: { display: false } },
892
  scales: {
893
+ x: { grid: { display: false }, ticks: { font: { size: 10, weight: '500' }, color: '#666666' } },
894
+ y: { grid: { color: '#333333' }, beginAtZero: true, ticks: { font: { size: 10 }, color: '#666666' }, title: { display: true, text: 'Total Vehicle Count', font: { size: 10, weight: '700' }, color: '#888888' } }
895
  },
896
  animation: { duration: 0 }
897
  }
 
899
 
900
  const flowChart = new Chart(document.getElementById('flowChart').getContext('2d'), {
901
  type: 'bar',
902
+ data: { labels: [], datasets: [{ data: [], backgroundColor: activePalette.flow, borderColor: '#0a0a0a', borderWidth: 1.5, barPercentage: 1.0, categoryPercentage: 1.0 }] },
903
  options: {
904
  responsive: true, maintainAspectRatio: false,
905
  plugins: { legend: { display: false } },
906
  scales: {
907
+ x: { grid: { display: false }, ticks: { maxTicksLimit: 10, font: { size: 10 }, color: '#666666' }, title: { display: true, text: 'Time (seconds)', font: { size: 10, weight: '700' }, color: '#888888' } },
908
+ y: { grid: { color: '#333333' }, beginAtZero: true, ticks: { font: { size: 10 }, color: '#666666' }, title: { display: true, text: 'Vehicles Crossed', font: { size: 10, weight: '700' }, color: '#888888' } }
909
  },
910
  animation: { duration: 0 }
911
  }
 
952
  const el = document.getElementById('live-palette-preview');
953
  if (el) {
954
  el.innerHTML = colors.map(c =>
955
+ `<div class="flex-1 rounded-lg overflow-hidden border border-neutral-800">
956
  <div class="h-6" style="background:${c.color}"></div>
957
+ <div class="text-[8px] font-bold text-neutral-500 text-center py-1 bg-neutral-900">${c.label}</div>
958
  </div>`
959
  ).join('');
960
  }
 
1028
  const pct = totalAll > 0 ? ((total / totalAll) * 100).toFixed(1) : '0.0';
1029
 
1030
  const row = document.createElement('div');
1031
+ row.className = 'flex items-center justify-between text-xs py-2 border-b border-slate-800';
1032
  row.innerHTML = `
1033
+ <div class="w-[30%] font-bold text-white truncate" title="${MODEL_CLASSES[id]}">${MODEL_CLASSES[id]}</div>
1034
  <div class="w-[20%] text-slate-500 text-[11px]">${total} total</div>
1035
  <div class="w-[15%] text-slate-500 text-[11px]"><i class="fa-solid fa-arrow-down text-[9px] mr-1"></i>${inC}</div>
1036
  <div class="w-[15%] text-slate-500 text-[11px]"><i class="fa-solid fa-arrow-up text-[9px] mr-1"></i>${outC}</div>
1037
+ <div class="w-[20%] text-right font-bold text-white">${pct}%</div>
1038
  `;
1039
  container.appendChild(row);
1040
  });
 
1138
  const badge = document.getElementById('results-status-badge');
1139
  if (badge) {
1140
  badge.innerText = 'Processing';
1141
+ badge.className = 'px-2.5 py-1 bg-slate-800 text-white text-[10px] font-bold rounded-full uppercase tracking-tighter animate-pulse';
1142
  }
1143
  document.getElementById('run-results-content').innerHTML = `
1144
+ <div class="flex flex-col items-center justify-center p-8 bg-black/40 border border-slate-800 rounded-2xl col-span-3 text-slate-500">
1145
+ <i class="fa-solid fa-spinner fa-spin text-2xl mb-3 text-white"></i>
1146
  <span class="text-xs font-semibold">Executing inference pipeline... results pending</span>
1147
  </div>`;
1148
 
 
1162
  }));
1163
  };
1164
 
1165
+ ws.onerror = e => {
1166
+ console.error('WS Error:', e);
1167
+ document.getElementById('proc-label').innerText = 'Connection Error';
1168
+ if (badge) {
1169
+ badge.innerText = 'Pipeline Failed';
1170
+ badge.className = 'px-2.5 py-1 bg-red-900/40 text-red-100 text-[10px] font-bold rounded-full uppercase tracking-tighter';
1171
+ }
1172
+ };
1173
+
1174
+ ws.onclose = () => {
1175
+ console.log('WS Closed');
1176
+ };
1177
+
1178
  let lastUIUpdate = 0;
1179
 
1180
  ws.onmessage = e => {
 
1189
  const badge = document.getElementById('results-status-badge');
1190
  if (badge) {
1191
  badge.innerText = 'Completed';
1192
+ badge.className = 'px-2.5 py-1 bg-white text-black text-[10px] font-bold rounded-full uppercase tracking-tighter';
1193
  }
1194
 
1195
  document.getElementById('run-results-content').innerHTML =
 
1255
  const isVideo = name.endsWith('.mp4');
1256
  const isPDF = name.endsWith('.pdf');
1257
  const card = document.createElement('div');
1258
+ card.className = 'bg-black rounded-xl border border-slate-800 shadow-sm flex flex-col overflow-hidden';
1259
 
1260
  let previewHTML = '';
1261
  if (isVideo) {
1262
  previewHTML = `
1263
+ <div class="flex flex-col items-center justify-center py-12 text-slate-700">
1264
+ <i class="fa-solid fa-film text-6xl mb-4 text-white"></i>
1265
+ <span class="text-xs font-bold uppercase tracking-widest text-slate-500">Video Ready for Local Analysis</span>
1266
  </div>`;
1267
  } else if (isPDF) {
1268
  previewHTML = `
1269
+ <div class="flex flex-col items-center justify-center py-12 text-slate-700">
1270
+ <i class="fa-solid fa-file-pdf text-6xl mb-4 text-white"></i>
1271
+ <span class="text-xs font-bold uppercase tracking-widest text-slate-500">PDF Document</span>
1272
  </div>`;
1273
  } else {
1274
  previewHTML = `<img src="${url}" alt="${info.title}" class="max-w-full max-h-[320px] object-contain rounded">`;
1275
  }
1276
 
1277
  card.innerHTML = `
1278
+ <div class="px-5 py-3 border-b border-slate-800 bg-slate-900/40 flex justify-between items-center">
1279
  <div>
1280
+ <h3 class="font-bold text-white text-sm">${info.title}</h3>
1281
  <p class="text-[10px] text-slate-400 mt-0.5">${info.desc}</p>
1282
  </div>
1283
  <a href="${url}" download="${name}"
1284
+ class="inline-flex items-center gap-1.5 px-3 py-1.5 border border-[#444444] text-white text-[10px] font-bold rounded-full hover:bg-neutral-800 transition uppercase tracking-wider">
1285
  <i class="fa-solid fa-download text-[9px]"></i> Download
1286
  </a>
1287
  </div>
1288
+ <div class="p-4 flex items-center justify-center bg-black/30">
1289
  ${previewHTML}
1290
  </div>
1291
  `;