Spaces:
Sleeping
Sleeping
| <html lang="en" class="light"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>CrashVision AI - Accident Detection</title> | |
| <meta name="apple-mobile-web-app-capable" content="yes"> | |
| <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> | |
| <meta name="apple-mobile-web-app-title" content="CrashVision"> | |
| <meta name="mobile-web-app-capable" content="yes"> | |
| <meta name="theme-color" content="#1e1b4b"> | |
| <!-- FIX: Using Absolute Pathing to the Custom "Ultimate Logo Finder" Route --> | |
| <link rel="icon" type="image/png" sizes="192x192" href="/app_logo?v=7"> | |
| <link rel="apple-touch-icon" href="/app_logo?v=7"> | |
| <link rel="manifest" href='data:application/manifest+json;utf8,{"name":"CrashVision AI","short_name":"CrashVision","start_url":"./","display":"standalone","background_color":"#F4F4F9","theme_color":"#1e1b4b","icons":[{"src":"/app_logo?v=7","sizes":"192x192","type":"image/png"},{"src":"/app_logo?v=7","sizes":"512x512","type":"image/png"}]}'> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script> | |
| tailwind.config = { | |
| darkMode: 'class', | |
| theme: { | |
| extend: { | |
| fontFamily: { sans: ['-apple-system', 'BlinkMacSystemFont', 'San Francisco', 'Inter', 'sans-serif'], }, | |
| animation: { 'blob': 'blob 10s infinite', 'pulse-glow': 'pulse-glow 3s infinite', }, | |
| keyframes: { | |
| blob: { | |
| '0%': { transform: 'translate(0px, 0px) scale(1)' }, | |
| '33%': { transform: 'translate(40px, -60px) scale(1.2)' }, | |
| '66%': { transform: 'translate(-30px, 30px) scale(0.8)' }, | |
| '100%': { transform: 'translate(0px, 0px) scale(1)' }, | |
| }, | |
| 'pulse-glow': { | |
| '0%, 100%': { opacity: 0.6, transform: 'scale(1)' }, | |
| '50%': { opacity: 1, transform: 'scale(1.05)' }, | |
| } | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| body { background-color: #F4F4F9; -webkit-font-smoothing: antialiased; transition: background-color 0.5s ease, background-image 0.5s ease; } | |
| .dark body { background-color: #151518; background-image: linear-gradient(135deg, #151518 0%, #2a1f3d 50%, #101012 100%); background-attachment: fixed; } | |
| .modal-bg { background-color: #F4F4F9; } | |
| .dark .modal-bg { background-color: #151518; background-image: linear-gradient(135deg, #151518 0%, #2a1f3d 50%, #101012 100%); background-attachment: fixed; } | |
| .ios-glass { background: rgba(255, 255, 255, 0.45); backdrop-filter: blur(40px) saturate(200%); border: 1.5px solid rgba(255, 255, 255, 0.9); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5), 0 8px 32px rgba(0, 0, 0, 0.08); transition: all 0.5s ease; } | |
| .dark .ios-glass { background: rgba(20, 20, 25, 0.35); border: 1px solid rgba(255, 255, 255, 0.15); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05), 0 8px 32px rgba(0, 0, 0, 0.6); backdrop-filter: blur(40px) saturate(200%); } | |
| .ios-glass-inner { background: rgba(255, 255, 255, 0.5); border: 1px solid rgba(255, 255, 255, 0.8); transition: all 0.5s ease; } | |
| .dark .ios-glass-inner { background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.12); } | |
| .bg-mesh { background-size: 40px 40px; background-image: linear-gradient(to right, rgba(100, 100, 150, 0.06) 1px, transparent 1px), linear-gradient(to bottom, rgba(100, 100, 150, 0.06) 1px, transparent 1px); } | |
| .dark .bg-mesh { background-image: linear-gradient(to right, rgba(255, 255, 255, 0.03) 1px, transparent 1px), linear-gradient(to bottom, rgba(255, 255, 255, 0.03) 1px, transparent 1px); } | |
| ::-webkit-scrollbar { width: 6px; } | |
| ::-webkit-scrollbar-track { background: transparent; } | |
| ::-webkit-scrollbar-thumb { background: rgba(156, 163, 175, 0.5); border-radius: 10px; } | |
| .dark ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); } | |
| .predict-input { width: 100%; padding: 0.75rem 1rem; border-radius: 0.75rem; font-size: 0.875rem; font-weight: 600; outline: none; transition: all 0.3s ease; } | |
| @keyframes jiggle { 0%, 100% { transform: scale3d(1, 1, 1); } 30% { transform: scale3d(1.25, 0.75, 1); } 40% { transform: scale3d(0.75, 1.25, 1); } 50% { transform: scale3d(1.15, 0.85, 1); } 65% { transform: scale3d(0.95, 1.05, 1); } 75% { transform: scale3d(1.05, 0.95, 1); } } | |
| .animate-jiggle { animation: jiggle 1.5s ease-in-out infinite; transform-origin: bottom; } | |
| </style> | |
| </head> | |
| <body class="relative min-h-screen transition-colors duration-500 overflow-x-hidden"> | |
| <div id="appSplashScreen" class="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-[#F4F4F9] dark:bg-[#151518] transition-all duration-700 ease-in-out"> | |
| <div class="mb-8"> | |
| <svg class="w-24 h-24 text-indigo-700 dark:text-purple-400 drop-shadow-xl" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M3 22L9 8H15L21 22" stroke-width="1.5" /> | |
| <path d="M12 22V17M12 13V11M12 9V8" stroke-width="1.5" /> | |
| <path d="M9.5 16L10.5 13H13.5L14.5 16V17.5H9.5V16Z" stroke-width="1.5" fill="currentColor" fill-opacity="0.1" /> | |
| <path d="M10.5 13L11 10.5H13L13.5 13" stroke-width="1.5" /> | |
| <path d="M8 9H6V11" stroke-width="2" /> | |
| <path d="M16 9H18V11" stroke-width="2" /> | |
| <path d="M8 19H6V17" stroke-width="2" /> | |
| <path d="M16 19H18V17" stroke-width="2" /> | |
| <circle cx="12" cy="14" r="1.5" fill="currentColor" stroke="none" class="animate-pulse" /> | |
| </svg> | |
| </div> | |
| <h1 class="text-4xl font-black tracking-tight text-indigo-950 dark:text-white drop-shadow-sm">CrashVision AI</h1> | |
| <p class="text-xs font-black uppercase tracking-[0.3em] text-indigo-500 mt-3">Initializing Systems</p> | |
| <div class="mt-8 flex gap-3"> | |
| <div class="w-3 h-3 rounded-md bg-fuchsia-500 animate-jiggle" style="animation-delay: 0s;"></div> | |
| <div class="w-3 h-3 rounded-md bg-indigo-500 animate-jiggle" style="animation-delay: 0.15s;"></div> | |
| <div class="w-3 h-3 rounded-md bg-cyan-500 animate-jiggle" style="animation-delay: 0.3s;"></div> | |
| </div> | |
| </div> | |
| <!-- Background Effects --> | |
| <div class="fixed inset-0 z-0 bg-mesh pointer-events-none"></div> | |
| <div class="fixed inset-0 z-0 overflow-hidden pointer-events-none"> | |
| <div class="absolute top-[5%] left-[10%] w-[35rem] h-[35rem] bg-indigo-400 dark:bg-purple-500/30 rounded-full mix-blend-multiply dark:mix-blend-screen filter blur-[120px] dark:blur-[140px] opacity-20 dark:opacity-30 animate-blob"></div> | |
| <div class="absolute top-[20%] right-[10%] w-[30rem] h-[30rem] bg-fuchsia-300 dark:bg-indigo-500/20 rounded-full mix-blend-multiply dark:mix-blend-screen filter blur-[120px] opacity-20 dark:opacity-25 animate-blob" style="animation-delay: 2s"></div> | |
| <div class="absolute bottom-[10%] left-[25%] w-[35rem] h-[35rem] bg-blue-300 dark:bg-fuchsia-500/20 rounded-full mix-blend-multiply dark:mix-blend-screen filter blur-[120px] dark:blur-[140px] opacity-20 dark:opacity-30 animate-blob" style="animation-delay: 4s"></div> | |
| </div> | |
| <!-- Theme Toggle --> | |
| <div class="fixed top-6 right-6 sm:top-8 sm:right-8 z-[70]"> | |
| <button id="themeToggle" class="w-10 h-10 rounded-full flex items-center justify-center hover:scale-105 active:scale-95 transition-all shadow-sm text-indigo-900 dark:text-purple-100 cursor-pointer bg-white/30 dark:bg-white/10 backdrop-blur-xl border border-white/40 dark:border-white/20"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" /></svg> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 hidden dark:block drop-shadow-md" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-2.25l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" /></svg> | |
| </button> | |
| </div> | |
| <!-- Emergency Dispatch Banner --> | |
| <div id="emergencyBanner" class="hidden fixed top-0 left-0 w-full bg-red-600 text-white py-3 px-4 z-50 flex items-center justify-center gap-4 animate-pulse shadow-lg shadow-red-500/50"> | |
| <i class="fas fa-exclamation-triangle text-xl"></i> | |
| <span id="emergencyText" class="font-black uppercase tracking-widest text-sm sm:text-base">Collision Detected. Automated SOS Dispatched.</span> | |
| <i class="fas fa-exclamation-triangle text-xl"></i> | |
| </div> | |
| <header class="relative z-40 pt-10 sm:pt-16 pb-6 sm:pb-8 flex flex-col items-center justify-center mt-2 sm:mt-6"> | |
| <div class="mb-4 sm:mb-5 flex items-center justify-center group transition-all duration-500 hover:scale-110 drop-shadow-xl"> | |
| <svg class="w-20 h-20 sm:w-16 sm:h-16 z-10 text-indigo-700 dark:text-purple-400 transition-colors duration-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M3 22L9 8H15L21 22" stroke-width="1.5" /> | |
| <path d="M12 22V17M12 13V11M12 9V8" stroke-width="1.5" /> | |
| <path d="M9.5 16L10.5 13H13.5L14.5 16V17.5H9.5V16Z" stroke-width="1.5" fill="currentColor" fill-opacity="0.1" /> | |
| <path d="M10.5 13L11 10.5H13L13.5 13" stroke-width="1.5" /> | |
| <path d="M8 9H6V11" stroke-width="2" /> | |
| <path d="M16 9H18V11" stroke-width="2" /> | |
| <path d="M8 19H6V17" stroke-width="2" /> | |
| <path d="M16 19H18V17" stroke-width="2" /> | |
| <circle cx="12" cy="14" r="1.5" fill="currentColor" stroke="none" class="animate-pulse" /> | |
| </svg> | |
| </div> | |
| <div class="text-center"> | |
| <h1 class="text-3xl sm:text-5xl font-black tracking-tight leading-tight text-indigo-700 dark:text-purple-400 drop-shadow-sm transition-colors duration-500">CrashVision AI</h1> | |
| <span class="text-[9px] sm:text-xs text-indigo-800/60 dark:text-purple-200 font-black tracking-[0.4em] uppercase mt-2 sm:mt-3 block opacity-90 transition-colors duration-500">Road Safety Analytics</span> | |
| </div> | |
| </header> | |
| <main class="relative z-10 container mx-auto pb-12 px-4 sm:px-6 lg:px-8 max-w-6xl"> | |
| <div class="w-full transition-all duration-500"> | |
| <!-- SECTION 1: HOME AREA --> | |
| <div id="uploadSection" class="flex flex-col gap-8 sm:gap-6"> | |
| <div class="ios-glass rounded-[32px] sm:rounded-[40px] px-6 py-10 sm:p-10 md:p-16 text-center shadow-lg"> | |
| <div class="max-w-3xl mx-auto"> | |
| <h2 class="text-2xl sm:text-3xl font-extrabold mb-2 sm:mb-4 tracking-tight text-indigo-950 dark:text-white">Incident Analysis</h2> | |
| <p class="text-indigo-800/60 dark:text-purple-100 mb-6 sm:mb-10 text-xs sm:text-base leading-relaxed font-semibold">Input surveillance streams or connect a live IP camera.</p> | |
| <div class="grid grid-cols-2 gap-3 sm:gap-6 justify-center"> | |
| <div class="relative group cursor-pointer h-full" onclick="openIpCamModal()"> | |
| <div class="block w-full h-full py-10 px-4 sm:p-10 border-2 border-dashed border-rose-400/30 dark:border-rose-400/40 rounded-[20px] sm:rounded-[32px] ios-glass-inner hover:bg-rose-50/70 dark:hover:bg-rose-900/20 transition-all group-hover:border-rose-500/50 dark:group-hover:border-rose-400/60 flex flex-col items-center justify-center"> | |
| <div class="w-16 h-16 sm:w-20 sm:h-20 bg-rose-50 dark:bg-rose-500/20 rounded-[14px] sm:rounded-3xl flex items-center justify-center mb-3 sm:mb-4 group-hover:scale-110 transition-transform duration-500 shadow-sm border border-rose-100 dark:border-rose-400/30"><i class="fas fa-satellite-dish text-2xl sm:text-3xl text-rose-600 dark:text-rose-300"></i></div> | |
| <span class="block text-sm sm:text-xl font-black text-rose-900 dark:text-rose-100 mb-0 sm:mb-1 leading-tight">Live IP</span> | |
| <span class="block text-[9px] sm:text-xs text-rose-600/60 dark:text-rose-300 font-bold uppercase tracking-widest hidden sm:block">Connect RTSP</span> | |
| </div> | |
| </div> | |
| <form id="uploadForm" class="relative group cursor-pointer h-full"> | |
| <input type="file" id="fileInput" name="file" accept="image/png, image/jpeg, image/jpg, video/mp4" class="hidden"> | |
| <label for="fileInput" class="block w-full h-full py-10 px-4 sm:p-10 border-2 border-dashed border-indigo-400/30 dark:border-purple-400/40 rounded-[20px] sm:rounded-[32px] ios-glass-inner hover:bg-white/70 dark:hover:bg-white/5 transition-all cursor-pointer group-hover:border-indigo-500/50 dark:group-hover:border-purple-300/60 flex flex-col items-center justify-center"> | |
| <div class="w-16 h-16 sm:w-20 sm:h-20 bg-indigo-50 dark:bg-white/10 rounded-[14px] sm:rounded-3xl flex items-center justify-center mb-3 sm:mb-4 group-hover:scale-110 transition-transform duration-500 shadow-sm border border-indigo-100 dark:border-white/20"><i class="fas fa-arrow-up-from-bracket text-2xl sm:text-3xl text-indigo-600 dark:text-purple-200"></i></div> | |
| <span class="block text-sm sm:text-xl font-black text-indigo-900 dark:text-white mb-0 sm:mb-1 leading-tight">Select Media</span> | |
| <span class="block text-[9px] sm:text-xs text-indigo-600/60 dark:text-purple-200 font-bold uppercase tracking-widest hidden sm:block">MP4 • JPEG • PNG</span> | |
| </label> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="relative group cursor-pointer mt-2 mb-2" onclick="openPredictModal()"> | |
| <div class="ios-glass p-7 sm:p-6 rounded-[32px] hover:-translate-y-1 transition-transform duration-300 shadow-sm border border-cyan-400/30 hover:border-cyan-500/50 dark:border-cyan-500/20 dark:hover:border-cyan-400/40 flex items-center justify-between bg-gradient-to-r from-transparent to-cyan-50/30 dark:to-cyan-900/10"> | |
| <div class="flex items-center gap-5"> | |
| <div class="w-16 h-16 sm:w-14 sm:h-14 rounded-2xl bg-cyan-100 dark:bg-cyan-500/20 flex items-center justify-center shadow-sm group-hover:scale-110 transition-transform duration-300 border border-cyan-200/50 dark:border-cyan-400/30"><i class="fas fa-chart-line text-cyan-600 dark:text-cyan-300 text-3xl sm:text-2xl"></i></div> | |
| <div class="text-left"><h3 class="text-xl sm:text-xl font-black tracking-tight text-indigo-950 dark:text-white mb-0">Accident Risk Predictor</h3><p class="text-[10px] sm:text-[10px] font-bold text-indigo-800/60 dark:text-purple-200 uppercase tracking-widest mt-1">Run Tabular AI Simulations</p></div> | |
| </div> | |
| <div class="w-12 h-12 sm:w-10 sm:h-10 rounded-full bg-indigo-900/5 dark:bg-white/5 flex items-center justify-center group-hover:bg-cyan-100 dark:group-hover:bg-cyan-500/30 transition-colors"><i class="fas fa-arrow-right text-indigo-400 dark:text-purple-300 group-hover:text-cyan-600 dark:group-hover:text-cyan-100 text-base sm:text-sm"></i></div> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6 text-left"> | |
| <div class="ios-glass p-6 rounded-[32px] hover:-translate-y-1 transition-transform duration-300 shadow-sm cursor-default"> | |
| <div class="w-12 h-12 rounded-2xl bg-indigo-100 dark:bg-white/10 flex items-center justify-center mb-5 border border-indigo-200/50 dark:border-white/10 shadow-sm"><i class="fas fa-eye text-indigo-600 dark:text-purple-300 text-xl"></i></div> | |
| <h4 class="text-sm font-black uppercase tracking-wider text-indigo-950 dark:text-purple-50 mb-2">Spatial Engine</h4> | |
| <p class="text-xs text-indigo-800/70 dark:text-purple-100 leading-relaxed font-bold">YOLOv8 XAI Tracking overlays.</p> | |
| </div> | |
| <div class="ios-glass p-6 rounded-[32px] hover:-translate-y-1 transition-transform duration-300 shadow-sm cursor-default"> | |
| <div class="w-12 h-12 rounded-2xl bg-fuchsia-100 dark:bg-white/10 flex items-center justify-center mb-5 border border-fuchsia-200/50 dark:border-white/10 shadow-sm"><i class="fas fa-layer-group text-fuchsia-600 dark:text-fuchsia-300 text-xl"></i></div> | |
| <h4 class="text-sm font-black uppercase tracking-wider text-indigo-950 dark:text-purple-50 mb-2">Temporal Engine</h4> | |
| <p class="text-xs text-indigo-800/70 dark:text-purple-100 leading-relaxed font-bold">3D-CNN models physical motion.</p> | |
| </div> | |
| <div class="ios-glass p-6 rounded-[32px] hover:-translate-y-1 transition-transform duration-300 shadow-sm cursor-default"> | |
| <div class="w-12 h-12 rounded-2xl bg-rose-100 dark:bg-white/10 flex items-center justify-center mb-5 border border-rose-200/50 dark:border-white/10 shadow-sm"><i class="fas fa-bolt text-rose-600 dark:text-rose-300 text-xl"></i></div> | |
| <h4 class="text-sm font-black uppercase tracking-wider text-indigo-950 dark:text-purple-50 mb-2">Automated SOS</h4> | |
| <p class="text-xs text-indigo-800/70 dark:text-purple-100 leading-relaxed font-bold">HTTP Push Dispatch via ntfy.sh.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- SECTION 2: RESULTS DASHBOARD --> | |
| <div id="results" class="hidden ios-glass rounded-[40px] overflow-hidden shadow-2xl"> | |
| <div class="grid grid-cols-1 lg:grid-cols-12 gap-0"> | |
| <!-- Left: Media View --> | |
| <div class="lg:col-span-7 p-8 md:p-12 border-b lg:border-b-0 lg:border-r border-indigo-200/50 dark:border-white/10"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h3 class="text-xs font-black uppercase tracking-[0.3em] text-indigo-800/50 dark:text-purple-200">XAI Analytics Feed</h3> | |
| <span class="px-4 py-1.5 text-[10px] font-black uppercase tracking-widest bg-white/60 dark:bg-white/10 rounded-full text-indigo-900 dark:text-white border border-indigo-200/50 dark:border-white/20 shadow-sm">Channel: CV-PRIME</span> | |
| </div> | |
| <div class="rounded-[32px] overflow-hidden shadow-2xl border border-white/50 dark:border-white/20 relative bg-black aspect-video flex items-center justify-center group"> | |
| <img id="previewImg" src="" class="w-full h-full object-contain z-10 hidden" alt="Video View"> | |
| <div id="snapshotContainer" class="absolute inset-0 z-30 hidden bg-black flex items-center justify-center"> | |
| <img id="snapshotImg" src="" class="w-full h-full object-contain"> | |
| <div class="absolute top-6 right-6 flex gap-3"> | |
| <button onclick="fullscreenSnapshot()" class="w-8 h-8 rounded-full bg-white/20 hover:bg-white/40 border border-white/40 text-white flex items-center justify-center transition-all shadow-lg backdrop-blur-md"><i class="fas fa-expand"></i></button> | |
| <button onclick="closeSnapshot()" class="w-8 h-8 rounded-full bg-red-500/80 hover:bg-red-500 border border-red-400 text-white flex items-center justify-center transition-all shadow-lg backdrop-blur-md"><i class="fas fa-times"></i></button> | |
| </div> | |
| <div class="absolute top-6 left-6 z-20"> | |
| <span class="bg-black/60 backdrop-blur-xl text-white text-[10px] px-3 py-1.5 rounded-xl font-black uppercase tracking-widest border border-white/20 flex items-center gap-2"><i class="fas fa-camera text-blue-400"></i> CAPTURED SNAPSHOT</span> | |
| </div> | |
| </div> | |
| <div id="liveBadgeContainer" class="absolute top-6 left-6 z-20 transition-opacity duration-300 group-hover:opacity-0"> | |
| <span id="liveBadge" class="bg-black/60 backdrop-blur-xl text-white text-[10px] px-3 py-1.5 rounded-xl font-black uppercase tracking-widest border border-white/20 flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-red-500 animate-pulse shadow-[0_0_8px_red]"></span> LIVE AI FEED</span> | |
| </div> | |
| <!-- Integrated Video Controls Overlay (Timeline Only) --> | |
| <div id="playerControlsOverlay" class="hidden absolute bottom-0 left-0 w-full p-4 sm:p-6 bg-gradient-to-t from-black/90 via-black/50 to-transparent z-40 transition-opacity duration-300 opacity-0 group-hover:opacity-100 flex-col gap-3"> | |
| <div class="w-full h-1.5 bg-white/30 rounded-full cursor-pointer relative" onclick="seekVideo(event)" id="progressBarContainer"> | |
| <div id="progressFill" class="absolute top-0 left-0 h-full bg-indigo-500 rounded-full pointer-events-none transition-all duration-300" style="width: 0%"></div> | |
| </div> | |
| <div class="flex items-center justify-center w-full mt-2"> | |
| <div class="flex items-center gap-6"> | |
| <button onclick="skipVideo(-5)" id="skipBackBtn" class="text-white hover:text-indigo-400 transition-colors"><i class="fas fa-backward"></i></button> | |
| <button onclick="togglePlayPause()" id="playBtn" class="w-10 h-10 rounded-full bg-indigo-600 text-white flex items-center justify-center hover:scale-105 transition-transform shadow-lg"><i id="playPauseIcon" class="fas fa-pause text-sm"></i></button> | |
| <button onclick="skipVideo(5)" id="skipFwdBtn" class="text-white hover:text-indigo-400 transition-colors"><i class="fas fa-forward"></i></button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Media Controls (Moved back outside player) --> | |
| <div class="flex justify-center gap-4 mt-8" id="mediaControls"> | |
| <button onclick="takeSnapshot()" class="px-5 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest text-indigo-900 dark:text-white bg-white/50 dark:bg-white/10 border border-indigo-200/50 dark:border-white/20 hover:scale-105 transition-transform shadow-sm flex items-center gap-2"><i class="fas fa-camera"></i> Snapshot</button> | |
| <button onclick="goTracking()" id="tBtn" class="px-5 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest text-white hover:bg-rose-500/80 transition-all bg-rose-600 shadow-lg shadow-rose-500/50 flex items-center gap-2 opacity-50"><i class="fas fa-brain"></i> AI Tracking</button> | |
| <button onclick="goOriginal()" id="oBtn" class="px-5 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest text-indigo-900 dark:text-white bg-white/50 dark:bg-white/10 border border-indigo-200/50 dark:border-white/20 hover:scale-105 transition-transform shadow-sm flex items-center gap-2"><i class="fas fa-play"></i> Raw Feed</button> | |
| </div> | |
| <div class="grid grid-cols-3 gap-2 sm:gap-5 mt-6 sm:mt-10"> | |
| <div class="ios-glass-inner p-3 sm:p-5 rounded-2xl sm:rounded-3xl flex flex-col items-center text-center gap-1 hover:bg-white/60 dark:hover:bg-white/10 transition-colors"> | |
| <i class="fas fa-location-dot text-rose-500 text-lg sm:text-xl mb-1 sm:mb-2"></i> | |
| <p class="text-[8px] sm:text-[9px] uppercase tracking-widest text-indigo-800/50 dark:text-purple-200 font-black">Location</p> | |
| <p id="vLoc" class="text-[10px] sm:text-xs font-black text-indigo-950 dark:text-white w-full tracking-tight leading-tight text-center px-1 break-words">--</p> | |
| </div> | |
| <div class="ios-glass-inner p-3 sm:p-5 rounded-2xl sm:rounded-3xl flex flex-col items-center text-center gap-1 hover:bg-white/60 dark:hover:bg-white/10 transition-colors"> | |
| <i class="fas fa-cloud-bolt text-cyan-500 text-lg sm:text-xl mb-1 sm:mb-2"></i> | |
| <p class="text-[8px] sm:text-[9px] uppercase tracking-widest text-indigo-800/50 dark:text-purple-200 font-black">Conditions</p> | |
| <p id="vWx" class="text-[10px] sm:text-xs font-black text-indigo-950 dark:text-white w-full tracking-tight leading-tight text-center break-words">--</p> | |
| </div> | |
| <div class="ios-glass-inner p-3 sm:p-5 rounded-2xl sm:rounded-3xl flex flex-col items-center text-center gap-1 hover:bg-white/60 dark:hover:bg-white/10 transition-colors"> | |
| <i class="fas fa-stopwatch text-indigo-500 dark:text-purple-300 text-lg sm:text-xl mb-1 sm:mb-2"></i> | |
| <p class="text-[8px] sm:text-[9px] uppercase tracking-widest text-indigo-800/50 dark:text-purple-200 font-black">Timeline</p> | |
| <p id="vTime" class="text-[10px] sm:text-xs font-black text-indigo-950 dark:text-white w-full tracking-tight leading-tight text-center break-words">--</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right: AI Diagnostics --> | |
| <div class="lg:col-span-5 p-8 md:p-12 bg-white/20 dark:bg-black/20 flex flex-col justify-between relative"> | |
| <div> | |
| <h3 class="text-xs font-black uppercase tracking-[0.3em] text-indigo-800/50 dark:text-purple-200 mb-8">AI Diagnostics</h3> | |
| <div id="statusCard" class="ios-glass-inner p-8 rounded-[32px] mb-8 shadow-sm dark:shadow-lg relative overflow-hidden border-l-4 border-indigo-500 transition-colors duration-500"> | |
| <div class="flex items-start justify-between"> | |
| <div> | |
| <div id="statusBadge" class="inline-flex items-center gap-2 px-3 py-1.5 rounded-xl text-[10px] font-black mb-4 uppercase tracking-[0.2em] border border-indigo-200 shadow-sm bg-white text-indigo-600"> | |
| <i id="statusIcon" class="fas fa-spinner fa-spin"></i> | |
| <span id="labelText">Scanning Stream...</span> | |
| </div> | |
| <h4 class="text-5xl font-black tracking-tighter text-indigo-950 dark:text-white mb-1"><span id="confVal">0</span>%</h4> | |
| <p class="text-indigo-800/60 dark:text-purple-200 text-[10px] font-black uppercase tracking-widest">Ensemble Confidence</p> | |
| </div> | |
| <div id="severityContainer" class="text-center bg-white dark:bg-white/10 px-4 py-3 rounded-2xl shadow-sm border border-indigo-200 dark:border-white/20"> | |
| <span class="block text-[9px] uppercase tracking-widest text-indigo-500 dark:text-indigo-300 font-black mb-1">Severity</span> | |
| <span id="severityLevel" class="font-black text-[10px] uppercase text-indigo-500 animate-pulse">--</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="space-y-7 mb-10"> | |
| <!-- ALPR Section (Shows dynamically when plates are detected) --> | |
| <div id="alprSection" class="hidden"> | |
| <div class="flex justify-between items-end mb-3"> | |
| <span class="font-black text-[11px] uppercase tracking-widest text-indigo-900 dark:text-purple-50 flex items-center gap-3"> | |
| <div class="w-8 h-8 rounded-xl bg-white/60 dark:bg-white/10 flex items-center justify-center border border-indigo-200/50 dark:border-white/20 shadow-sm"><i class="fas fa-car-side text-indigo-600 dark:text-fuchsia-300 text-xs"></i></div> | |
| ALPR (License Plates) | |
| </span> | |
| </div> | |
| <div id="alprContainer" class="w-full flex flex-wrap gap-3"></div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between items-end mb-3"> | |
| <span class="font-black text-[11px] uppercase tracking-widest text-indigo-900 dark:text-purple-50 flex items-center gap-3"> | |
| <div class="w-8 h-8 rounded-xl bg-white/60 dark:bg-white/10 flex items-center justify-center border border-indigo-200/50 dark:border-white/20 shadow-sm"><i class="fas fa-layer-group text-indigo-600 dark:text-fuchsia-300 text-xs"></i></div> | |
| Temporal (3D-CNN) | |
| </span> | |
| <span id="rcnnVal" class="font-black text-sm text-indigo-900 dark:text-white">0%</span> | |
| </div> | |
| <div class="w-full bg-indigo-200/50 dark:bg-white/20 rounded-full h-3 overflow-hidden border border-white/40 dark:border-white/10"><div id="rcnnBar" class="bg-gradient-to-r from-indigo-500 to-fuchsia-500 dark:from-purple-500 dark:to-fuchsia-400 h-full rounded-full transition-all duration-1000 ease-out shadow-sm" style="width: 0%"></div></div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between items-end mb-3"> | |
| <span class="font-black text-[11px] uppercase tracking-widest text-indigo-900 dark:text-purple-50 flex items-center gap-3"> | |
| <div class="w-8 h-8 rounded-xl bg-white/60 dark:bg-white/10 flex items-center justify-center border border-indigo-200/50 dark:border-white/20 shadow-sm"><i class="fas fa-crosshairs text-amber-600 dark:text-amber-400 text-xs"></i></div> | |
| Spatial (YOLOv8 Max) | |
| </span> | |
| <span id="cnnVal" class="font-black text-sm text-indigo-900 dark:text-white">0%</span> | |
| </div> | |
| <div class="w-full bg-indigo-200/50 dark:bg-white/20 rounded-full h-3 overflow-hidden border border-white/40 dark:border-white/10"><div id="cnnBar" class="bg-gradient-to-r from-amber-400 to-orange-500 dark:from-fuchsia-400 dark:to-orange-400 h-full rounded-full transition-all duration-1000 ease-out shadow-sm" style="width: 0%"></div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-8 flex gap-4"> | |
| <button onclick="resetApp()" class="flex-1 py-4 px-6 bg-indigo-950 dark:bg-purple-600 text-white rounded-2xl font-black uppercase tracking-widest transition-all active:scale-95 text-xs flex justify-center items-center gap-3 shadow-xl dark:shadow-purple-500/40 hover:opacity-90 dark:hover:bg-purple-500"> | |
| <i class="fas fa-stop text-white"></i> End Session | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- IP Camera Modal --> | |
| <div id="ipCamModal" class="hidden fixed inset-0 z-[60] flex items-center justify-center bg-indigo-950/60 dark:bg-black/80 backdrop-blur-md p-4"> | |
| <div class="ios-glass p-8 rounded-[32px] max-w-md w-full shadow-2xl relative"> | |
| <h3 class="text-2xl font-black text-indigo-950 dark:text-white mb-2">Connect IP Camera</h3> | |
| <p class="text-sm font-bold text-indigo-800/60 dark:text-purple-200 mb-6">Enter the RTSP or HTTP stream URL for your surveillance camera.</p> | |
| <input type="text" id="ipCamUrl" placeholder="e.g., http://192.168.1.5:8080/video" class="w-full px-5 py-4 rounded-xl border border-indigo-200 dark:border-white/20 bg-white/50 dark:bg-white/5 text-indigo-900 dark:text-white font-semibold outline-none focus:border-indigo-500 dark:focus:border-purple-400 mb-6 placeholder-indigo-300 dark:placeholder-white/30"> | |
| <div class="flex gap-4"> | |
| <button onclick="closeIpCamModal()" class="flex-1 py-3 rounded-xl font-black uppercase tracking-widest text-xs bg-white dark:bg-white/10 text-indigo-900 dark:text-white border border-indigo-200 dark:border-white/20 shadow-sm hover:bg-gray-50 dark:hover:bg-white/20 transition-all">Cancel</button> | |
| <button onclick="connectIpCam()" class="flex-1 py-3 rounded-xl font-black uppercase tracking-widest text-xs bg-rose-600 text-white shadow-lg shadow-rose-500/30 hover:bg-rose-500 transition-all">Connect</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Predictive Analysis Form Modal --> | |
| <div id="predictiveModal" class="hidden fixed inset-0 z-[60] modal-bg overflow-y-auto transition-colors duration-500"> | |
| <div class="fixed inset-0 z-0 bg-mesh pointer-events-none"></div> | |
| <div class="fixed inset-0 z-0 overflow-hidden pointer-events-none"> | |
| <div class="absolute top-[10%] left-[20%] w-[35rem] h-[35rem] bg-cyan-400 dark:bg-cyan-500/20 rounded-full mix-blend-multiply dark:mix-blend-screen filter blur-[120px] dark:blur-[140px] opacity-20 dark:opacity-30 animate-blob"></div> | |
| <div class="absolute bottom-[20%] right-[10%] w-[30rem] h-[30rem] bg-indigo-300 dark:bg-indigo-500/20 rounded-full mix-blend-multiply dark:mix-blend-screen filter blur-[120px] opacity-20 dark:opacity-25 animate-blob" style="animation-delay: 2s"></div> | |
| </div> | |
| <button onclick="closePredictModal()" class="fixed top-6 left-6 sm:top-8 sm:left-8 z-[70] w-10 h-10 rounded-full flex items-center justify-center hover:scale-105 active:scale-95 transition-all shadow-sm text-indigo-900 dark:text-purple-100 cursor-pointer bg-white/30 dark:bg-white/10 backdrop-blur-xl border border-white/40 dark:border-white/20"><i class="fas fa-arrow-left text-sm"></i></button> | |
| <div class="relative z-10 min-h-screen flex flex-col"> | |
| <div class="pt-20 sm:pt-24 pb-8 px-4 flex flex-col items-center justify-center text-center"> | |
| <div class="mb-4 sm:mb-6 flex items-center justify-center group transition-all duration-500 hover:scale-110 drop-shadow-xl"> | |
| <div class="w-14 h-14 sm:w-16 sm:h-16 rounded-2xl bg-cyan-100 dark:bg-cyan-500/20 flex items-center justify-center border border-cyan-200/50 dark:border-cyan-400/30 shadow-sm relative overflow-hidden"><i class="fas fa-chart-line text-cyan-600 dark:text-cyan-300 text-2xl sm:text-3xl z-10"></i></div> | |
| </div> | |
| <h2 class="text-3xl sm:text-4xl font-black tracking-tight text-indigo-950 dark:text-white mb-3">Accident Risk Predictor</h2> | |
| <p class="text-indigo-800/60 dark:text-purple-200 text-xs sm:text-sm font-semibold max-w-xl mx-auto px-4">Predict the probability of an accident using our trained Tabular AI model.</p> | |
| </div> | |
| <div class="container mx-auto px-4 pb-16 max-w-4xl flex-grow"> | |
| <div class="ios-glass p-6 sm:p-10 rounded-[32px] sm:rounded-[40px] shadow-2xl w-full"> | |
| <div id="predictiveFormView"> | |
| <form id="tabularForm" onsubmit="submitPredictiveData(event)"> | |
| <div class="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6"> | |
| <div class="ios-glass-inner p-5 sm:p-6 rounded-[24px] shadow-sm border border-white/50 dark:border-white/10 hover:bg-white/50 dark:hover:bg-white/5 transition-colors group"> | |
| <div class="flex items-center gap-3 mb-4 border-b border-indigo-100/50 dark:border-white/10 pb-3"><div class="w-8 h-8 rounded-xl bg-cyan-100 dark:bg-cyan-500/20 flex items-center justify-center border border-cyan-200/50 dark:border-cyan-400/30 group-hover:scale-110 transition-transform"><i class="fas fa-cloud-sun text-cyan-600 dark:text-cyan-300 text-xs"></i></div><h4 class="font-black text-[11px] uppercase tracking-widest text-indigo-900 dark:text-purple-200">Conditions</h4></div> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-[10px] font-black uppercase tracking-widest text-indigo-900/70 dark:text-purple-200/70 mb-1.5 ml-1">Weather</label> | |
| <select id="tab_weather" class="predict-input bg-white/60 dark:bg-black/20 border border-indigo-200/60 dark:border-white/10 text-indigo-950 dark:text-white"> | |
| <option value="">Select...</option> | |
| <option value="Clear">Clear</option> | |
| <option value="Rainy">Rainy</option> | |
| <option value="Foggy">Foggy</option> | |
| <option value="Snowy">Snowy</option> | |
| <option value="Stormy">Stormy</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-[10px] font-black uppercase tracking-widest text-indigo-900/70 dark:text-purple-200/70 mb-1.5 ml-1">Light Cond.</label> | |
| <select id="tab_light" class="predict-input bg-white/60 dark:bg-black/20 border border-indigo-200/60 dark:border-white/10 text-indigo-950 dark:text-white"> | |
| <option value="">Select...</option> | |
| <option value="Daylight">Daylight</option> | |
| <option value="Artificial Light">Artificial Light</option> | |
| <option value="No Light">No Light</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-[10px] font-black uppercase tracking-widest text-indigo-900/70 dark:text-purple-200/70 mb-1.5 ml-1">Time of Day</label> | |
| <select id="tab_time" class="predict-input bg-white/60 dark:bg-black/20 border border-indigo-200/60 dark:border-white/10 text-indigo-950 dark:text-white"> | |
| <option value="">Select...</option> | |
| <option value="Morning">Morning</option> | |
| <option value="Afternoon">Afternoon</option> | |
| <option value="Evening">Evening</option> | |
| <option value="Night">Night</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="ios-glass-inner p-5 sm:p-6 rounded-[24px] shadow-sm border border-white/50 dark:border-white/10 hover:bg-white/50 dark:hover:bg-white/5 transition-colors group"> | |
| <div class="flex items-center gap-3 mb-4 border-b border-indigo-100/50 dark:border-white/10 pb-3"><div class="w-8 h-8 rounded-xl bg-rose-100 dark:bg-rose-500/20 flex items-center justify-center border border-rose-200/50 dark:border-rose-400/30 group-hover:scale-110 transition-transform"><i class="fas fa-road text-rose-600 dark:text-rose-300 text-xs"></i></div><h4 class="font-black text-[11px] uppercase tracking-widest text-indigo-900 dark:text-purple-200">Road Specs</h4></div> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-[10px] font-black uppercase tracking-widest text-indigo-900/70 dark:text-purple-200/70 mb-1.5 ml-1">Road Type</label> | |
| <select id="tab_road_type" class="predict-input bg-white/60 dark:bg-black/20 border border-indigo-200/60 dark:border-white/10 text-indigo-950 dark:text-white"> | |
| <option value="">Select...</option> | |
| <option value="City Road">City Road</option> | |
| <option value="Highway">Highway</option> | |
| <option value="Rural Road">Rural Road</option> | |
| <option value="Mountain Road">Mountain Road</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-[10px] font-black uppercase tracking-widest text-indigo-900/70 dark:text-purple-200/70 mb-1.5 ml-1">Surface</label> | |
| <select id="tab_road_cond" class="predict-input bg-white/60 dark:bg-black/20 border border-indigo-200/60 dark:border-white/10 text-indigo-950 dark:text-white"> | |
| <option value="">Select...</option> | |
| <option value="Dry">Dry</option> | |
| <option value="Wet">Wet</option> | |
| <option value="Icy">Icy</option> | |
| <option value="Under Construction">Under Construction</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-[10px] font-black uppercase tracking-widest text-indigo-900/70 dark:text-purple-200/70 mb-1.5 ml-1">Speed Limit</label> | |
| <input type="number" id="tab_speed" placeholder="e.g. 60" min="10" max="250" class="predict-input bg-white/60 dark:bg-black/20 border border-indigo-200/60 dark:border-white/10 text-indigo-950 dark:text-white"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="ios-glass-inner p-5 sm:p-6 rounded-[24px] shadow-sm border border-white/50 dark:border-white/10 hover:bg-white/50 dark:hover:bg-white/5 transition-colors group"> | |
| <div class="flex items-center gap-3 mb-4 border-b border-indigo-100/50 dark:border-white/10 pb-3"><div class="w-8 h-8 rounded-xl bg-amber-100 dark:bg-amber-500/20 flex items-center justify-center border border-amber-200/50 dark:border-amber-400/30 group-hover:scale-110 transition-transform"><i class="fas fa-car-side text-amber-600 dark:text-amber-300 text-xs"></i></div><h4 class="font-black text-[11px] uppercase tracking-widest text-indigo-900 dark:text-purple-200">Traffic Data</h4></div> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-[10px] font-black uppercase tracking-widest text-indigo-900/70 dark:text-purple-200/70 mb-1.5 ml-1">Density (0-5)</label> | |
| <input type="number" id="tab_density" placeholder="e.g. 2" min="0" max="5" class="predict-input bg-white/60 dark:bg-black/20 border border-indigo-200/60 dark:border-white/10 text-indigo-950 dark:text-white"> | |
| </div> | |
| <div> | |
| <label class="block text-[10px] font-black uppercase tracking-widest text-indigo-900/70 dark:text-purple-200/70 mb-1.5 ml-1">Vehicles Inv.</label> | |
| <input type="number" id="tab_vehicles" placeholder="e.g. 2" min="1" max="20" class="predict-input bg-white/60 dark:bg-black/20 border border-indigo-200/60 dark:border-white/10 text-indigo-950 dark:text-white"> | |
| </div> | |
| <div> | |
| <label class="block text-[10px] font-black uppercase tracking-widest text-indigo-900/70 dark:text-purple-200/70 mb-1.5 ml-1">Vehicle Type</label> | |
| <select id="tab_vehicle" class="predict-input bg-white/60 dark:bg-black/20 border border-indigo-200/60 dark:border-white/10 text-indigo-950 dark:text-white"> | |
| <option value="">Select...</option> | |
| <option value="Car">Car</option> | |
| <option value="Truck">Truck</option> | |
| <option value="Bus">Bus</option> | |
| <option value="Motorcycle">Motorcycle</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="ios-glass-inner p-5 sm:p-6 rounded-[24px] shadow-sm border border-white/50 dark:border-white/10 hover:bg-white/50 dark:hover:bg-white/5 transition-colors group"> | |
| <div class="flex items-center gap-3 mb-4 border-b border-indigo-100/50 dark:border-white/10 pb-3"><div class="w-8 h-8 rounded-xl bg-fuchsia-100 dark:bg-fuchsia-500/20 flex items-center justify-center border border-fuchsia-200/50 dark:border-fuchsia-400/30 group-hover:scale-110 transition-transform"><i class="fas fa-user text-fuchsia-600 dark:text-fuchsia-300 text-xs"></i></div><h4 class="font-black text-[11px] uppercase tracking-widest text-indigo-900 dark:text-purple-200">Driver Profile</h4></div> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-[10px] font-black uppercase tracking-widest text-indigo-900/70 dark:text-purple-200/70 mb-1.5 ml-1">Driver Age</label> | |
| <input type="number" id="tab_age" placeholder="e.g. 35" min="16" max="99" class="predict-input bg-white/60 dark:bg-black/20 border border-indigo-200/60 dark:border-white/10 text-indigo-950 dark:text-white"> | |
| </div> | |
| <div> | |
| <label class="block text-[10px] font-black uppercase tracking-widest text-indigo-900/70 dark:text-purple-200/70 mb-1.5 ml-1">Experience (Yrs)</label> | |
| <input type="number" id="tab_exp" placeholder="e.g. 5" min="0" max="80" class="predict-input bg-white/60 dark:bg-black/20 border border-indigo-200/60 dark:border-white/10 text-indigo-950 dark:text-white"> | |
| </div> | |
| <div> | |
| <label class="block text-[10px] font-black uppercase tracking-widest text-indigo-900/70 dark:text-purple-200/70 mb-1.5 ml-1">Drunk</label> | |
| <select id="tab_alcohol" class="predict-input bg-white/60 dark:bg-black/20 border border-indigo-200/60 dark:border-white/10 text-indigo-950 dark:text-white"> | |
| <option value="">Select...</option> | |
| <option value="No">No</option> | |
| <option value="Yes">Yes</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-8 pt-6 border-t border-indigo-200/50 dark:border-white/10 flex justify-end"> | |
| <button type="submit" id="predictBtn" class="w-full sm:w-auto px-8 py-4 rounded-2xl font-black uppercase tracking-widest text-xs bg-cyan-600 text-white shadow-lg shadow-cyan-500/40 hover:bg-cyan-500 active:scale-95 transition-all flex items-center justify-center gap-3"> | |
| <span>Run Risk Simulation</span><i id="predictSpinner" class="fas fa-circle-notch fa-spin hidden"></i> | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| <div id="predictiveResultView" class="hidden text-center animate-fade-in py-6"> | |
| <h3 class="text-xs font-black uppercase tracking-[0.3em] text-indigo-800/50 dark:text-purple-200 mb-6">Prediction Outcome</h3> | |
| <div class="flex flex-col items-center justify-center gap-6"> | |
| <div id="riskStatusCard" class="ios-glass-inner p-10 rounded-[32px] shadow-sm dark:shadow-lg relative overflow-hidden w-full max-w-sm border border-indigo-200/50 dark:border-white/20"> | |
| <div id="riskBadge" class="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl text-xs font-black mb-6 uppercase tracking-[0.2em] border shadow-sm"><i id="riskIcon" class="fas fa-check-circle"></i><span id="riskLabelText">Low Risk</span></div> | |
| <h4 class="text-6xl font-black tracking-tighter text-indigo-950 dark:text-white mb-2 drop-shadow-sm" id="riskLevelResult">--</h4> | |
| <p class="text-indigo-800/60 dark:text-purple-200 text-[10px] font-black uppercase tracking-widest mt-4">Predicted Accident Severity</p> | |
| </div> | |
| <div class="flex gap-4 max-w-sm w-full mt-4"> | |
| <button onclick="resetPredictModal()" class="flex-1 py-4 px-6 bg-cyan-600 text-white rounded-2xl font-black uppercase tracking-widest transition-all active:scale-95 text-xs flex justify-center items-center gap-2 shadow-xl shadow-cyan-500/30 hover:bg-cyan-500"><i class="fas fa-redo text-white"></i> Retry</button> | |
| <button onclick="closePredictModal()" class="flex-1 py-4 px-6 bg-indigo-950 dark:bg-white/10 text-white rounded-2xl font-black uppercase tracking-widest transition-all active:scale-95 text-xs flex justify-center items-center gap-2 shadow-xl hover:opacity-90 border border-indigo-200/50 dark:border-white/20">Dashboard</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const themeToggleBtn = document.getElementById('themeToggle'); | |
| const htmlElement = document.documentElement; | |
| if (localStorage.theme === 'dark') { htmlElement.classList.add('dark'); } | |
| else { htmlElement.classList.remove('dark'); localStorage.theme = 'light'; } | |
| themeToggleBtn.addEventListener('click', () => { | |
| htmlElement.classList.toggle('dark'); | |
| if (htmlElement.classList.contains('dark')) { | |
| localStorage.theme = 'dark'; | |
| } else { | |
| localStorage.theme = 'light'; | |
| } | |
| }); | |
| window.addEventListener('load', () => { | |
| setTimeout(() => { | |
| const splash = document.getElementById('appSplashScreen'); | |
| if(splash) { | |
| splash.style.opacity = '0'; | |
| splash.style.transform = 'scale(1.05)'; | |
| setTimeout(() => { | |
| splash.style.display = 'none'; | |
| }, 700); | |
| } | |
| }, 1800); | |
| }); | |
| let streamId = null; | |
| let poller = null; | |
| let isPaused = false; | |
| function openIpCamModal() { document.getElementById('ipCamModal').classList.remove('hidden'); } | |
| function closeIpCamModal() { document.getElementById('ipCamModal').classList.add('hidden'); } | |
| function openPredictModal() { | |
| document.getElementById('predictiveModal').classList.remove('hidden'); | |
| document.body.style.overflow = 'hidden'; | |
| } | |
| function closePredictModal() { | |
| document.getElementById('predictiveModal').classList.add('hidden'); | |
| document.body.style.overflow = ''; | |
| document.getElementById('tabularForm').reset(); | |
| resetPredictModal(); | |
| } | |
| function resetPredictModal() { | |
| document.getElementById('predictiveResultView').classList.add('hidden'); | |
| document.getElementById('predictiveFormView').classList.remove('hidden'); | |
| document.getElementById('riskStatusCard').className = 'ios-glass-inner p-10 rounded-[32px] shadow-sm dark:shadow-lg relative overflow-hidden w-full max-w-sm border border-indigo-200/50 dark:border-white/20'; | |
| } | |
| async function connectIpCam() { | |
| const url = document.getElementById('ipCamUrl').value.trim(); | |
| if(!url) return; | |
| closeIpCamModal(); | |
| document.getElementById('uploadSection').classList.add('hidden'); | |
| document.getElementById('results').classList.remove('hidden'); | |
| document.getElementById('labelText').innerText = "Resolving URL..."; | |
| try { | |
| await startSession({url: url}, '/init_stream'); | |
| } catch (err) { | |
| alert("Failed to connect to the camera. Please check the URL."); | |
| resetApp(); | |
| } | |
| } | |
| document.getElementById('fileInput').addEventListener('change', async (e) => { | |
| if(!e.target.files.length) return; | |
| const fd = new FormData(); | |
| fd.append('file', e.target.files[0]); | |
| document.getElementById('uploadSection').classList.add('hidden'); | |
| document.getElementById('results').classList.remove('hidden'); | |
| document.getElementById('labelText').innerText = "Uploading Media..."; | |
| startSession(fd, '/init_upload', true); | |
| }); | |
| async function startSession(payload, endpoint, isFormData=false) { | |
| const opts = { method: 'POST' }; | |
| if(isFormData) { opts.body = payload; } | |
| else { opts.headers = {'Content-Type': 'application/json'}; opts.body = JSON.stringify(payload); } | |
| const res = await fetch(endpoint, opts); | |
| if (!res.ok) throw new Error("Backend error"); | |
| const data = await res.json(); | |
| streamId = data.stream_id; | |
| if(document.getElementById('vLoc')) document.getElementById('vLoc').innerText = data.location || "--"; | |
| if(document.getElementById('vWx')) document.getElementById('vWx').innerText = data.weather || "--"; | |
| if(document.getElementById('vTime')) document.getElementById('vTime').innerText = data.time || "--"; | |
| document.getElementById('previewImg').classList.remove('hidden'); | |
| document.getElementById('playerControlsOverlay').classList.remove('hidden'); | |
| document.getElementById('playerControlsOverlay').classList.add('flex'); | |
| document.getElementById('oBtn').classList.add('opacity-50'); | |
| document.getElementById('tBtn').classList.remove('opacity-50'); | |
| document.getElementById('liveBadge').innerHTML = '<span class="w-2 h-2 rounded-full bg-red-500 animate-pulse shadow-[0_0_8px_red]"></span> LIVE AI FEED'; | |
| document.getElementById('previewImg').src = `/video_feed/${streamId}?loc=${encodeURIComponent(data.location)}&t=${Date.now()}`; | |
| isPaused = false; | |
| document.getElementById('playPauseIcon').className = 'fas fa-pause text-sm'; | |
| if(poller) clearInterval(poller); | |
| poller = setInterval(update, 1000); | |
| } | |
| async function goTracking() { | |
| document.getElementById('oBtn').classList.add('opacity-50'); | |
| document.getElementById('tBtn').classList.remove('opacity-50'); | |
| document.getElementById('liveBadge').innerHTML = '<span class="w-2 h-2 rounded-full bg-red-500 animate-pulse shadow-[0_0_8px_red]"></span> LIVE AI FEED'; | |
| await fetch(`/video_control/${streamId}`, { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({action: 'toggle_tracking', track: true}) | |
| }); | |
| } | |
| async function goOriginal() { | |
| document.getElementById('tBtn').classList.add('opacity-50'); | |
| document.getElementById('oBtn').classList.remove('opacity-50'); | |
| document.getElementById('liveBadge').innerHTML = '<span class="w-2 h-2 rounded-full bg-blue-500 shadow-[0_0_8px_blue]"></span> RAW FEED'; | |
| await fetch(`/video_control/${streamId}`, { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({action: 'toggle_tracking', track: false}) | |
| }); | |
| } | |
| async function togglePlayPause() { | |
| if(!streamId) return; | |
| isPaused = !isPaused; | |
| document.getElementById('playPauseIcon').className = isPaused ? 'fas fa-play text-sm ml-0.5' : 'fas fa-pause text-sm'; | |
| await fetch(`/video_control/${streamId}`, { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({action: isPaused ? 'pause' : 'play'}) | |
| }); | |
| } | |
| async function skipVideo(seconds) { | |
| if(!streamId) return; | |
| await fetch(`/video_control/${streamId}`, { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({action: 'skip', value: seconds}) | |
| }); | |
| } | |
| async function seekVideo(event) { | |
| if(!streamId) return; | |
| const container = document.getElementById('progressBarContainer'); | |
| const rect = container.getBoundingClientRect(); | |
| const pos = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width)); | |
| await fetch(`/video_control/${streamId}`, { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({action: 'seek', value: pos}) | |
| }); | |
| } | |
| function takeSnapshot() { | |
| const feed = document.getElementById('previewImg'); | |
| if(feed && feed.src) { | |
| const c = document.createElement('canvas'); | |
| c.width = feed.naturalWidth || feed.width || 640; | |
| c.height = feed.naturalHeight || feed.height || 480; | |
| c.getContext('2d').drawImage(feed, 0, 0, c.width, c.height); | |
| document.getElementById('snapshotImg').src = c.toDataURL('image/jpeg'); | |
| document.getElementById('snapshotContainer').classList.remove('hidden'); | |
| document.getElementById('previewImg').classList.add('hidden'); | |
| document.getElementById('liveBadgeContainer').classList.add('hidden'); | |
| document.getElementById('playerControlsOverlay').classList.add('hidden'); | |
| } | |
| } | |
| function closeSnapshot() { | |
| document.getElementById('snapshotContainer').classList.add('hidden'); | |
| document.getElementById('previewImg').classList.remove('hidden'); | |
| document.getElementById('liveBadgeContainer').classList.remove('hidden'); | |
| document.getElementById('playerControlsOverlay').classList.remove('hidden'); | |
| } | |
| function fullscreenSnapshot() { | |
| const imgSrc = document.getElementById('snapshotImg').src; | |
| if(imgSrc) { | |
| const w = window.open(''); | |
| w.document.write(`<html><body style="margin:0;background:#000;display:flex;justify-content:center;align-items:center;height:100vh;"><img src="${imgSrc}" style="max-width:100%;max-height:100%;object-fit:contain;"></body></html>`); | |
| } | |
| } | |
| async function update() { | |
| if(!streamId) return; | |
| try { | |
| const res = await fetch(`/stream_status/${streamId}`); | |
| const data = await res.json(); | |
| if(!streamId) return; // Prevent ghost updates if ended mid-fetch | |
| if(!data || Object.keys(data).length === 0) return; | |
| document.getElementById('labelText').innerText = data.label || "Monitoring..."; | |
| document.getElementById('confVal').innerText = data.confidence || 0; | |
| document.getElementById('cnnBar').style.width = `${data.cnn || 0}%`; | |
| document.getElementById('cnnVal').innerText = `${data.cnn || 0}%`; | |
| document.getElementById('rcnnBar').style.width = `${data.rcnn || 0}%`; | |
| document.getElementById('rcnnVal').innerText = `${data.rcnn || 0}%`; | |
| document.getElementById('severityLevel').innerText = data.severity || "--"; | |
| if (data.progress !== undefined) { | |
| if (data.progress >= 100) { document.getElementById('progressFill').style.width = `100%`; } | |
| else { document.getElementById('progressFill').style.width = `${data.progress}%`; } | |
| } | |
| const statusCard = document.getElementById('statusCard'); | |
| const statusIcon = document.getElementById('statusIcon'); | |
| const emergencyBanner = document.getElementById('emergencyBanner'); | |
| const emergencyText = document.getElementById('emergencyText'); | |
| if(data.is_accident || data.final_decision) { | |
| statusCard.className = "ios-glass-inner p-8 rounded-[32px] mb-8 shadow-sm dark:shadow-lg relative overflow-hidden border-l-4 border-rose-500 bg-rose-50/50 dark:bg-rose-900/20 transition-colors duration-500"; | |
| statusIcon.className = "fas fa-exclamation-triangle text-rose-600"; | |
| if(data.severity) { | |
| const sevLevel = data.severity.toUpperCase(); | |
| emergencyText.innerText = `${sevLevel} COLLISION DETECTED. AUTOMATED SOS DISPATCHED.`; | |
| emergencyBanner.classList.remove('hidden'); | |
| } | |
| } else { | |
| statusCard.className = "ios-glass-inner p-8 rounded-[32px] mb-8 shadow-sm dark:shadow-lg relative overflow-hidden border-l-4 border-indigo-500 transition-colors duration-500"; | |
| statusIcon.className = "fas fa-shield-check text-indigo-600"; | |
| emergencyBanner.classList.add('hidden'); | |
| } | |
| const alprSec = document.getElementById('alprSection'); | |
| const alprCont = document.getElementById('alprContainer'); | |
| if(data.plates && data.plates.length > 0) { | |
| alprSec.classList.remove('hidden'); | |
| alprCont.innerHTML = data.plates.map(p => ` | |
| <div class="flex items-center gap-2 bg-white/80 dark:bg-black/40 px-3 py-1.5 rounded-lg border border-indigo-100 dark:border-white/10 shadow-sm"> | |
| <span class="font-mono font-bold text-xs text-indigo-900 dark:text-white">${p.text}</span> | |
| </div> | |
| `).join(''); | |
| } else { | |
| alprSec.classList.add('hidden'); | |
| alprCont.innerHTML = ''; | |
| } | |
| } catch(e) {} | |
| } | |
| // Fix: Instantly unloads the video and resets UI without ghosts | |
| function resetApp() { | |
| if(poller) clearInterval(poller); | |
| streamId = null; | |
| document.getElementById('previewImg').src = ""; | |
| document.getElementById('uploadSection').classList.remove('hidden'); | |
| document.getElementById('results').classList.add('hidden'); | |
| document.getElementById('emergencyBanner').classList.add('hidden'); | |
| document.getElementById('alprSection').classList.add('hidden'); | |
| document.getElementById('progressFill').style.width = "0%"; | |
| document.getElementById('playerControlsOverlay').classList.add('hidden'); | |
| document.getElementById('playerControlsOverlay').classList.remove('flex'); | |
| // Wipe Form and Upload fields completely | |
| document.getElementById('fileInput').value = ""; | |
| document.getElementById('uploadForm').reset(); | |
| document.getElementById('ipCamUrl').value = ""; | |
| // Reset Diagnostics View Default State | |
| document.getElementById('confVal').innerText = "0"; | |
| document.getElementById('severityLevel').innerText = "--"; | |
| document.getElementById('cnnBar').style.width = "0%"; | |
| document.getElementById('rcnnBar').style.width = "0%"; | |
| document.getElementById('cnnVal').innerText = "0%"; | |
| document.getElementById('rcnnVal').innerText = "0%"; | |
| document.getElementById('labelText').innerText = "Scanning Stream..."; | |
| const statusCard = document.getElementById('statusCard'); | |
| if(statusCard) statusCard.className = "ios-glass-inner p-8 rounded-[32px] mb-8 shadow-sm dark:shadow-lg relative overflow-hidden border-l-4 border-indigo-500 transition-colors duration-500"; | |
| const statusIcon = document.getElementById('statusIcon'); | |
| if(statusIcon) statusIcon.className = "fas fa-spinner fa-spin text-indigo-600"; | |
| } | |
| async function submitPredictiveData(e) { | |
| e.preventDefault(); | |
| const btn = document.getElementById('predictBtn'); | |
| const spinner = document.getElementById('predictSpinner'); | |
| btn.disabled = true; | |
| spinner.classList.remove('hidden'); | |
| const payload = { | |
| Weather: document.getElementById('tab_weather').value, | |
| Road_Light_Condition: document.getElementById('tab_light').value, | |
| Time_of_Day: document.getElementById('tab_time').value, | |
| Road_Type: document.getElementById('tab_road_type').value, | |
| Road_Condition: document.getElementById('tab_road_cond').value, | |
| Speed_Limit: document.getElementById('tab_speed').value, | |
| Traffic_Density: document.getElementById('tab_density').value, | |
| Number_of_Vehicles: document.getElementById('tab_vehicles').value, | |
| Vehicle_Type: document.getElementById('tab_vehicle').value, | |
| Driver_Age: document.getElementById('tab_age').value, | |
| Driver_Experience: document.getElementById('tab_exp').value, | |
| Driver_Alcohol: document.getElementById('tab_alcohol').value === "Yes" ? 1 : (document.getElementById('tab_alcohol').value === "No" ? 0 : "") | |
| }; | |
| const inputs = ['tab_weather', 'tab_light', 'tab_time', 'tab_road_type', 'tab_road_cond', 'tab_speed', 'tab_density', 'tab_vehicles', 'tab_vehicle', 'tab_age', 'tab_exp', 'tab_alcohol']; | |
| const isAllBlank = inputs.every(id => document.getElementById(id).value.trim() === ""); | |
| if (isAllBlank) { | |
| document.getElementById('predictiveFormView').classList.add('hidden'); | |
| document.getElementById('predictiveResultView').classList.remove('hidden'); | |
| const riskCard = document.getElementById('riskStatusCard'); | |
| const riskBadge = document.getElementById('riskBadge'); | |
| const riskIcon = document.getElementById('riskIcon'); | |
| const riskLabel = document.getElementById('riskLabelText'); | |
| const riskText = document.getElementById('riskLevelResult'); | |
| riskCard.className = 'ios-glass-inner p-10 rounded-[32px] shadow-sm dark:shadow-lg relative overflow-hidden w-full max-w-sm border border-gray-400 dark:border-gray-600 bg-gray-50 dark:bg-gray-800/50'; | |
| riskBadge.className = 'inline-flex items-center gap-2 px-5 py-2.5 rounded-xl text-xs font-black mb-6 uppercase tracking-[0.2em] border shadow-sm text-white bg-gray-500 border-gray-400'; | |
| riskIcon.className = 'fas fa-ban'; | |
| riskLabel.innerText = 'Invalid Input'; | |
| riskText.innerText = 'ERROR'; | |
| btn.disabled = false; | |
| spinner.classList.add('hidden'); | |
| return; | |
| } | |
| try { | |
| const res = await fetch('/predict_traffic_risk', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify(payload) | |
| }); | |
| const data = await res.json(); | |
| document.getElementById('predictiveFormView').classList.add('hidden'); | |
| document.getElementById('predictiveResultView').classList.remove('hidden'); | |
| const riskCard = document.getElementById('riskStatusCard'); | |
| const riskBadge = document.getElementById('riskBadge'); | |
| const riskIcon = document.getElementById('riskIcon'); | |
| const riskLabel = document.getElementById('riskLabelText'); | |
| const riskText = document.getElementById('riskLevelResult'); | |
| riskCard.className = 'ios-glass-inner p-10 rounded-[32px] shadow-sm dark:shadow-lg relative overflow-hidden w-full max-w-sm border'; | |
| riskBadge.className = 'inline-flex items-center gap-2 px-5 py-2.5 rounded-xl text-xs font-black mb-6 uppercase tracking-[0.2em] border shadow-sm text-white'; | |
| if(data.status === "Major") { | |
| riskCard.classList.add('border-red-500', 'bg-red-50/10', 'dark:bg-red-900/20'); | |
| riskBadge.classList.add('bg-red-500', 'border-red-400'); | |
| riskIcon.className = 'fas fa-exclamation-triangle'; | |
| riskLabel.innerText = 'High Alert'; | |
| riskText.innerText = 'Major'; | |
| } else if(data.status === "Moderate") { | |
| riskCard.classList.add('border-amber-500', 'bg-amber-50/10', 'dark:bg-amber-900/20'); | |
| riskBadge.classList.add('bg-amber-500', 'border-amber-400'); | |
| riskIcon.className = 'fas fa-exclamation-circle'; | |
| riskLabel.innerText = 'Elevated Alert'; | |
| riskText.innerText = 'Moderate'; | |
| } else { | |
| riskCard.classList.add('border-green-500', 'bg-green-50/10', 'dark:bg-green-900/20'); | |
| riskBadge.classList.add('bg-green-500', 'border-green-400'); | |
| riskIcon.className = 'fas fa-shield-check'; | |
| riskLabel.innerText = 'Safe Zone'; | |
| riskText.innerText = 'Minor'; | |
| } | |
| } catch (e) { | |
| alert("Error running prediction."); | |
| } finally { | |
| btn.disabled = false; | |
| spinner.classList.add('hidden'); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |