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 &mdash; 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 &amp; 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 &amp; 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 &mdash; 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 &nbsp;&rarr;
                    </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>