Spaces:
Running
Running
code refactored: high cohesion & low coupling..; seperation of concerns
Browse files- frontend/css/initial.css +16 -78
- frontend/initial.html +26 -36
- frontend/js/initial.js +45 -34
frontend/css/initial.css
CHANGED
|
@@ -6,127 +6,65 @@
|
|
| 6 |
--t2: #a89f97;
|
| 7 |
--border: #2a2a2a;
|
| 8 |
}
|
| 9 |
-
|
| 10 |
body {
|
| 11 |
font-family: 'Montserrat', sans-serif;
|
| 12 |
-
background-color: #000000
|
| 13 |
color: var(--t1);
|
| 14 |
-
margin: 0;
|
| 15 |
-
padding: 0;
|
| 16 |
}
|
| 17 |
-
|
| 18 |
.fade-in {
|
| 19 |
animation: fadeIn 0.4s ease-in-out forwards;
|
| 20 |
}
|
| 21 |
-
|
| 22 |
@keyframes fadeIn {
|
| 23 |
from {
|
| 24 |
opacity: 0;
|
| 25 |
transform: translateY(10px);
|
| 26 |
}
|
| 27 |
-
|
| 28 |
to {
|
| 29 |
opacity: 1;
|
| 30 |
transform: translateY(0);
|
| 31 |
}
|
| 32 |
}
|
| 33 |
-
|
| 34 |
-
/* Executive Overrides for Pure Black Theme */
|
| 35 |
-
.bg-black {
|
| 36 |
-
background-color: #000000 !important;
|
| 37 |
-
}
|
| 38 |
-
|
| 39 |
-
.bg-neutral-950 {
|
| 40 |
-
background-color: #050505 !important;
|
| 41 |
-
}
|
| 42 |
-
|
| 43 |
-
.bg-neutral-900 {
|
| 44 |
-
background-color: #0a0a0a !important;
|
| 45 |
-
}
|
| 46 |
-
|
| 47 |
-
.text-white {
|
| 48 |
-
color: #f0ece6 !important;
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
.traffic-dynamics-card {
|
| 52 |
-
background-color: #
|
| 53 |
border: 2px solid var(--cocoa) !important;
|
| 54 |
-
transition: all 0.3s ease;
|
| 55 |
}
|
| 56 |
-
|
| 57 |
.traffic-dynamics-card:hover {
|
| 58 |
border-color: var(--cocoa-l) !important;
|
| 59 |
-
transform: translateY(-4px);
|
| 60 |
-
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
| 61 |
}
|
| 62 |
-
|
| 63 |
#dropzone {
|
| 64 |
transition: all 0.2s ease;
|
| 65 |
-
border-color: #2a2a2a
|
| 66 |
-
background-color: #020202 !important;
|
| 67 |
}
|
| 68 |
-
|
| 69 |
#dropzone:hover {
|
| 70 |
border-color: var(--cocoa-l) !important;
|
| 71 |
-
background-color: #
|
| 72 |
}
|
| 73 |
-
|
| 74 |
.core-badge {
|
| 75 |
background-color: var(--cocoa) !important;
|
| 76 |
-
color:
|
| 77 |
-
font-weight: 800;
|
| 78 |
}
|
| 79 |
-
|
| 80 |
/* Onboarding */
|
| 81 |
.onboard-overlay {
|
| 82 |
position: fixed; inset: 0; z-index: 9999;
|
| 83 |
-
background: rgba(0,0,0,0.
|
| 84 |
display: flex; align-items: center; justify-content: center;
|
| 85 |
-
backdrop-filter: blur(4px);
|
| 86 |
}
|
| 87 |
-
|
| 88 |
.onboard-card {
|
| 89 |
-
background: #
|
| 90 |
-
border-radius:
|
| 91 |
padding: 40px 32px; text-align: center;
|
| 92 |
-
box-shadow: 0 20px 50px rgba(0,0,0,0.8);
|
| 93 |
}
|
| 94 |
-
|
| 95 |
.onboard-step { display: none; }
|
| 96 |
.onboard-step.active { display: block; }
|
| 97 |
-
|
| 98 |
-
.onboard-dots { display: flex; gap: 8px; justify-content: center; margin-top: 24px; }
|
| 99 |
.onboard-dot {
|
| 100 |
-
width:
|
| 101 |
-
background: #
|
| 102 |
-
}
|
| 103 |
-
.onboard-dot.active {
|
| 104 |
-
background: var(--cocoa-l);
|
| 105 |
-
transform: scale(1.3);
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
#btn-proceed {
|
| 109 |
-
background-color: #c89a6c !important;
|
| 110 |
-
color: #000000 !important;
|
| 111 |
-
border: none !important;
|
| 112 |
-
box-shadow: 0 4px 15px rgba(200, 154, 108, 0.3) !important;
|
| 113 |
-
transition: all 0.3s ease !important;
|
| 114 |
}
|
| 115 |
-
|
| 116 |
-
#btn-proceed:hover {
|
| 117 |
-
background-color: #d4b08a !important;
|
| 118 |
-
transform: scale(1.05) translateY(-2px) !important;
|
| 119 |
-
box-shadow: 0 8px 25px rgba(200, 154, 108, 0.4) !important;
|
| 120 |
-
}
|
| 121 |
-
|
| 122 |
-
#upload-bar {
|
| 123 |
-
background-color: #c89a6c !important;
|
| 124 |
-
}
|
| 125 |
-
|
| 126 |
/* Mobile responsive */
|
| 127 |
@media (max-width: 768px) {
|
| 128 |
-
main { grid-template-columns: 1fr !important; padding:
|
| 129 |
-
h1 { font-size: 2.
|
| 130 |
-
|
| 131 |
-
}
|
| 132 |
-
|
|
|
|
| 6 |
--t2: #a89f97;
|
| 7 |
--border: #2a2a2a;
|
| 8 |
}
|
|
|
|
| 9 |
body {
|
| 10 |
font-family: 'Montserrat', sans-serif;
|
| 11 |
+
background-color: #000000;
|
| 12 |
color: var(--t1);
|
|
|
|
|
|
|
| 13 |
}
|
|
|
|
| 14 |
.fade-in {
|
| 15 |
animation: fadeIn 0.4s ease-in-out forwards;
|
| 16 |
}
|
|
|
|
| 17 |
@keyframes fadeIn {
|
| 18 |
from {
|
| 19 |
opacity: 0;
|
| 20 |
transform: translateY(10px);
|
| 21 |
}
|
|
|
|
| 22 |
to {
|
| 23 |
opacity: 1;
|
| 24 |
transform: translateY(0);
|
| 25 |
}
|
| 26 |
}
|
| 27 |
+
/* Executive Overrides */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
.traffic-dynamics-card {
|
| 29 |
+
background-color: #0a0a0a !important;
|
| 30 |
border: 2px solid var(--cocoa) !important;
|
|
|
|
| 31 |
}
|
|
|
|
| 32 |
.traffic-dynamics-card:hover {
|
| 33 |
border-color: var(--cocoa-l) !important;
|
|
|
|
|
|
|
| 34 |
}
|
|
|
|
| 35 |
#dropzone {
|
| 36 |
transition: all 0.2s ease;
|
| 37 |
+
border-color: #2a2a2a;
|
|
|
|
| 38 |
}
|
|
|
|
| 39 |
#dropzone:hover {
|
| 40 |
border-color: var(--cocoa-l) !important;
|
| 41 |
+
background-color: #0a0a0a !important;
|
| 42 |
}
|
|
|
|
| 43 |
.core-badge {
|
| 44 |
background-color: var(--cocoa) !important;
|
| 45 |
+
color: var(--t1) !important;
|
|
|
|
| 46 |
}
|
|
|
|
| 47 |
/* Onboarding */
|
| 48 |
.onboard-overlay {
|
| 49 |
position: fixed; inset: 0; z-index: 9999;
|
| 50 |
+
background: rgba(0,0,0,0.92);
|
| 51 |
display: flex; align-items: center; justify-content: center;
|
|
|
|
| 52 |
}
|
|
|
|
| 53 |
.onboard-card {
|
| 54 |
+
background: #0a0a0a; border: 1px solid #2a2a2a;
|
| 55 |
+
border-radius: 16px; max-width: 440px; width: 90%;
|
| 56 |
padding: 40px 32px; text-align: center;
|
|
|
|
| 57 |
}
|
|
|
|
| 58 |
.onboard-step { display: none; }
|
| 59 |
.onboard-step.active { display: block; }
|
| 60 |
+
.onboard-dots { display: flex; gap: 6px; justify-content: center; margin-top: 20px; }
|
|
|
|
| 61 |
.onboard-dot {
|
| 62 |
+
width: 8px; height: 8px; border-radius: 50%;
|
| 63 |
+
background: #333; transition: background 0.2s;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
}
|
| 65 |
+
.onboard-dot.active { background: var(--cocoa-l); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
/* Mobile responsive */
|
| 67 |
@media (max-width: 768px) {
|
| 68 |
+
main { grid-template-columns: 1fr !important; padding: 16px !important; }
|
| 69 |
+
h1 { font-size: 2.2rem !important; }
|
| 70 |
+
}
|
|
|
|
|
|
frontend/initial.html
CHANGED
|
@@ -18,27 +18,24 @@
|
|
| 18 |
<meta charset="UTF-8">
|
| 19 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 20 |
<title>UrbanFlow</title>
|
| 21 |
-
<link rel="icon" type="image/
|
| 22 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 23 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 24 |
-
<link
|
| 25 |
-
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700;800;900&display=swap"
|
| 26 |
-
rel="stylesheet">
|
| 27 |
<link rel="stylesheet" href="/css/initial.css">
|
| 28 |
</head>
|
| 29 |
|
| 30 |
-
<body
|
| 31 |
-
class="bg-black text-white min-h-screen w-full flex flex-col items-center selection:bg-white selection:text-black">
|
| 32 |
|
| 33 |
<header class="mt-16 flex flex-col items-center flex-shrink-0 w-full z-10">
|
| 34 |
<img src="/assets/uf_rf.png" alt="UrbanFlow Logo" class="h-44 md:h-52 w-auto object-contain mb-3">
|
| 35 |
</header>
|
| 36 |
|
| 37 |
-
<main
|
| 38 |
-
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">
|
| 39 |
|
| 40 |
<div class="lg:col-span-7 flex flex-col justify-center xl:pl-10 pb-10 lg:pb-0">
|
| 41 |
-
<h1 class="text-5xl xl:text-[4.5rem] font-extrabold mb-4 leading-[1.1] tracking-tight"
|
|
|
|
| 42 |
Automated <br>Vision Intelligence
|
| 43 |
</h1>
|
| 44 |
<p class="font-bold mb-8 text-sm uppercase tracking-[0.2em] flex items-center" style="color:#a89f97">
|
|
@@ -54,21 +51,17 @@
|
|
| 54 |
</ul>
|
| 55 |
</div>
|
| 56 |
|
| 57 |
-
<div
|
| 58 |
-
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">
|
| 59 |
|
| 60 |
<!-- STEP: Modules -->
|
| 61 |
<div id="step-modules" class="w-full flex flex-col fade-in">
|
| 62 |
<h2 class="text-3xl font-bold mb-2 text-center" style="color:#f0ece6">UrbanFlow</h2>
|
| 63 |
<p class="text-[13px] font-medium mb-8 text-center" style="color:#a89f97">Select an analytical module to continue.</p>
|
| 64 |
-
|
| 65 |
<div class="flex justify-center w-full">
|
| 66 |
<div onclick="showStep('upload')"
|
| 67 |
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">
|
| 68 |
-
<div
|
| 69 |
-
|
| 70 |
-
style="background:#c89a6c;color:#000">
|
| 71 |
-
DEMO</div>
|
| 72 |
<i class="fa-solid fa-car-side text-4xl mb-4 block mx-auto" style="color:#c89a6c"></i>
|
| 73 |
<h3 class="font-bold text-lg mb-2 leading-tight" style="color:#f0ece6">Traffic <br>Dynamics</h3>
|
| 74 |
<p class="text-[10px] font-medium leading-relaxed" style="color:#a89f97">Vehicle counting, classification, and flow analysis for Indian roads.</p>
|
|
@@ -80,28 +73,24 @@
|
|
| 80 |
<div id="step-upload" class="hidden w-full flex flex-col fade-in">
|
| 81 |
<button onclick="showStep('modules')"
|
| 82 |
class="text-neutral-500 hover:text-white transition flex items-center text-xs font-bold uppercase tracking-widest mb-6 w-fit">
|
| 83 |
-
<i class="fa-solid fa-arrow-left mr-2"></i> Back
|
| 84 |
</button>
|
| 85 |
<h2 class="text-3xl font-bold mb-2 text-center" style="color:#f0ece6">Source Media</h2>
|
| 86 |
<p class="text-[13px] font-medium mb-8 text-center" style="color:#a89f97">Submit camera footage to begin traffic analysis.</p>
|
| 87 |
-
|
| 88 |
<input id="file-input" type="file" accept="video/*" class="hidden">
|
| 89 |
<div id="dropzone" onclick="document.getElementById('file-input').click()"
|
| 90 |
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">
|
| 91 |
-
<i
|
| 92 |
-
class="fa-solid fa-arrow-up-from-bracket text-4xl mb-5 block mx-auto transition" style="color:#a89f97"></i>
|
| 93 |
<span class="font-semibold text-lg mb-2 text-center block" style="color:#f0ece6">Drop or select a video file</span>
|
| 94 |
<span class="text-[10px] font-bold uppercase tracking-widest text-center block" style="color:#a89f97">Any standard video format accepted</span>
|
| 95 |
</div>
|
| 96 |
-
|
| 97 |
<div id="upload-progress-container" class="hidden mt-10 w-full">
|
| 98 |
<div class="flex justify-between text-[11px] font-bold uppercase tracking-widest mb-3" style="color:#f0ece6">
|
| 99 |
<span id="upload-text">Uploading...</span>
|
| 100 |
<span id="upload-percentage">0%</span>
|
| 101 |
</div>
|
| 102 |
<div class="w-full h-1 bg-neutral-900 rounded-full overflow-hidden">
|
| 103 |
-
<div id="upload-bar"
|
| 104 |
-
class="h-full w-0 transition-all duration-75 ease-linear rounded-full" style="background:#c89a6c"></div>
|
| 105 |
</div>
|
| 106 |
</div>
|
| 107 |
</div>
|
|
@@ -110,26 +99,24 @@
|
|
| 110 |
<div id="step-draw" class="hidden w-full flex flex-col fade-in">
|
| 111 |
<h2 class="text-3xl font-bold mb-2 text-center" style="color:#f0ece6">Spatial Boundary</h2>
|
| 112 |
<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>
|
| 113 |
-
|
| 114 |
-
<div
|
| 115 |
-
class="relative w-full aspect-video bg-neutral-950 rounded-3xl overflow-hidden cursor-crosshair mb-6">
|
| 116 |
<img id="frame-img" class="absolute inset-0 w-full h-full object-contain" style="display:none;">
|
| 117 |
<div id="frame-placeholder"
|
| 118 |
class="absolute inset-0 flex flex-col items-center justify-center text-neutral-800 pointer-events-none">
|
| 119 |
<i class="fa-solid fa-video text-4xl mb-3 opacity-30"></i>
|
| 120 |
-
<span class="font-bold text-[10px] uppercase tracking-widest opacity-50">Media Frame
|
| 121 |
-
Preview</span>
|
| 122 |
</div>
|
| 123 |
<canvas id="drawing-canvas" class="absolute inset-0 w-full h-full"></canvas>
|
| 124 |
</div>
|
| 125 |
-
|
| 126 |
<div class="flex flex-col items-center gap-3">
|
| 127 |
<button id="btn-proceed" onclick="startRun()"
|
| 128 |
-
class="w-fit px-16 py-3.5 rounded-full font-bold text-center text-sm shadow-lg"
|
|
|
|
| 129 |
Continue →
|
| 130 |
</button>
|
| 131 |
<button onclick="resetCanvas()"
|
| 132 |
-
class="text-[10px] font-bold uppercase tracking-widest text-slate-500 hover:text-white transition"
|
|
|
|
| 133 |
</div>
|
| 134 |
</div>
|
| 135 |
|
|
@@ -144,7 +131,7 @@
|
|
| 144 |
<div class="onboard-step active" data-step="0">
|
| 145 |
<i class="fa-solid fa-cloud-arrow-up text-4xl mb-4" style="color:var(--cocoa-l)"></i>
|
| 146 |
<h3 class="text-lg font-bold mb-2" style="color:#f0ece6">Upload a Traffic Video</h3>
|
| 147 |
-
<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>
|
| 148 |
</div>
|
| 149 |
<div class="onboard-step" data-step="1">
|
| 150 |
<i class="fa-solid fa-draw-polygon text-4xl mb-4" style="color:var(--cocoa-l)"></i>
|
|
@@ -153,7 +140,7 @@
|
|
| 153 |
</div>
|
| 154 |
<div class="onboard-step" data-step="2">
|
| 155 |
<i class="fa-solid fa-chart-line text-4xl mb-4" style="color:var(--cocoa-l)"></i>
|
| 156 |
-
<h3 class="text-lg font-bold mb-2" style="color:#f0ece6">Review Analytics & Export</h3>
|
| 157 |
<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>
|
| 158 |
</div>
|
| 159 |
<div class="onboard-dots">
|
|
@@ -162,12 +149,15 @@
|
|
| 162 |
<span class="onboard-dot"></span>
|
| 163 |
</div>
|
| 164 |
<div class="flex gap-3 justify-center mt-6">
|
| 165 |
-
<button onclick="closeOnboarding()"
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
</div>
|
| 168 |
</div>
|
| 169 |
</div>
|
| 170 |
|
| 171 |
</body>
|
| 172 |
-
|
| 173 |
</html>
|
|
|
|
| 18 |
<meta charset="UTF-8">
|
| 19 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 20 |
<title>UrbanFlow</title>
|
| 21 |
+
<link rel="icon" type="image/png" href="/assets/rf.png">
|
| 22 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 23 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 24 |
+
<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">
|
|
|
|
|
|
|
| 25 |
<link rel="stylesheet" href="/css/initial.css">
|
| 26 |
</head>
|
| 27 |
|
| 28 |
+
<body class="bg-black text-white min-h-screen w-full flex flex-col items-center selection:bg-white selection:text-black">
|
|
|
|
| 29 |
|
| 30 |
<header class="mt-16 flex flex-col items-center flex-shrink-0 w-full z-10">
|
| 31 |
<img src="/assets/uf_rf.png" alt="UrbanFlow Logo" class="h-44 md:h-52 w-auto object-contain mb-3">
|
| 32 |
</header>
|
| 33 |
|
| 34 |
+
<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">
|
|
|
|
| 35 |
|
| 36 |
<div class="lg:col-span-7 flex flex-col justify-center xl:pl-10 pb-10 lg:pb-0">
|
| 37 |
+
<h1 class="text-5xl xl:text-[4.5rem] font-extrabold mb-4 leading-[1.1] tracking-tight"
|
| 38 |
+
style="background:linear-gradient(110deg,#f0ece6 0%,#f0ece6 35%,#c89a6c 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text">
|
| 39 |
Automated <br>Vision Intelligence
|
| 40 |
</h1>
|
| 41 |
<p class="font-bold mb-8 text-sm uppercase tracking-[0.2em] flex items-center" style="color:#a89f97">
|
|
|
|
| 51 |
</ul>
|
| 52 |
</div>
|
| 53 |
|
| 54 |
+
<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">
|
|
|
|
| 55 |
|
| 56 |
<!-- STEP: Modules -->
|
| 57 |
<div id="step-modules" class="w-full flex flex-col fade-in">
|
| 58 |
<h2 class="text-3xl font-bold mb-2 text-center" style="color:#f0ece6">UrbanFlow</h2>
|
| 59 |
<p class="text-[13px] font-medium mb-8 text-center" style="color:#a89f97">Select an analytical module to continue.</p>
|
|
|
|
| 60 |
<div class="flex justify-center w-full">
|
| 61 |
<div onclick="showStep('upload')"
|
| 62 |
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">
|
| 63 |
+
<div class="absolute top-4 right-6 text-[9px] font-bold px-2.5 py-1 rounded-full uppercase tracking-wider"
|
| 64 |
+
style="background:#c89a6c;color:#000">DEMO</div>
|
|
|
|
|
|
|
| 65 |
<i class="fa-solid fa-car-side text-4xl mb-4 block mx-auto" style="color:#c89a6c"></i>
|
| 66 |
<h3 class="font-bold text-lg mb-2 leading-tight" style="color:#f0ece6">Traffic <br>Dynamics</h3>
|
| 67 |
<p class="text-[10px] font-medium leading-relaxed" style="color:#a89f97">Vehicle counting, classification, and flow analysis for Indian roads.</p>
|
|
|
|
| 73 |
<div id="step-upload" class="hidden w-full flex flex-col fade-in">
|
| 74 |
<button onclick="showStep('modules')"
|
| 75 |
class="text-neutral-500 hover:text-white transition flex items-center text-xs font-bold uppercase tracking-widest mb-6 w-fit">
|
| 76 |
+
<i class="fa-solid fa-arrow-left mr-2"></i> Back
|
| 77 |
</button>
|
| 78 |
<h2 class="text-3xl font-bold mb-2 text-center" style="color:#f0ece6">Source Media</h2>
|
| 79 |
<p class="text-[13px] font-medium mb-8 text-center" style="color:#a89f97">Submit camera footage to begin traffic analysis.</p>
|
|
|
|
| 80 |
<input id="file-input" type="file" accept="video/*" class="hidden">
|
| 81 |
<div id="dropzone" onclick="document.getElementById('file-input').click()"
|
| 82 |
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">
|
| 83 |
+
<i class="fa-solid fa-arrow-up-from-bracket text-4xl mb-5 block mx-auto transition" style="color:#a89f97"></i>
|
|
|
|
| 84 |
<span class="font-semibold text-lg mb-2 text-center block" style="color:#f0ece6">Drop or select a video file</span>
|
| 85 |
<span class="text-[10px] font-bold uppercase tracking-widest text-center block" style="color:#a89f97">Any standard video format accepted</span>
|
| 86 |
</div>
|
|
|
|
| 87 |
<div id="upload-progress-container" class="hidden mt-10 w-full">
|
| 88 |
<div class="flex justify-between text-[11px] font-bold uppercase tracking-widest mb-3" style="color:#f0ece6">
|
| 89 |
<span id="upload-text">Uploading...</span>
|
| 90 |
<span id="upload-percentage">0%</span>
|
| 91 |
</div>
|
| 92 |
<div class="w-full h-1 bg-neutral-900 rounded-full overflow-hidden">
|
| 93 |
+
<div id="upload-bar" class="h-full w-0 transition-all duration-75 ease-linear rounded-full" style="background:#c89a6c"></div>
|
|
|
|
| 94 |
</div>
|
| 95 |
</div>
|
| 96 |
</div>
|
|
|
|
| 99 |
<div id="step-draw" class="hidden w-full flex flex-col fade-in">
|
| 100 |
<h2 class="text-3xl font-bold mb-2 text-center" style="color:#f0ece6">Spatial Boundary</h2>
|
| 101 |
<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>
|
| 102 |
+
<div class="relative w-full aspect-video bg-neutral-950 rounded-3xl overflow-hidden cursor-crosshair mb-6">
|
|
|
|
|
|
|
| 103 |
<img id="frame-img" class="absolute inset-0 w-full h-full object-contain" style="display:none;">
|
| 104 |
<div id="frame-placeholder"
|
| 105 |
class="absolute inset-0 flex flex-col items-center justify-center text-neutral-800 pointer-events-none">
|
| 106 |
<i class="fa-solid fa-video text-4xl mb-3 opacity-30"></i>
|
| 107 |
+
<span class="font-bold text-[10px] uppercase tracking-widest opacity-50">Media Frame Preview</span>
|
|
|
|
| 108 |
</div>
|
| 109 |
<canvas id="drawing-canvas" class="absolute inset-0 w-full h-full"></canvas>
|
| 110 |
</div>
|
|
|
|
| 111 |
<div class="flex flex-col items-center gap-3">
|
| 112 |
<button id="btn-proceed" onclick="startRun()"
|
| 113 |
+
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"
|
| 114 |
+
style="background:#c89a6c;color:#000">
|
| 115 |
Continue →
|
| 116 |
</button>
|
| 117 |
<button onclick="resetCanvas()"
|
| 118 |
+
class="text-[10px] font-bold uppercase tracking-widest text-slate-500 hover:text-white transition"
|
| 119 |
+
style="background:none;border:none;">Reset Boundary</button>
|
| 120 |
</div>
|
| 121 |
</div>
|
| 122 |
|
|
|
|
| 131 |
<div class="onboard-step active" data-step="0">
|
| 132 |
<i class="fa-solid fa-cloud-arrow-up text-4xl mb-4" style="color:var(--cocoa-l)"></i>
|
| 133 |
<h3 class="text-lg font-bold mb-2" style="color:#f0ece6">Upload a Traffic Video</h3>
|
| 134 |
+
<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>
|
| 135 |
</div>
|
| 136 |
<div class="onboard-step" data-step="1">
|
| 137 |
<i class="fa-solid fa-draw-polygon text-4xl mb-4" style="color:var(--cocoa-l)"></i>
|
|
|
|
| 140 |
</div>
|
| 141 |
<div class="onboard-step" data-step="2">
|
| 142 |
<i class="fa-solid fa-chart-line text-4xl mb-4" style="color:var(--cocoa-l)"></i>
|
| 143 |
+
<h3 class="text-lg font-bold mb-2" style="color:#f0ece6">Review Analytics & Export</h3>
|
| 144 |
<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>
|
| 145 |
</div>
|
| 146 |
<div class="onboard-dots">
|
|
|
|
| 149 |
<span class="onboard-dot"></span>
|
| 150 |
</div>
|
| 151 |
<div class="flex gap-3 justify-center mt-6">
|
| 152 |
+
<button onclick="closeOnboarding()"
|
| 153 |
+
class="text-[10px] font-bold uppercase tracking-widest px-4 py-2 rounded-full"
|
| 154 |
+
style="color:#555;border:1px solid #222">Skip</button>
|
| 155 |
+
<button id="onboard-next" onclick="nextOnboardStep()"
|
| 156 |
+
class="text-[10px] font-bold uppercase tracking-widest px-6 py-2 rounded-full"
|
| 157 |
+
style="background:var(--cocoa);color:#f0ece6">Next</button>
|
| 158 |
</div>
|
| 159 |
</div>
|
| 160 |
</div>
|
| 161 |
|
| 162 |
</body>
|
|
|
|
| 163 |
</html>
|
frontend/js/initial.js
CHANGED
|
@@ -12,7 +12,6 @@ function showStep(name) {
|
|
| 12 |
if (name === 'upload') {
|
| 13 |
document.getElementById('upload-progress-container').classList.add('hidden');
|
| 14 |
document.getElementById('dropzone').classList.remove('hidden');
|
| 15 |
-
// Reset Progress Bar state for new uploads
|
| 16 |
document.getElementById('upload-bar').style.width = '0%';
|
| 17 |
document.getElementById('upload-percentage').innerText = '0%';
|
| 18 |
document.getElementById('upload-text').innerText = 'Uploading...';
|
|
@@ -31,8 +30,13 @@ if (fileInput) {
|
|
| 31 |
}
|
| 32 |
|
| 33 |
if (dropzone) {
|
| 34 |
-
dropzone.addEventListener('dragover', e => {
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
dropzone.addEventListener('drop', e => {
|
| 37 |
e.preventDefault();
|
| 38 |
dropzone.classList.remove('border-white', 'bg-neutral-950');
|
|
@@ -41,18 +45,18 @@ if (dropzone) {
|
|
| 41 |
}
|
| 42 |
|
| 43 |
let currentXHR = null;
|
|
|
|
| 44 |
function uploadFile(file) {
|
| 45 |
-
// Abort previous upload if it exists to prevent jitter/multiple requests
|
| 46 |
if (currentXHR) currentXHR.abort();
|
| 47 |
|
| 48 |
const dropzoneEl = document.getElementById('dropzone');
|
| 49 |
-
const prog
|
| 50 |
-
const bar
|
| 51 |
-
const pct
|
| 52 |
-
const txt
|
| 53 |
|
| 54 |
if (dropzoneEl) dropzoneEl.classList.add('hidden');
|
| 55 |
-
if (prog)
|
| 56 |
|
| 57 |
const form = new FormData();
|
| 58 |
form.append('file', file);
|
|
@@ -93,33 +97,36 @@ function uploadFile(file) {
|
|
| 93 |
.then(cfg => {
|
| 94 |
runConfig = cfg;
|
| 95 |
runConfig.conf = 0.12;
|
| 96 |
-
runConfig.iou
|
| 97 |
txt.innerText = 'Initialization Complete';
|
| 98 |
fileInput.value = '';
|
| 99 |
setTimeout(() => showStep('draw'), 800);
|
| 100 |
})
|
| 101 |
-
.catch(
|
| 102 |
txt.innerText = 'Metadata Failed';
|
| 103 |
txt.classList.add('text-red-500');
|
| 104 |
fileInput.value = '';
|
| 105 |
});
|
| 106 |
};
|
|
|
|
| 107 |
xhr.send(form);
|
| 108 |
}
|
| 109 |
|
| 110 |
-
// Draw Canvas
|
| 111 |
const canvas = document.getElementById('drawing-canvas');
|
| 112 |
-
const ctx
|
| 113 |
-
let points
|
| 114 |
-
let imgNatW
|
| 115 |
|
| 116 |
function loadFirstFrame() {
|
| 117 |
-
const img
|
| 118 |
const placeholder = document.getElementById('frame-placeholder');
|
| 119 |
-
|
| 120 |
img.onerror = () => {
|
| 121 |
console.error('Failed to load first frame');
|
| 122 |
-
placeholder
|
|
|
|
|
|
|
| 123 |
};
|
| 124 |
|
| 125 |
img.src = '/first-frame/' + videoId;
|
|
@@ -127,14 +134,14 @@ function loadFirstFrame() {
|
|
| 127 |
imgNatW = img.naturalWidth;
|
| 128 |
imgNatH = img.naturalHeight;
|
| 129 |
img.style.display = 'block';
|
| 130 |
-
placeholder.style.display = 'none';
|
| 131 |
initCanvas();
|
| 132 |
};
|
| 133 |
}
|
| 134 |
|
| 135 |
function initCanvas() {
|
| 136 |
if (canvas) {
|
| 137 |
-
canvas.width
|
| 138 |
canvas.height = canvas.offsetHeight;
|
| 139 |
}
|
| 140 |
}
|
|
@@ -145,10 +152,10 @@ if (canvas) {
|
|
| 145 |
canvas.addEventListener('mousedown', e => {
|
| 146 |
if (points.length >= 2) return;
|
| 147 |
const rect = canvas.getBoundingClientRect();
|
| 148 |
-
const cx
|
| 149 |
-
const cy
|
| 150 |
-
const rx
|
| 151 |
-
const ry
|
| 152 |
points.push({ cx, cy, rx: Math.round(rx), ry: Math.round(ry) });
|
| 153 |
drawDot(cx, cy);
|
| 154 |
if (points.length === 2) drawLine();
|
|
@@ -158,10 +165,10 @@ if (canvas) {
|
|
| 158 |
function drawDot(x, y) {
|
| 159 |
ctx.beginPath();
|
| 160 |
ctx.arc(x, y, 5, 0, Math.PI * 2);
|
| 161 |
-
ctx.fillStyle
|
| 162 |
ctx.fill();
|
| 163 |
ctx.strokeStyle = '#f0ece6';
|
| 164 |
-
ctx.lineWidth
|
| 165 |
ctx.stroke();
|
| 166 |
}
|
| 167 |
|
|
@@ -170,13 +177,13 @@ function drawLine() {
|
|
| 170 |
ctx.moveTo(points[0].cx, points[0].cy);
|
| 171 |
ctx.lineTo(points[1].cx, points[1].cy);
|
| 172 |
ctx.strokeStyle = '#c89a6c';
|
| 173 |
-
ctx.lineWidth
|
| 174 |
ctx.stroke();
|
| 175 |
}
|
| 176 |
|
| 177 |
function resetCanvas() {
|
| 178 |
points = [];
|
| 179 |
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 180 |
}
|
| 181 |
|
| 182 |
function startRun() {
|
|
@@ -184,25 +191,29 @@ function startRun() {
|
|
| 184 |
const line = [[points[0].rx, points[0].ry], [points[1].rx, points[1].ry]];
|
| 185 |
sessionStorage.setItem('funky_run', JSON.stringify({
|
| 186 |
video_id: videoId,
|
| 187 |
-
line:
|
| 188 |
-
config:
|
| 189 |
}));
|
| 190 |
window.location.href = '/';
|
| 191 |
}
|
| 192 |
|
|
|
|
| 193 |
let _obStep = 0;
|
|
|
|
| 194 |
function nextOnboardStep() {
|
| 195 |
_obStep++;
|
| 196 |
if (_obStep >= 3) { closeOnboarding(); return; }
|
| 197 |
document.querySelectorAll('.onboard-step').forEach((s, i) => s.classList.toggle('active', i === _obStep));
|
| 198 |
-
document.querySelectorAll('.onboard-dot').forEach((d, i)
|
| 199 |
if (_obStep === 2) document.getElementById('onboard-next').innerText = 'Get Started';
|
| 200 |
}
|
|
|
|
| 201 |
function closeOnboarding() {
|
| 202 |
document.getElementById('onboard-overlay').style.display = 'none';
|
| 203 |
}
|
| 204 |
|
| 205 |
// Show onboarding on every page load
|
| 206 |
-
document.addEventListener(
|
| 207 |
-
document.getElementById('onboard-overlay')
|
| 208 |
-
|
|
|
|
|
|
| 12 |
if (name === 'upload') {
|
| 13 |
document.getElementById('upload-progress-container').classList.add('hidden');
|
| 14 |
document.getElementById('dropzone').classList.remove('hidden');
|
|
|
|
| 15 |
document.getElementById('upload-bar').style.width = '0%';
|
| 16 |
document.getElementById('upload-percentage').innerText = '0%';
|
| 17 |
document.getElementById('upload-text').innerText = 'Uploading...';
|
|
|
|
| 30 |
}
|
| 31 |
|
| 32 |
if (dropzone) {
|
| 33 |
+
dropzone.addEventListener('dragover', e => {
|
| 34 |
+
e.preventDefault();
|
| 35 |
+
dropzone.classList.add('border-white', 'bg-neutral-950');
|
| 36 |
+
});
|
| 37 |
+
dropzone.addEventListener('dragleave', () => {
|
| 38 |
+
dropzone.classList.remove('border-white', 'bg-neutral-950');
|
| 39 |
+
});
|
| 40 |
dropzone.addEventListener('drop', e => {
|
| 41 |
e.preventDefault();
|
| 42 |
dropzone.classList.remove('border-white', 'bg-neutral-950');
|
|
|
|
| 45 |
}
|
| 46 |
|
| 47 |
let currentXHR = null;
|
| 48 |
+
|
| 49 |
function uploadFile(file) {
|
|
|
|
| 50 |
if (currentXHR) currentXHR.abort();
|
| 51 |
|
| 52 |
const dropzoneEl = document.getElementById('dropzone');
|
| 53 |
+
const prog = document.getElementById('upload-progress-container');
|
| 54 |
+
const bar = document.getElementById('upload-bar');
|
| 55 |
+
const pct = document.getElementById('upload-percentage');
|
| 56 |
+
const txt = document.getElementById('upload-text');
|
| 57 |
|
| 58 |
if (dropzoneEl) dropzoneEl.classList.add('hidden');
|
| 59 |
+
if (prog) prog.classList.remove('hidden');
|
| 60 |
|
| 61 |
const form = new FormData();
|
| 62 |
form.append('file', file);
|
|
|
|
| 97 |
.then(cfg => {
|
| 98 |
runConfig = cfg;
|
| 99 |
runConfig.conf = 0.12;
|
| 100 |
+
runConfig.iou = 0.60;
|
| 101 |
txt.innerText = 'Initialization Complete';
|
| 102 |
fileInput.value = '';
|
| 103 |
setTimeout(() => showStep('draw'), 800);
|
| 104 |
})
|
| 105 |
+
.catch(() => {
|
| 106 |
txt.innerText = 'Metadata Failed';
|
| 107 |
txt.classList.add('text-red-500');
|
| 108 |
fileInput.value = '';
|
| 109 |
});
|
| 110 |
};
|
| 111 |
+
|
| 112 |
xhr.send(form);
|
| 113 |
}
|
| 114 |
|
| 115 |
+
// Draw Canvas
|
| 116 |
const canvas = document.getElementById('drawing-canvas');
|
| 117 |
+
const ctx = canvas ? canvas.getContext('2d') : null;
|
| 118 |
+
let points = [];
|
| 119 |
+
let imgNatW = 0, imgNatH = 0;
|
| 120 |
|
| 121 |
function loadFirstFrame() {
|
| 122 |
+
const img = document.getElementById('frame-img');
|
| 123 |
const placeholder = document.getElementById('frame-placeholder');
|
| 124 |
+
|
| 125 |
img.onerror = () => {
|
| 126 |
console.error('Failed to load first frame');
|
| 127 |
+
if (placeholder) {
|
| 128 |
+
placeholder.innerHTML = '<i class="fa-solid fa-circle-exclamation text-4xl mb-3 opacity-50" style="color:#c89a6c"></i><span class="font-bold text-[10px] uppercase tracking-widest opacity-50 block mt-2">Frame Load Error</span>';
|
| 129 |
+
}
|
| 130 |
};
|
| 131 |
|
| 132 |
img.src = '/first-frame/' + videoId;
|
|
|
|
| 134 |
imgNatW = img.naturalWidth;
|
| 135 |
imgNatH = img.naturalHeight;
|
| 136 |
img.style.display = 'block';
|
| 137 |
+
if (placeholder) placeholder.style.display = 'none';
|
| 138 |
initCanvas();
|
| 139 |
};
|
| 140 |
}
|
| 141 |
|
| 142 |
function initCanvas() {
|
| 143 |
if (canvas) {
|
| 144 |
+
canvas.width = canvas.offsetWidth;
|
| 145 |
canvas.height = canvas.offsetHeight;
|
| 146 |
}
|
| 147 |
}
|
|
|
|
| 152 |
canvas.addEventListener('mousedown', e => {
|
| 153 |
if (points.length >= 2) return;
|
| 154 |
const rect = canvas.getBoundingClientRect();
|
| 155 |
+
const cx = e.clientX - rect.left;
|
| 156 |
+
const cy = e.clientY - rect.top;
|
| 157 |
+
const rx = (cx / canvas.width) * imgNatW;
|
| 158 |
+
const ry = (cy / canvas.height) * imgNatH;
|
| 159 |
points.push({ cx, cy, rx: Math.round(rx), ry: Math.round(ry) });
|
| 160 |
drawDot(cx, cy);
|
| 161 |
if (points.length === 2) drawLine();
|
|
|
|
| 165 |
function drawDot(x, y) {
|
| 166 |
ctx.beginPath();
|
| 167 |
ctx.arc(x, y, 5, 0, Math.PI * 2);
|
| 168 |
+
ctx.fillStyle = '#c89a6c';
|
| 169 |
ctx.fill();
|
| 170 |
ctx.strokeStyle = '#f0ece6';
|
| 171 |
+
ctx.lineWidth = 2;
|
| 172 |
ctx.stroke();
|
| 173 |
}
|
| 174 |
|
|
|
|
| 177 |
ctx.moveTo(points[0].cx, points[0].cy);
|
| 178 |
ctx.lineTo(points[1].cx, points[1].cy);
|
| 179 |
ctx.strokeStyle = '#c89a6c';
|
| 180 |
+
ctx.lineWidth = 3;
|
| 181 |
ctx.stroke();
|
| 182 |
}
|
| 183 |
|
| 184 |
function resetCanvas() {
|
| 185 |
points = [];
|
| 186 |
+
if (ctx) ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 187 |
}
|
| 188 |
|
| 189 |
function startRun() {
|
|
|
|
| 191 |
const line = [[points[0].rx, points[0].ry], [points[1].rx, points[1].ry]];
|
| 192 |
sessionStorage.setItem('funky_run', JSON.stringify({
|
| 193 |
video_id: videoId,
|
| 194 |
+
line: line,
|
| 195 |
+
config: runConfig
|
| 196 |
}));
|
| 197 |
window.location.href = '/';
|
| 198 |
}
|
| 199 |
|
| 200 |
+
// Onboarding
|
| 201 |
let _obStep = 0;
|
| 202 |
+
|
| 203 |
function nextOnboardStep() {
|
| 204 |
_obStep++;
|
| 205 |
if (_obStep >= 3) { closeOnboarding(); return; }
|
| 206 |
document.querySelectorAll('.onboard-step').forEach((s, i) => s.classList.toggle('active', i === _obStep));
|
| 207 |
+
document.querySelectorAll('.onboard-dot').forEach((d, i) => d.classList.toggle('active', i === _obStep));
|
| 208 |
if (_obStep === 2) document.getElementById('onboard-next').innerText = 'Get Started';
|
| 209 |
}
|
| 210 |
+
|
| 211 |
function closeOnboarding() {
|
| 212 |
document.getElementById('onboard-overlay').style.display = 'none';
|
| 213 |
}
|
| 214 |
|
| 215 |
// Show onboarding on every page load
|
| 216 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 217 |
+
const onboard = document.getElementById('onboard-overlay');
|
| 218 |
+
if (onboard) onboard.style.display = 'flex';
|
| 219 |
+
});
|