itsluckysharma01's picture
make website responsive
d9f53a4
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video Analysis - NETRA</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/dark-theme.css') }}">
<style>
/* ── Model panel ── */
.model-checkbox-label {
display: flex;
align-items: center;
gap: 0.8rem;
padding: 0.8rem;
border-radius: 10px;
cursor: pointer;
transition: var(--transition);
background: var(--white);
border: 1px solid var(--line);
}
.model-checkbox-label:hover { background: var(--blue-100); border-color: var(--blue-600); }
.model-checkbox-label input { cursor: pointer; width: 18px; height: 18px; }
.panel-header { cursor: pointer; user-select: none; transition: var(--transition); }
.panel-header:hover { background: rgba(28,110,242,0.05); }
.panel-actions { display: flex; gap: 0.8rem; margin-top: 1rem; flex-wrap: wrap; }
.panel-actions .btn { flex: 1; min-width: 120px; }
/* ── Live alert banner ── */
#alert-banner {
display: none;
position: fixed;
top: 70px;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
background: var(--danger);
color: white;
padding: 0.9rem 2rem;
border-radius: 12px;
font-weight: 700;
font-size: 1.05rem;
box-shadow: 0 8px 30px rgba(229,57,53,0.45);
animation: pulseBanner 1s ease-in-out infinite;
text-align: center;
min-width: 340px;
max-width: 90vw;
}
@keyframes pulseBanner {
0%,100% { box-shadow: 0 8px 30px rgba(229,57,53,0.45); }
50% { box-shadow: 0 8px 50px rgba(229,57,53,0.85); transform: translateX(-50%) scale(1.02); }
}
/* ── Real-time processing panel ── */
#realtime-panel {
display: none;
background: var(--white);
border: 2px solid var(--blue-200);
border-radius: var(--radius-md);
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: var(--shadow-sm);
}
.rt-header { display: flex; align-items: center; gap: 0.8rem; margin-bottom: 1rem; }
.rt-spinner { width: 20px; height: 20px; border: 3px solid var(--blue-200); border-top-color: var(--blue-600); border-radius: 50%; animation: spin 0.7s linear infinite; flex-shrink: 0; }
@keyframes spin { to { transform: rotate(360deg); } }
.rt-frame-info { color: var(--slate-700); font-size: 0.9rem; margin-bottom: 0.5rem; }
/* Progress bar */
.rt-progress-wrap { background: var(--blue-100); border-radius: 99px; height: 10px; overflow: hidden; margin-bottom: 1rem; }
.rt-progress-fill { height: 100%; background: linear-gradient(90deg, var(--blue-600), var(--orange-600)); border-radius: 99px; transition: width 0.5s ease; width: 0%; }
/* Live stat cards */
.rt-stats { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; }
.rt-stat { flex: 1; min-width: 80px; background: var(--blue-50); border: 1px solid var(--blue-200); border-radius: 10px; padding: 0.7rem 1rem; text-align: center; }
.rt-stat-val { font-size: 1.6rem; font-weight: 700; color: var(--blue-700); }
.rt-stat-lbl { font-size: 0.75rem; color: var(--slate-700); margin-top: 0.2rem; }
/* Live alert feed */
#live-alerts-feed {
max-height: 200px;
overflow-y: auto;
border: 1px solid var(--line);
border-radius: 10px;
padding: 0.5rem;
background: var(--white);
}
.feed-item {
display: flex;
align-items: flex-start;
gap: 0.6rem;
padding: 0.5rem 0.6rem;
border-radius: 8px;
margin-bottom: 0.4rem;
font-size: 0.85rem;
animation: feedSlide 0.3s ease;
}
@keyframes feedSlide { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; } }
.feed-item.critical { background: #ffeef0; border-left: 3px solid var(--danger); }
.feed-item.high { background: #fff3e0; border-left: 3px solid var(--warn); }
.feed-item.medium { background: var(--blue-50); border-left: 3px solid var(--info); }
.feed-item.low { background: #f1f8f4; border-left: 3px solid var(--ok); }
.feed-badge { font-weight: 700; font-size: 0.75rem; padding: 2px 6px; border-radius: 4px; flex-shrink: 0; margin-top: 1px; }
.feed-badge.critical { background: var(--danger); color: white; }
.feed-badge.high { background: var(--warn); color: white; }
.feed-badge.medium { background: var(--info); color: white; }
.feed-badge.low { background: var(--ok); color: white; }
/* ── Video Processing Panel ── */
.processing-video-panel {
background: var(--white);
border: 2px solid var(--blue-200);
border-radius: var(--radius-md);
overflow: hidden;
box-shadow: var(--shadow-sm);
position: relative;
}
.processing-video-container {
position: relative;
background: #000;
width: 100%;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
overflow: hidden;
border-bottom: 1px solid var(--line);
}
.processing-video-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.processing-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
z-index: 10;
}
.processing-spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(28, 110, 242, 0.2);
border-top-color: var(--blue-600);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.processing-info {
color: white;
text-align: center;
font-size: 0.95rem;
}
.processing-info strong { display: block; margin-bottom: 0.3rem; font-size: 1.1rem; }
.processing-progress {
width: 200px;
height: 6px;
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
overflow: hidden;
margin-top: 0.5rem;
}
.processing-bar {
height: 100%;
background: linear-gradient(90deg, var(--blue-600), var(--orange-600));
width: 0%;
border-radius: 3px;
transition: width 0.4s ease;
}
.processing-video-info {
padding: 1rem;
background: var(--blue-50);
border-top: 1px solid var(--blue-200);
font-size: 0.9rem;
color: var(--slate-700);
}
.processing-video-info strong { color: var(--blue-900); }
/* ── Results ── */
.results-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
.res-card { background: var(--white); border: 1px solid var(--line); border-radius: var(--radius-md); padding: 1.1rem; text-align: center; box-shadow: var(--shadow-sm); }
.res-card.danger { border-color: #ffcdd2; background: #fff5f5; }
.res-card.warn { border-color: #ffe0b2; background: #fffbf0; }
.res-val { font-size: 2rem; font-weight: 700; color: var(--blue-700); }
.res-card.danger .res-val { color: var(--danger); }
.res-card.warn .res-val { color: var(--warn); }
.res-lbl { font-size: 0.8rem; color: var(--slate-700); margin-top: 0.3rem; }
/* History list */
.history-item { display: flex; gap: 1rem; align-items: flex-start; padding: 1rem; border: 1px solid var(--line); border-radius: 10px; background: var(--white); transition: var(--transition); }
.history-item:hover { border-color: var(--blue-600); box-shadow: var(--shadow-sm); }
.history-item + .history-item { margin-top: 0.6rem; }
.history-thumb { width: 90px; height: 60px; object-fit: cover; border-radius: 6px; flex-shrink: 0; background: var(--blue-50); }
.history-meta { flex: 1; min-width: 0; }
.history-badges { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.4rem; }
.hbadge { font-size: 0.75rem; padding: 2px 7px; border-radius: 10px; background: var(--blue-100); color: var(--blue-700); font-weight: 600; }
.hbadge.red { background: #ffeef0; color: var(--danger); }
/* Detection record cards */
.det-record { display: flex; gap: 0.8rem; align-items: flex-start; padding: 0.8rem; border: 1px solid var(--line); border-radius: 10px; background: var(--white); }
.det-record + .det-record { margin-top: 0.5rem; }
.det-thumb { width: 70px; height: 55px; object-fit: cover; border-radius: 6px; flex-shrink: 0; }
.det-info { flex: 1; min-width: 0; font-size: 0.85rem; }
.badge-sev { display: inline-block; padding: 2px 8px; border-radius: 6px; font-weight: 700; font-size: 0.75rem; color: white; }
.badge-sev.CRITICAL,.badge-sev.HIGH { background: var(--danger); }
.badge-sev.MEDIUM { background: var(--warn); }
.badge-sev.LOW { background: var(--ok); }
@media (max-width: 640px) {
.panel-actions { flex-direction: column; }
.rt-stats { gap: 0.5rem; }
}
/* ── Insights Panel ── */
#insights-panel {
animation: fadeIn 0.5s ease;
}
.insight-card {
background: linear-gradient(135deg, var(--blue-50) 0%, var(--blue-100) 100%);
border: 2px solid var(--blue-200);
border-radius: 12px;
padding: 1.2rem;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.insight-card:hover {
border-color: var(--blue-600);
box-shadow: 0 8px 20px rgba(28, 110, 242, 0.25);
transform: translateY(-2px);
}
.insight-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(28, 110, 242, 0.05) 0%, transparent 100%);
pointer-events: none;
}
.insight-card.alert {
background: linear-gradient(135deg, #fff5f5 0%, #ffe6e6 100%);
border-color: #ffb3b3;
}
.insight-card.alert:hover {
border-color: var(--danger);
box-shadow: 0 8px 20px rgba(229, 57, 53, 0.25);
}
.insight-card.warning {
background: linear-gradient(135deg, #fffbf0 0%, #ffe0b2 100%);
border-color: #ffb74d;
}
.insight-card.warning:hover {
border-color: var(--warn);
box-shadow: 0 8px 20px rgba(255, 152, 0, 0.25);
}
.insight-icon {
font-size: 2rem;
margin-bottom: 0.5rem;
}
.insight-label {
font-size: 0.85rem;
color: var(--slate-700);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 0.3rem;
}
.insight-value {
font-size: 1.8rem;
font-weight: 700;
color: var(--blue-700);
margin-bottom: 0.2rem;
}
.insight-card.alert .insight-value {
color: var(--danger);
}
.insight-card.warning .insight-value {
color: var(--warn);
}
.insight-desc {
font-size: 0.75rem;
color: var(--slate-600);
margin-top: 0.4rem;
line-height: 1.4;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<!-- Critical alert banner (shown for HIGH/CRITICAL detections during analysis) -->
<div id="alert-banner">
<span id="alert-banner-text">🚨 HIGH RISK DETECTED</span>
<button onclick="dismissBanner()" style="margin-left:1.2rem;background:rgba(255,255,255,0.25);border:none;color:white;cursor:pointer;border-radius:6px;padding:3px 8px;font-size:0.85rem;">βœ•</button>
</div>
<!-- Toast Container -->
<div id="toast-container"></div>
<!-- Navigation Bar -->
<nav class="navbar">
<div class="nav-container">
<div class="nav-brand">
<span class="brand-icon">πŸ‘οΈ</span>
<span>NETRA</span>
</div>
<button class="hamburger-menu" id="hamburger-toggle">
<span></span>
<span></span>
<span></span>
</button>
<div class="nav-menu" id="nav-menu">
<a href="{{ url_for('dashboard') }}" class="nav-link">Dashboard</a>
<div class="dropdown" id="analysis-dropdown">
<button class="nav-link dropdown-btn" onclick="toggleDropdown(event)">πŸ“Š Analysis β–Ό</button>
<div class="dropdown-content">
<a href="{{ url_for('live_camera') }}">πŸ“Ή Live Camera</a>
<a href="{{ url_for('video_analysis') }}" style="opacity:0.6;">πŸ“‹ Video Analysis</a>
</div>
</div>
<a href="{{ url_for('logout') }}" class="btn btn-outline">Logout</a>
</div>
</div>
</nav>
<div class="content-container">
<!-- Page Header -->
<div class="page-header reveal">
<h1>πŸ“Ή Video Analysis</h1>
<p>Upload a video and watch AI detect threats in real-time</p>
</div>
<!-- Model Selection -->
<div class="upload-section reveal">
<div class="model-selection-panel">
<div class="panel-header" onclick="toggleModelPanel()" style="padding:1rem;border-bottom:1px solid var(--line);">
<div style="display:flex;align-items:center;justify-content:space-between;">
<span class="panel-title">πŸ€– Select Detection Models</span>
<span id="model-toggle-icon">β–Ό</span>
</div>
</div>
<div id="model-panel-content" class="panel-content">
<div class="models-grid" id="models-grid">
<p style="text-align:center;color:var(--slate-700);">
<span class="spinner" style="width:14px;height:14px;margin-right:0.5rem;"></span>
Loading available models...
</p>
</div>
<div class="panel-actions">
<button class="btn btn-secondary" onclick="selectAllModels()">βœ“ Select All</button>
<button class="btn btn-secondary" onclick="deselectAllModels()">βœ— Deselect All</button>
<button class="btn btn-primary" onclick="applyModelSelection()">βš™οΈ Apply</button>
</div>
</div>
</div>
</div>
<!-- Upload Box -->
<div class="upload-section reveal" id="upload-section">
<div class="upload-box" id="upload-box" onclick="document.getElementById('video-file').click()" title="Click or drag & drop a video file">
<div class="upload-icon" style="animation:bounce 1.5s ease-in-out infinite;">πŸ“</div>
<h3>Upload Video File</h3>
<p>Click to select or drag and drop</p>
<small style="color:var(--slate-500);">Supported: MP4, AVI, MOV, MKV (Max 500 MB)</small>
<input type="file" id="video-file" accept="video/*" style="display:none;" onchange="handleFileSelect(event)">
</div>
<!-- Selected file row -->
<div id="file-info" style="display:none;align-items:center;gap:1rem;padding:1rem;background:var(--blue-50);border:1px solid var(--blue-200);border-radius:var(--radius-md);">
<span style="font-size:2rem;">🎬</span>
<div style="flex:1;min-width:0;">
<strong id="file-name" style="word-break:break-all;"></strong>
<p id="file-size" style="color:var(--slate-700);margin-top:0.2rem;font-size:0.9rem;"></p>
</div>
<div style="display:flex;gap:0.6rem;flex-shrink:0;">
<button class="btn btn-secondary" onclick="cancelFile()">βœ• Cancel</button>
<button class="btn btn-primary" onclick="uploadAndAnalyze()">πŸš€ Analyze</button>
</div>
</div>
<!-- Upload progress -->
<div id="upload-progress" style="display:none;margin-top:1rem;">
<div class="progress-bar"><div class="progress-fill" id="upload-fill"></div></div>
<p id="upload-text" style="color:var(--slate-700);margin-top:0.5rem;font-size:0.9rem;text-align:center;">Uploading...</p>
</div>
</div>
<!-- Real-time processing panel (shown while analysing) -->
<div id="realtime-panel">
<div class="rt-header">
<div class="rt-spinner"></div>
<div>
<strong style="color:var(--blue-900);">Analysing video in real-time…</strong>
<p class="rt-frame-info" id="rt-frame-info">Preparing…</p>
</div>
</div>
<div class="rt-progress-wrap">
<div class="rt-progress-fill" id="rt-progress-fill"></div>
</div>
<!-- Live Video Processing Preview -->
<div class="processing-video-panel" style="margin-bottom:1rem;">
<div class="processing-video-container">
<div class="processing-video-content">
<div id="processing-video-frame" style="width:100%;height:100%;background:var(--slate-900);"></div>
</div>
<div id="processing-overlay" class="processing-overlay">
<div class="processing-spinner"></div>
<div class="processing-info">
<strong id="processing-status">Processing Video…</strong>
<div id="processing-frame-count">Frame 0 of 0</div>
<div class="processing-progress">
<div class="processing-bar" id="processing-bar-fill"></div>
</div>
</div>
</div>
</div>
<div class="processing-video-info">
<div><strong>πŸ“Š Live Stats:</strong></div>
<div>Detections: <span id="proc-detections">0</span> | Alerts: <span id="proc-alerts">0</span> | High Risk: <span id="proc-high-risk" style="color:var(--danger);">0</span></div>
</div>
</div>
<div class="rt-stats">
<div class="rt-stat">
<div class="rt-stat-val" id="rt-detections">0</div>
<div class="rt-stat-lbl">Detections</div>
</div>
<div class="rt-stat">
<div class="rt-stat-val" id="rt-alerts" style="color:var(--danger);">0</div>
<div class="rt-stat-lbl">Alerts</div>
</div>
<div class="rt-stat">
<div class="rt-stat-val" id="rt-high-risk" style="color:var(--danger);">0</div>
<div class="rt-stat-lbl">High Risk</div>
</div>
<div class="rt-stat" style="flex:2;">
<div style="font-size:0.8rem;color:var(--slate-700);text-align:left;">Live Threat Feed</div>
<div id="live-alerts-feed" style="margin-top:0.3rem;">
<p style="color:var(--slate-500);font-size:0.85rem;padding:0.3rem;">No alerts yet β€” all clear</p>
</div>
</div>
</div>
</div>
<!-- Results Section -->
<div id="results-section" style="display:none;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1.5rem;flex-wrap:wrap;gap:1rem;">
<h2 style="color:var(--blue-900);">πŸ“Š Analysis Results</h2>
<div style="display:flex;gap:0.8rem;flex-wrap:wrap;">
<button class="btn btn-primary" onclick="reanalyzeVideo()" id="reanalyze-btn" style="display:none;">πŸ”„ Re-analyze</button>
<button class="btn btn-secondary" onclick="startNewAnalysis()">πŸ“ New Analysis</button>
</div>
</div>
<!-- Summary cards -->
<div class="results-grid" id="summary-cards">
<div class="res-card"><div class="res-val" id="res-total-frames">0</div><div class="res-lbl">Total Frames</div></div>
<div class="res-card"><div class="res-val" id="res-analyzed">0</div><div class="res-lbl">Analyzed</div></div>
<div class="res-card"><div class="res-val" id="res-detections">0</div><div class="res-lbl">Detections</div></div>
<div class="res-card warn"><div class="res-val" id="res-alerts">0</div><div class="res-lbl">Alerts</div></div>
<div class="res-card danger"><div class="res-val" id="res-emergency">0</div><div class="res-lbl">Emergency Frames</div></div>
</div>
<!-- Processed video -->
<div class="detections-table" style="margin-bottom:1.5rem;">
<h3>🎬 Processed Video</h3>
<div id="media-results" class="detections-list"></div>
</div>
<!-- Emergency frames gallery -->
<div id="emergency-section" class="detections-table" style="display:none;margin-bottom:1.5rem;">
<h3>🚨 Emergency Frames</h3>
<div id="emergency-gallery" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:1rem;"></div>
</div>
<!-- Interactive Detection Insights Panel -->
<div class="detections-table" style="margin-bottom:1.5rem;">
<h3>πŸ“Š Quick Insights</h3>
<div id="insights-panel" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
<!-- Dynamically populated with key detections -->
</div>
</div>
</div>
</div>
<script src="{{ url_for('static', filename='js/ui-utils.js') }}"></script>
<script src="{{ url_for('static', filename='js/video_analysis.js') }}"></script>
<script>
// Mobile menu toggle
const hamburger = document.getElementById('hamburger-toggle');
const navMenu = document.getElementById('nav-menu');
if (hamburger) {
hamburger.addEventListener('click', function(e) {
e.stopPropagation();
hamburger.classList.toggle('active');
navMenu.classList.toggle('active');
});
}
// Close menu when clicking on a link
document.querySelectorAll('.nav-link, .nav-menu a').forEach(link => {
link.addEventListener('click', function() {
hamburger?.classList.remove('active');
navMenu?.classList.remove('active');
});
});
// Close menu when clicking outside
document.addEventListener('click', function(e) {
if (!e.target.closest('.nav-container')) {
hamburger?.classList.remove('active');
navMenu?.classList.remove('active');
}
});
// Dropdown toggle for mobile
function toggleDropdown(e) {
e.preventDefault();
const dropdown = e.target.closest('.dropdown');
dropdown.classList.toggle('active');
}
</script>
</body>
</html>