CrashVisionAI / templates /index.html
webapp1's picture
Update templates/index.html
f819bd9 verified
Raw
History Blame Contribute Delete
74.6 kB
<!DOCTYPE html>
<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>