Spaces:
Running
Running
File size: 21,492 Bytes
b87a24a 23f66d2 b87a24a 23f66d2 b87a24a 3e04ea5 089fec2 b87a24a c734973 b87a24a 3e04ea5 bb34c97 c734973 b87a24a bb34c97 c734973 bb34c97 c734973 bb34c97 c734973 bb34c97 c734973 b87a24a d9ebe88 b87a24a bb34c97 b87a24a bb34c97 89b4995 b87a24a 3207e43 b87a24a c734973 f111bd5 b87a24a c734973 f111bd5 b87a24a bb34c97 7932d1b b87a24a 3e04ea5 b87a24a c734973 b87a24a c734973 7f449cc c734973 7f449cc b87a24a bb34c97 b87a24a bb34c97 b7e37b8 b87a24a 7932d1b 7f449cc bb34c97 b87a24a 7932d1b 7f449cc b87a24a c734973 b87a24a bb34c97 b87a24a c734973 b87a24a bb34c97 7932d1b b87a24a bb34c97 b87a24a bb34c97 b87a24a bb34c97 b87a24a e0d9763 b87a24a e0d9763 b87a24a 3e04ea5 bb34c97 b87a24a bb34c97 b87a24a bb34c97 a80bf6a bb34c97 b87a24a bb34c97 b87a24a a80bf6a b87a24a a80bf6a bb34c97 b87a24a bb34c97 b87a24a a80bf6a b87a24a bb34c97 b87a24a bb34c97 b87a24a bb34c97 b87a24a bb34c97 b87a24a bb34c97 b87a24a bb34c97 b87a24a bb34c97 b87a24a c734973 b87a24a c734973 b87a24a c734973 b87a24a 23f66d2 b87a24a d9ebe88 92dde2d d9ebe88 b87a24a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 | <!DOCTYPE html>
<html lang="en">
<head>
<!-- SPA Bootstrap: load dashboard without changing URL -->
<script>
if (sessionStorage.getItem('funky_run')) {
document.documentElement.style.display = 'none';
fetch('/vehicles.html')
.then(function (r) { return r.text(); })
.then(function (html) {
document.open();
document.write(html);
document.close();
});
}
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UrbanFlow</title>
<link rel="icon" type="image/svg+xml" href="rf.png">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700;800;900&display=swap"
rel="stylesheet">
<style>
:root {
--cocoa: #8b5e3c;
--cocoa-l: #c89a6c;
--cocoa-xl: #d4b08a;
--t1: #f0ece6;
--t2: #a89f97;
--border: #2a2a2a;
}
body {
font-family: 'Montserrat', sans-serif;
background-color: #000000;
color: var(--t1);
}
.fade-in {
animation: fadeIn 0.4s ease-in-out forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Executive Overrides */
.traffic-dynamics-card {
background-color: #0a0a0a !important;
border: 2px solid var(--cocoa) !important;
}
.traffic-dynamics-card:hover {
border-color: var(--cocoa-l) !important;
}
#dropzone {
transition: all 0.2s ease;
border-color: #2a2a2a;
}
#dropzone:hover {
border-color: var(--cocoa-l) !important;
background-color: #0a0a0a !important;
}
.core-badge {
background-color: var(--cocoa) !important;
color: var(--t1) !important;
}
/* Onboarding */
.onboard-overlay {
position: fixed; inset: 0; z-index: 9999;
background: rgba(0,0,0,0.92);
display: flex; align-items: center; justify-content: center;
}
.onboard-card {
background: #0a0a0a; border: 1px solid #2a2a2a;
border-radius: 16px; max-width: 440px; width: 90%;
padding: 40px 32px; text-align: center;
}
.onboard-step { display: none; }
.onboard-step.active { display: block; }
.onboard-dots { display: flex; gap: 6px; justify-content: center; margin-top: 20px; }
.onboard-dot {
width: 8px; height: 8px; border-radius: 50%;
background: #333; transition: background 0.2s;
}
.onboard-dot.active { background: var(--cocoa-l); }
/* Mobile responsive */
@media (max-width: 768px) {
main { grid-template-columns: 1fr !important; padding: 16px !important; }
h1 { font-size: 2.2rem !important; }
}
</style>
</head>
<body
class="bg-black text-white min-h-screen w-full flex flex-col items-center selection:bg-white selection:text-black">
<header class="mt-16 flex flex-col items-center flex-shrink-0 w-full z-10">
<img src="uf_rf.png" alt="UrbanFlow Logo" class="h-44 md:h-52 w-auto object-contain mb-3">
</header>
<main
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">
<div class="lg:col-span-7 flex flex-col justify-center xl:pl-10 pb-10 lg:pb-0">
<h1 class="text-5xl xl:text-[4.5rem] font-extrabold mb-4 leading-[1.1] tracking-tight" style="background:linear-gradient(110deg,#f0ece6 0%,#f0ece6 35%,#c89a6c 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text">
Automated <br>Vision Intelligence
</h1>
<p class="font-bold mb-8 text-sm uppercase tracking-[0.2em] flex items-center" style="color:#a89f97">
<span class="core-badge px-3 py-1 rounded-full text-[10px] mr-3">DEMO</span>
Cloud-Native Traffic Intelligence
</p>
<ul class="space-y-4 xl:space-y-5 text-base xl:text-lg font-medium" style="color:#a89f97">
<li class="flex items-center"><i class="fa-solid fa-check mr-5 text-xl" style="color:#c89a6c"></i> No new hardware — works with your existing cameras</li>
<li class="flex items-center"><i class="fa-solid fa-check mr-5 text-xl" style="color:#c89a6c"></i> Granular vehicle counts across 14 Indian road classes</li>
<li class="flex items-center"><i class="fa-solid fa-check mr-5 text-xl" style="color:#c89a6c"></i> Directional flow & congestion insights in minutes</li>
<li class="flex items-center"><i class="fa-solid fa-check mr-5 text-xl" style="color:#c89a6c"></i> Downloadable reports ready for planning & compliance</li>
<li class="flex items-center"><i class="fa-solid fa-check mr-5 text-xl" style="color:#c89a6c"></i> Built for Indian roads — tested on real field conditions</li>
</ul>
</div>
<div
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">
<!-- STEP: Modules -->
<div id="step-modules" class="w-full flex flex-col fade-in">
<h2 class="text-3xl font-bold mb-2 text-center" style="color:#f0ece6">UrbanFlow</h2>
<p class="text-[13px] font-medium mb-8 text-center" style="color:#a89f97">Select an analytical module to continue.</p>
<div class="flex justify-center w-full">
<div onclick="showStep('upload')"
class="group relative border-2 rounded-[2rem] p-8 cursor-pointer hover:-translate-y-1 transition-all duration-300 text-center max-w-sm w-full traffic-dynamics-card">
<div
class="absolute top-4 right-6 text-[9px] font-bold px-2.5 py-1 rounded-full uppercase tracking-wider"
style="background:#c89a6c;color:#000">
DEMO</div>
<i class="fa-solid fa-car-side text-4xl mb-4 block mx-auto" style="color:#c89a6c"></i>
<h3 class="font-bold text-lg mb-2 leading-tight" style="color:#f0ece6">Traffic <br>Dynamics</h3>
<p class="text-[10px] font-medium leading-relaxed" style="color:#a89f97">Vehicle counting, classification, and flow analysis for Indian roads.</p>
</div>
</div>
</div>
<!-- STEP: Upload -->
<div id="step-upload" class="hidden w-full flex flex-col fade-in">
<button onclick="showStep('modules')"
class="text-neutral-500 hover:text-white transition flex items-center text-xs font-bold uppercase tracking-widest mb-6 w-fit">
<i class="fa-solid fa-arrow-left mr-2"></i> Back
</button>
<h2 class="text-3xl font-bold mb-2 text-center" style="color:#f0ece6">Source Media</h2>
<p class="text-[13px] font-medium mb-8 text-center" style="color:#a89f97">Submit camera footage to begin traffic analysis.</p>
<input id="file-input" type="file" accept="video/*" class="hidden">
<div id="dropzone" onclick="document.getElementById('file-input').click()"
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">
<i
class="fa-solid fa-arrow-up-from-bracket text-4xl mb-5 block mx-auto transition" style="color:#a89f97"></i>
<span class="font-semibold text-lg mb-2 text-center block" style="color:#f0ece6">Drop or select a video file</span>
<span class="text-[10px] font-bold uppercase tracking-widest text-center block" style="color:#a89f97">Any standard video format accepted</span>
</div>
<div id="upload-progress-container" class="hidden mt-10 w-full">
<div class="flex justify-between text-[11px] font-bold uppercase tracking-widest mb-3" style="color:#f0ece6">
<span id="upload-text">Uploading...</span>
<span id="upload-percentage">0%</span>
</div>
<div class="w-full h-1 bg-neutral-900 rounded-full overflow-hidden">
<div id="upload-bar"
class="h-full w-0 transition-all duration-75 ease-linear rounded-full" style="background:#c89a6c"></div>
</div>
</div>
</div>
<!-- STEP: Draw -->
<div id="step-draw" class="hidden w-full flex flex-col fade-in">
<h2 class="text-3xl font-bold mb-2 text-center" style="color:#f0ece6">Spatial Boundary</h2>
<p class="text-[11px] font-bold uppercase tracking-widest mb-6 text-center" style="color:#a89f97">Mark two points to define the vehicle counting threshold</p>
<div
class="relative w-full aspect-video bg-neutral-950 rounded-3xl overflow-hidden cursor-crosshair mb-6">
<img id="frame-img" class="absolute inset-0 w-full h-full object-contain" style="display:none;">
<div id="frame-placeholder"
class="absolute inset-0 flex flex-col items-center justify-center text-neutral-800 pointer-events-none">
<i class="fa-solid fa-video text-4xl mb-3 opacity-30"></i>
<span class="font-bold text-[10px] uppercase tracking-widest opacity-50">Media Frame
Preview</span>
</div>
<canvas id="drawing-canvas" class="absolute inset-0 w-full h-full"></canvas>
</div>
<div class="flex flex-col items-center gap-3">
<button id="btn-proceed" onclick="startRun()"
class="w-fit px-16 py-3.5 rounded-full font-bold transition-all text-center text-sm shadow-lg hover:scale-105 active:scale-95" style="background:#c89a6c;color:#000">
Continue →
</button>
<button onclick="resetCanvas()"
class="text-[10px] font-bold uppercase tracking-widest text-slate-500 hover:text-white transition" style="background:none;border:none;">Reset Boundary</button>
</div>
</div>
</div>
</main>
<script>
let videoId = null;
let runConfig = {};
function showStep(name) {
['modules', 'upload', 'draw'].forEach(s => {
const el = document.getElementById('step-' + s);
if (el) el.classList.add('hidden');
});
const target = document.getElementById('step-' + name);
if (target) target.classList.remove('hidden');
if (name === 'upload') {
document.getElementById('upload-progress-container').classList.add('hidden');
document.getElementById('dropzone').classList.remove('hidden');
// Reset Progress Bar state for new uploads
document.getElementById('upload-bar').style.width = '0%';
document.getElementById('upload-percentage').innerText = '0%';
document.getElementById('upload-text').innerText = 'Uploading...';
document.getElementById('upload-text').classList.remove('text-red-500');
}
if (name === 'draw') loadFirstFrame();
}
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('file-input');
if (fileInput) {
fileInput.addEventListener('change', () => {
if (fileInput.files.length) uploadFile(fileInput.files[0]);
});
}
if (dropzone) {
dropzone.addEventListener('dragover', e => { e.preventDefault(); dropzone.classList.add('border-white', 'bg-neutral-950'); });
dropzone.addEventListener('dragleave', () => dropzone.classList.remove('border-white', 'bg-neutral-950'));
dropzone.addEventListener('drop', e => {
e.preventDefault();
dropzone.classList.remove('border-white', 'bg-neutral-950');
if (e.dataTransfer.files.length) uploadFile(e.dataTransfer.files[0]);
});
}
let currentXHR = null;
function uploadFile(file) {
// Abort previous upload if it exists to prevent jitter/multiple requests
if (currentXHR) currentXHR.abort();
const dropzoneEl = document.getElementById('dropzone');
const prog = document.getElementById('upload-progress-container');
const bar = document.getElementById('upload-bar');
const pct = document.getElementById('upload-percentage');
const txt = document.getElementById('upload-text');
if (dropzoneEl) dropzoneEl.classList.add('hidden');
if (prog) prog.classList.remove('hidden');
const form = new FormData();
form.append('file', file);
const xhr = new XMLHttpRequest();
currentXHR = xhr;
xhr.open('POST', '/upload');
xhr.upload.onprogress = e => {
if (e.lengthComputable) {
const p = Math.round(e.loaded / e.total * 100);
bar.style.width = p + '%';
pct.innerText = p + '%';
}
};
xhr.onerror = () => {
txt.innerText = 'Error: Network failure';
txt.classList.add('text-red-500');
fileInput.value = '';
};
xhr.onload = () => {
if (xhr.status !== 200) {
txt.innerText = 'Error: ' + xhr.status;
txt.classList.add('text-red-500');
fileInput.value = '';
return;
}
const res = JSON.parse(xhr.responseText);
videoId = res.video_id;
txt.innerText = 'Extracting Metadata...';
bar.style.width = '100%';
pct.innerText = '100%';
fetch('/config/' + videoId)
.then(r => r.json())
.then(cfg => {
runConfig = cfg;
runConfig.conf = 0.12;
runConfig.iou = 0.60;
txt.innerText = 'Initialization Complete';
fileInput.value = '';
setTimeout(() => showStep('draw'), 800);
})
.catch(e => {
txt.innerText = 'Metadata Failed';
txt.classList.add('text-red-500');
fileInput.value = '';
});
};
xhr.send(form);
}
// Draw Canvas Logic
const canvas = document.getElementById('drawing-canvas');
const ctx = canvas.getContext('2d');
let points = [];
let imgNatW = 0, imgNatH = 0;
function loadFirstFrame() {
const img = document.getElementById('frame-img');
img.src = '/first-frame/' + videoId;
img.onload = () => {
imgNatW = img.naturalWidth;
imgNatH = img.naturalHeight;
img.style.display = 'block';
document.getElementById('frame-placeholder').style.display = 'none';
initCanvas();
};
}
function initCanvas() {
if (canvas) {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
}
}
window.addEventListener('resize', initCanvas);
if (canvas) {
canvas.addEventListener('mousedown', e => {
if (points.length >= 2) return;
const rect = canvas.getBoundingClientRect();
const cx = e.clientX - rect.left;
const cy = e.clientY - rect.top;
const rx = (cx / canvas.width) * imgNatW;
const ry = (cy / canvas.height) * imgNatH;
points.push({ cx, cy, rx: Math.round(rx), ry: Math.round(ry) });
drawDot(cx, cy);
if (points.length === 2) drawLine();
});
}
function drawDot(x, y) {
ctx.beginPath();
ctx.arc(x, y, 5, 0, Math.PI * 2);
ctx.fillStyle = '#c89a6c';
ctx.fill();
ctx.strokeStyle = '#f0ece6';
ctx.lineWidth = 2;
ctx.stroke();
}
function drawLine() {
ctx.beginPath();
ctx.moveTo(points[0].cx, points[0].cy);
ctx.lineTo(points[1].cx, points[1].cy);
ctx.strokeStyle = '#c89a6c';
ctx.lineWidth = 3;
ctx.stroke();
}
function resetCanvas() {
points = [];
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function startRun() {
if (points.length < 2) return;
const line = [[points[0].rx, points[0].ry], [points[1].rx, points[1].ry]];
sessionStorage.setItem('funky_run', JSON.stringify({
video_id: videoId,
line: line,
config: runConfig
}));
window.location.href = '/';
}
</script>
<!-- Onboarding Walkthrough -->
<div id="onboard-overlay" class="onboard-overlay" style="display:none">
<div class="onboard-card">
<div class="onboard-step active" data-step="0">
<i class="fa-solid fa-cloud-arrow-up text-4xl mb-4" style="color:var(--cocoa-l)"></i>
<h3 class="text-lg font-bold mb-2" style="color:#f0ece6">Upload a Traffic Video</h3>
<p class="text-xs" style="color:#777;line-height:1.7">Drag & drop or select a video file recorded from any traffic camera. MP4, MOV, AVI formats supported.</p>
</div>
<div class="onboard-step" data-step="1">
<i class="fa-solid fa-draw-polygon text-4xl mb-4" style="color:var(--cocoa-l)"></i>
<h3 class="text-lg font-bold mb-2" style="color:#f0ece6">Draw a Counting Boundary</h3>
<p class="text-xs" style="color:#777;line-height:1.7">Click two points on the first frame to define a spatial boundary. Vehicles crossing this line will be counted and classified.</p>
</div>
<div class="onboard-step" data-step="2">
<i class="fa-solid fa-chart-line text-4xl mb-4" style="color:var(--cocoa-l)"></i>
<h3 class="text-lg font-bold mb-2" style="color:#f0ece6">Review Analytics & Export</h3>
<p class="text-xs" style="color:#777;line-height:1.7">Watch real-time charts populate as inference runs. Download annotated video, reports, and structured JSON when complete.</p>
</div>
<div class="onboard-dots">
<span class="onboard-dot active"></span>
<span class="onboard-dot"></span>
<span class="onboard-dot"></span>
</div>
<div class="flex gap-3 justify-center mt-6">
<button onclick="closeOnboarding()" class="text-[10px] font-bold uppercase tracking-widest px-4 py-2 rounded-full" style="color:#555;border:1px solid #222">Skip</button>
<button id="onboard-next" onclick="nextOnboardStep()" class="text-[10px] font-bold uppercase tracking-widest px-6 py-2 rounded-full" style="background:var(--cocoa);color:#f0ece6">Next</button>
</div>
</div>
</div>
<script>
let _obStep = 0;
function nextOnboardStep() {
_obStep++;
if (_obStep >= 3) { closeOnboarding(); return; }
document.querySelectorAll('.onboard-step').forEach((s, i) => s.classList.toggle('active', i === _obStep));
document.querySelectorAll('.onboard-dot').forEach((d, i) => d.classList.toggle('active', i === _obStep));
if (_obStep === 2) document.getElementById('onboard-next').innerText = 'Get Started';
}
function closeOnboarding() {
document.getElementById('onboard-overlay').style.display = 'none';
}
// Show onboarding on every page load
document.getElementById('onboard-overlay').style.display = 'flex';
</script>
</body>
</html> |