assignment / index.html
SRVCP's picture
Upload index.html
d8b3608 verified
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>TrafficAI Review System Prototype</title>
<style>
:root{
--bg:#06111f;
--panel:#0d1a2b;
--panel2:#111d31;
--line:#22314a;
--text:#f6f7fb;
--sub:#9fb0c8;
--orange:#ff7a14;
--green:#22c55e;
--blue:#4688ff;
--purple:#8b5cf6;
--yellow:#f7c948;
--red:#ef4444;
}
*{box-sizing:border-box}
body{
margin:0;
font-family:Inter, Segoe UI, system-ui, -apple-system, sans-serif;
background: radial-gradient(circle at top right, #112342 0%, var(--bg) 42%);
color:var(--text);
}
.hidden{display:none !important}
.app{min-height:100vh;padding:20px}
.card, .panel{
background:linear-gradient(180deg, rgba(17,29,49,.95), rgba(10,18,31,.98));
border:1px solid var(--line);
border-radius:18px;
box-shadow:0 12px 32px rgba(0,0,0,.28);
}
.topbar{
display:flex;align-items:center;justify-content:space-between;
padding:16px 22px;margin-bottom:18px
}
.brand{display:flex;align-items:center;gap:14px}
.logo{width:42px;height:42px;border-radius:12px;background:var(--orange);display:grid;place-items:center;font-size:18px;font-weight:700}
.brand h1{font-size:18px;margin:0}
.brand p{margin:2px 0 0;color:var(--sub);font-size:13px}
.user-badge{display:flex;align-items:center;gap:14px}
.avatar{width:42px;height:42px;border-radius:50%;display:grid;place-items:center;background:linear-gradient(135deg,#3654ff,#8b5cf6);font-weight:700}
.metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:18px}
.metric{padding:20px 22px;position:relative;overflow:hidden}
.metric .accent{position:absolute;inset:0 auto 0 0;width:4px;background:var(--orange);opacity:.95}
.metric.green .accent{background:var(--green)} .metric.blue .accent{background:var(--blue)} .metric.purple .accent{background:var(--purple)}
.metric h3{margin:0 0 10px;color:#b7c4d9;font-size:14px;font-weight:600}
.metric .value{font-size:36px;font-weight:800;line-height:1}
.metric .note{font-size:13px;color:var(--sub);margin-top:8px}
.searchbar, .triage{padding:16px 18px;margin-bottom:18px}
.searchbar input{
width:100%;padding:14px 16px;border-radius:14px;border:1px solid var(--line);
background:#07131f;color:var(--text);font-size:15px;outline:none
}
.section-title{font-size:18px;font-weight:700;margin:4px 0 14px}
.triage-grid, .violations{display:grid;gap:16px}
.triage-grid{grid-template-columns:repeat(3,1fr)}
.lane{padding:18px;border-radius:16px;border:1px solid var(--line);background:rgba(8,17,31,.7)}
.lane h4{margin:0 0 8px;font-size:16px}
.lane p{margin:0;color:var(--sub);font-size:13px;line-height:1.4}
.lane.fast{border-color:rgba(34,197,94,.5)} .lane.standard{border-color:rgba(70,136,255,.5)} .lane.exception{border-color:rgba(255,122,20,.5)}
.violations{grid-template-columns:repeat(4,1fr)}
.violation-card{padding:20px;cursor:pointer;transition:.2s transform,.2s border-color}
.violation-card:hover{transform:translateY(-2px);border-color:rgba(255,122,20,.45)}
.violation-card h4{margin:0 0 8px;font-size:18px}
.violation-meta{display:flex;justify-content:space-between;align-items:center;font-size:13px;color:var(--sub);margin:10px 0}
.badge{padding:6px 10px;border-radius:10px;border:1px solid var(--line);font-size:12px;background:rgba(255,255,255,.03);display:inline-flex;gap:6px;align-items:center}
.bar{height:8px;border-radius:999px;background:#1b2d46;overflow:hidden}
.bar > span{display:block;height:100%;background:var(--orange);border-radius:inherit}
.login-wrap{min-height:100vh;display:grid;place-items:center;padding:24px}
.login{width:min(420px,92vw);padding:24px}
.login .brand{margin-bottom:18px}
label{display:block;color:#b7c4d9;font-size:13px;margin:12px 0 6px}
input, textarea{
width:100%;padding:13px 14px;border-radius:12px;border:1px solid var(--line);
background:#08131f;color:var(--text);outline:none;font-size:14px
}
.btn{padding:14px 18px;border:none;border-radius:14px;background:var(--orange);color:white;font-weight:700;cursor:pointer;font-size:14px}
.btn.secondary{background:transparent;border:1px solid var(--line);color:var(--text)}
.dashboard-shell,.review-shell{max-width:1440px;margin:0 auto}
.review-toolbar{display:flex;justify-content:space-between;align-items:center;padding:14px 20px;margin-bottom:18px}
.review-toolbar .left{display:flex;align-items:center;gap:18px}
.back{cursor:pointer;color:var(--sub)}
.review-toolbar h2{font-size:22px;margin:0}
.review-toolbar p{margin:2px 0 0;color:var(--sub);font-size:13px}
.stats{display:flex;gap:24px;align-items:center;font-size:13px;color:var(--sub)}
.stats strong{display:block;color:var(--text);font-size:16px}
.progress{width:140px;height:10px;background:#1a2a42;border-radius:999px;overflow:hidden}
.progress span{display:block;height:100%;width:2%;background:linear-gradient(90deg,var(--orange),#ffc56e)}
.review-grid{display:grid;grid-template-columns:1.55fr .72fr .72fr;gap:18px}
.review-col{padding:18px}
.chips{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
.chip{padding:6px 10px;border-radius:10px;font-size:12px;font-weight:600}
.chip.blue{background:rgba(70,136,255,.18);color:#87b0ff;border:1px solid rgba(70,136,255,.35)}
.chip.purple{background:rgba(139,92,246,.18);color:#c1a9ff;border:1px solid rgba(139,92,246,.35)}
.video-box{height:360px;border-radius:18px;background:linear-gradient(180deg,#3567e8 0%,#2d58c4 60%,#1e3f95 100%);display:grid;place-items:center;font-size:110px;font-weight:800;letter-spacing:-2px;position:relative;overflow:hidden}
.video-box:before{content:"";position:absolute;inset:auto 0 0 0;height:92px;background:linear-gradient(180deg, rgba(0,0,0,0), rgba(0,0,0,.45));}
.playbar{display:flex;align-items:center;gap:14px;padding:14px 10px 2px;color:white;font-size:13px}
.playline{flex:1;height:5px;background:rgba(255,255,255,.28);border-radius:999px;overflow:hidden}
.playline span{display:block;width:33%;height:100%;background:var(--orange)}
.meta-list{display:grid;gap:10px;margin-top:14px;color:#d8e0ee}
.meta-item{display:flex;gap:10px;align-items:center;font-size:15px}
.evidence-card{padding:14px;border-radius:16px;background:#0a1323;border:1px solid var(--line);margin-bottom:12px}
.event-thumb{height:160px;border-radius:14px;background:linear-gradient(180deg,#20293b,#181f2e);display:grid;place-items:center;font-size:42px;font-weight:700}
.event-thumb.red{background:#e31f25}
.event-thumb.blue{background:linear-gradient(180deg,#4d73d6,#4065c6)}
.evidence-title{font-size:15px;font-weight:700;margin:12px 0 6px}
.muted{color:var(--sub);font-size:13px}
textarea{height:236px;resize:none}
.tags{display:flex;flex-wrap:wrap;gap:10px;margin-top:14px}
.tag{padding:8px 12px;border-radius:12px;border:1px solid var(--line);background:#0a1323;color:var(--text);cursor:pointer;font-size:13px}
.tag.active{border-color:rgba(255,122,20,.55);background:rgba(255,122,20,.14)}
.actions{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:18px}
.action{padding:18px;border-radius:16px;font-size:18px;font-weight:700;border:1px solid transparent;cursor:pointer;display:flex;justify-content:space-between;align-items:center}
.action small{font-size:13px;opacity:.85}
.dismiss{background:rgba(239,68,68,.08);border-color:rgba(239,68,68,.35);color:#ff9d9d}
.escalate{background:rgba(247,201,72,.09);border-color:rgba(247,201,72,.35);color:#ffe08d}
.approve{background:rgba(34,197,94,.08);border-color:rgba(34,197,94,.35);color:#86f2ae}
.tip{font-size:13px;color:var(--sub);margin-top:12px}
.notes-header{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:10px}
.voice-btn{display:inline-flex;align-items:center;gap:8px;padding:10px 12px;border-radius:12px;border:1px solid rgba(255,122,20,.35);background:rgba(255,122,20,.12);color:#ffd0ad;font-size:13px;font-weight:700;cursor:pointer}
.voice-btn.recording{background:rgba(239,68,68,.16);border-color:rgba(239,68,68,.42);color:#ffb7b7}
.helper-copy{font-size:12px;line-height:1.45;color:var(--sub);margin-bottom:10px}
.voice-status{margin-top:10px;padding:10px 12px;border-radius:12px;border:1px dashed rgba(255,122,20,.35);background:rgba(255,122,20,.06);font-size:12px;color:#ffd0ad}
.transcript-preview{margin-top:10px;padding:12px;border-radius:14px;border:1px solid var(--line);background:#0a1323;font-size:13px;line-height:1.45;color:#d8e0ee}
.transcript-preview strong{display:block;font-size:11px;color:var(--sub);margin-bottom:6px;text-transform:uppercase;letter-spacing:.04em}
.footer-meta{margin-top:18px;padding-top:14px;border-top:1px solid var(--line);font-size:13px;color:var(--sub);display:grid;grid-template-columns:1fr auto;gap:10px}
.toast{position:fixed;right:22px;bottom:22px;padding:14px 16px;border-radius:14px;background:#101d31;border:1px solid var(--line);box-shadow:0 12px 30px rgba(0,0,0,.3);display:none}
.toast.show{display:block}
@media (max-width: 1180px){
.metrics,.violations,.review-grid,.triage-grid,.actions{grid-template-columns:1fr 1fr}
.review-grid .review-col:first-child{grid-column:1/-1}
}
@media (max-width: 820px){
.metrics,.violations,.triage-grid,.review-grid,.actions{grid-template-columns:1fr}
.video-box{font-size:72px;height:260px}
.topbar,.review-toolbar{flex-direction:column;align-items:flex-start;gap:14px}
}
</style>
</head>
<body>
<div id="loginScreen" class="login-wrap">
<div class="login card">
<div class="brand">
<div class="logo">๐ŸŽฅ</div>
<div>
<h1>TrafficAI Review System</h1>
<p>Traffic Enforcement Portal</p>
</div>
</div>
<label>Officer ID</label>
<input id="officerId" value="TPO-2847" />
<label>Password</label>
<input type="password" value="password" />
<div style="display:flex;gap:10px;margin-top:18px">
<button class="btn" onclick="enterDashboard()">Sign In</button>
<button class="btn secondary" onclick="enterDashboard()">Use Demo</button>
</div>
<p style="margin:14px 0 0;color:var(--sub);font-size:12px">Demo shortcut: press Enter to sign in.</p>
</div>
</div>
<div id="app" class="app hidden">
<div id="dashboardScreen" class="dashboard-shell">
<div class="topbar card">
<div class="brand">
<div class="logo">๐ŸŽฅ</div>
<div>
<h1>TrafficAI Review System</h1>
<p>Traffic Enforcement Dashboard</p>
</div>
</div>
<div class="user-badge">
<div style="text-align:right">
<div style="font-size:12px;color:var(--sub)">Officer</div>
<div id="officerLabel" style="font-size:22px;font-weight:800">TPO-2847</div>
</div>
<div class="avatar">TP</div>
</div>
</div>
<div class="metrics">
<div class="metric card">
<div class="accent"></div>
<h3>Pending Reviews</h3>
<div class="value" id="pendingValue">1626</div>
<div class="note">Across all violation types</div>
</div>
<div class="metric green card">
<div class="accent"></div>
<h3>Today Reviewed</h3>
<div class="value" id="reviewedValue">127</div>
<div class="note">+23% from yesterday</div>
</div>
<div class="metric blue card">
<div class="accent"></div>
<h3>Avg Review Time</h3>
<div class="value" id="avgValue">7.8s</div>
<div class="note">Target: &lt; 10s</div>
</div>
<div class="metric purple card">
<div class="accent"></div>
<h3>System Accuracy</h3>
<div class="value">93.2%</div>
<div class="note">AI-human agreement</div>
</div>
</div>
<div class="searchbar card">
<input placeholder="Search violation types, case IDs, or locations..." />
</div>
<div class="triage card">
<div class="section-title">Suggested triage lanes</div>
<div class="triage-grid">
<div class="lane fast">
<h4>Fast lane</h4>
<p>High-confidence cases with complete evidence. Best path for sub-10 second review.</p>
</div>
<div class="lane standard">
<h4>Standard lane</h4>
<p>Normal review flow for cases that need officer validation but have no major risk flags.</p>
</div>
<div class="lane exception">
<h4>Exception lane</h4>
<p>Poor visibility, emergency vehicle, unclear signage, or low OCR confidence.</p>
</div>
</div>
</div>
<div>
<div class="section-title">Violation Types - Quick Access</div>
<div class="violations" id="violationGrid"></div>
</div>
</div>
<div id="reviewScreen" class="review-shell hidden">
<div class="review-toolbar card">
<div class="left">
<div class="back" onclick="goDashboard()">โ† Dashboard</div>
<div>
<h2 id="reviewTitle">Red Light Running</h2>
<p id="caseCounter">Case 1 of 50</p>
</div>
</div>
<div class="stats">
<div><span>Avg Review Time</span><strong id="sessionAvg">0.0s</strong></div>
<div><span>Reviewed</span><strong id="sessionReviewed">0</strong></div>
<div>
<span>Progress</span>
<div class="progress"><span id="reviewProgress"></span></div>
</div>
</div>
</div>
<div class="review-grid">
<div class="review-col panel">
<div class="chips">
<div class="chip blue" id="aiConfidence">AI: 83.5%</div>
<div class="chip purple" id="plateConfidence">Plate: 93%</div>
</div>
<div class="video-box" id="videoStage">Vehicle</div>
<div class="playbar">
<div>โ–ถ</div>
<div class="playline"><span></span></div>
<div id="clipTime">0:03 / 0:08</div>
</div>
<div class="meta-list">
<div class="meta-item">๐Ÿ“ <span id="caseLocation">Banjara Hills Signal</span></div>
<div class="meta-item">๐Ÿ“ท <span id="casePlate">TS 69 BP 4715</span></div>
<div class="meta-item">๐Ÿ•’ <span id="caseTimestamp">4/20/2026, 8:54:54 PM</span></div>
<div class="meta-item">๐Ÿšฆ Signal state: <strong>Red</strong> ยท Stop line crossed: <strong>Yes</strong></div>
</div>
<div class="actions">
<button class="action dismiss" onclick="takeAction('dismiss')">Dismiss <small>โ† A</small></button>
<button class="action escalate" onclick="takeAction('escalate')">Escalate <small>โ†‘ S</small></button>
<button class="action approve" onclick="takeAction('approve')">Issue Citation <small>D โ†’</small></button>
</div>
<div class="tip">๐Ÿ’ก Keyboard shortcuts: A = Dismiss ยท S = Escalate ยท D = Issue Citation</div>
</div>
<div class="review-col panel">
<div class="section-title" style="margin-top:0">Evidence Trail</div>
<div class="evidence-card">
<div class="event-thumb">Approach</div>
<div class="evidence-title">Vehicle approaching intersection</div>
<div class="muted">Context frame ยท 0.0s</div>
</div>
<div class="evidence-card">
<div class="event-thumb red">Violation</div>
<div class="evidence-title">Stop line crossed on red</div>
<div class="muted">Violation frame ยท 2.5s</div>
</div>
<div class="evidence-card">
<div class="event-thumb blue" style="font-size:32px">TS 69 BP</div>
<div class="evidence-title">Plate crop</div>
<div class="muted">OCR: TS69BP4715 ยท Confidence 93%</div>
</div>
</div>
<div class="review-col panel">
<div class="notes-header">
<div class="section-title" style="margin:0">Review Notes</div>
<button id="voiceBtn" class="voice-btn" onclick="toggleVoiceNote()">๐ŸŽ™ Start voice note</button>
</div>
<div class="helper-copy">Use quick tags for common reasons and a 3โ€“5 second voice note for dismiss or escalate cases so rationale capture stays within the &lt;10 second target.</div>
<textarea id="notes" placeholder="Add notes about this case... or tap Start voice note"></textarea>
<div id="voiceStatus" class="voice-status">Ready for short dictation. Best used for exception cases that need audit-ready reasoning.</div>
<div id="transcriptPreview" class="transcript-preview"><strong>Latest transcript</strong><span id="transcriptText">No voice note captured yet.</span></div>
<div class="section-title" style="margin:16px 0 10px">Quick Tags</div>
<div class="tags">
<button class="tag" onclick="toggleTag(this)">Clear violation</button>
<button class="tag" onclick="toggleTag(this)">Unclear</button>
<button class="tag" onclick="toggleTag(this)">Emergency vehicle</button>
<button class="tag" onclick="toggleTag(this)">Poor visibility</button>
<button class="tag" onclick="toggleTag(this)">Plate mismatch</button>
</div>
<div class="footer-meta">
<div>Case ID: <span id="caseId">CASE-1776709784805-0</span></div>
<div>Current Time: <span id="timerValue">46.5s</span></div>
</div>
</div>
</div>
</div>
</div>
<div id="toast" class="toast"></div>
<script>
const violations = [
{ name:'Red Light Running', count:234, time:'7.2s', accuracy:'94.5%', progress:94 },
{ name:'Speeding', count:456, time:'6.8s', accuracy:'96.2%', progress:96 },
{ name:'Illegal Parking', count:189, time:'8.1s', accuracy:'91.3%', progress:91 },
{ name:'Wrong Way', count:98, time:'9.2s', accuracy:'89.7%', progress:89 },
{ name:'No Helmet', count:167, time:'5.9s', accuracy:'97.1%', progress:97 },
{ name:'Triple Riding', count:145, time:'7.5s', accuracy:'92.8%', progress:92 },
{ name:'Mobile While Driving', count:203, time:'8.7s', accuracy:'88.4%', progress:88 },
{ name:'No Seatbelt', count:134, time:'6.3s', accuracy:'95.6%', progress:95 }
];
const cases = [
{
title:'Red Light Running', ai:'83.5%', plate:'93%', location:'Banjara Hills Signal', plateText:'TS 69 BP 4715',
timestamp:'4/20/2026, 8:54:54 PM', caseId:'CASE-1776709784805-0', stage:'Vehicle', clip:'0:03 / 0:08',
voiceNote:'Driver entered the intersection after the light turned red. Plate readable and stop line clearly crossed.'
},
{
title:'Speeding', ai:'91.2%', plate:'96%', location:'Outer Ring Road - Exit 4', plateText:'TS 08 CR 2201',
timestamp:'4/20/2026, 9:02:11 PM', caseId:'CASE-1776709784805-1', stage:'Speed', clip:'0:02 / 0:06',
voiceNote:'Radar overlay matches vehicle lane. Speed above posted limit and plate OCR is high confidence.'
},
{
title:'No Helmet', ai:'88.9%', plate:'90%', location:'Madhapur Main Road', plateText:'TS 11 DU 8877',
timestamp:'4/20/2026, 9:07:40 PM', caseId:'CASE-1776709784805-2', stage:'Rider', clip:'0:01 / 0:05',
voiceNote:'Rider head is fully visible without helmet. No obstruction and number plate remains readable.'
}
];
let state = {
officer:'TPO-2847',
pending:1626,
reviewed:127,
avg:7.8,
currentCase:0,
sessionReviewed:0,
sessionSeconds:0,
timer:46.5,
recording:false
};
function renderViolations(){
const grid = document.getElementById('violationGrid');
grid.innerHTML = violations.map((v, i) => `
<div class="violation-card card" data-index="${i}">
<div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start">
<div>
<h4>${v.name}</h4>
<div style="color:var(--sub);font-size:13px">Click to review cases</div>
</div>
<div class="badge">${v.count}</div>
</div>
<div class="violation-meta"><span>Avg Time</span><strong style="color:var(--text)">${v.time}</strong></div>
<div class="violation-meta"><span>Accuracy</span><strong style="color:var(--text)">${v.accuracy}</strong></div>
<div class="bar"><span style="width:${v.progress}%"></span></div>
</div>
`).join('');
grid.querySelectorAll('.violation-card').forEach(card => {
card.addEventListener('click', () => openReviewByIndex(Number(card.dataset.index)));
});
}
function showToast(message){
const toast = document.getElementById('toast');
toast.textContent = message;
toast.classList.add('show');
clearTimeout(showToast._t);
showToast._t = setTimeout(() => toast.classList.remove('show'), 1800);
}
function enterDashboard(){
state.officer = document.getElementById('officerId').value || 'TPO-2847';
document.getElementById('officerLabel').textContent = state.officer;
document.getElementById('loginScreen').classList.add('hidden');
document.getElementById('app').classList.remove('hidden');
renderViolations();
}
function goDashboard(){
document.getElementById('reviewScreen').classList.add('hidden');
document.getElementById('dashboardScreen').classList.remove('hidden');
}
function openReviewByIndex(index){
state.currentCase = index % cases.length;
hydrateCase();
document.getElementById('dashboardScreen').classList.add('hidden');
document.getElementById('reviewScreen').classList.remove('hidden');
}
function hydrateCase(){
const c = cases[state.currentCase];
document.getElementById('reviewTitle').textContent = c.title;
document.getElementById('caseCounter').textContent = `Case ${state.currentCase + 1} of 50`;
document.getElementById('aiConfidence').textContent = `AI: ${c.ai}`;
document.getElementById('plateConfidence').textContent = `Plate: ${c.plate}`;
document.getElementById('videoStage').textContent = c.stage;
document.getElementById('caseLocation').textContent = c.location;
document.getElementById('casePlate').textContent = c.plateText;
document.getElementById('caseTimestamp').textContent = c.timestamp;
document.getElementById('caseId').textContent = c.caseId;
document.getElementById('clipTime').textContent = c.clip;
document.getElementById('sessionReviewed').textContent = state.sessionReviewed;
document.getElementById('sessionAvg').textContent = state.sessionReviewed ? `${(state.sessionSeconds/state.sessionReviewed).toFixed(1)}s` : '0.0s';
document.getElementById('reviewProgress').style.width = `${Math.max(2, (state.sessionReviewed / 50) * 100)}%`;
document.getElementById('timerValue').textContent = `${state.timer.toFixed(1)}s`;
document.getElementById('notes').value = '';
document.getElementById('transcriptText').textContent = 'No voice note captured yet.';
document.getElementById('voiceStatus').textContent = 'Ready for short dictation. Best used for exception cases that need audit-ready reasoning.';
state.recording = false;
const voiceBtn = document.getElementById('voiceBtn');
voiceBtn.textContent = '๐ŸŽ™ Start voice note';
voiceBtn.classList.remove('recording');
document.querySelectorAll('.tag').forEach(t => t.classList.remove('active'));
}
function toggleVoiceNote(){
const btn = document.getElementById('voiceBtn');
const status = document.getElementById('voiceStatus');
const transcriptText = document.getElementById('transcriptText');
const notes = document.getElementById('notes');
const c = cases[state.currentCase];
if (!state.recording) {
state.recording = true;
btn.textContent = 'โบ Recording...';
btn.classList.add('recording');
status.textContent = 'Listening... speak a short reason such as โ€œclear violation, plate readable, issue citation.โ€';
clearTimeout(toggleVoiceNote._timer);
toggleVoiceNote._timer = setTimeout(() => {
state.recording = false;
btn.textContent = '๐ŸŽ™ Add another voice note';
btn.classList.remove('recording');
status.textContent = 'Voice note captured and transcribed. This note will be saved with the audit trail.';
transcriptText.textContent = c.voiceNote;
notes.value = c.voiceNote;
showToast('Voice note transcribed');
}, 1200);
} else {
clearTimeout(toggleVoiceNote._timer);
state.recording = false;
btn.textContent = '๐ŸŽ™ Add voice note';
btn.classList.remove('recording');
status.textContent = 'Voice note stopped. Tap again to capture a new 3โ€“5 second note.';
}
}
function takeAction(type){
const labels = {
approve:'Citation issued',
dismiss:'Case dismissed',
escalate:'Case escalated for manual review'
};
state.pending -= 1;
state.reviewed += 1;
state.sessionReviewed += 1;
state.sessionSeconds += type === 'escalate' ? 11.2 : (type === 'dismiss' ? 6.4 : 7.1);
state.avg = Math.max(6.8, ((state.avg * (state.reviewed - 1)) + (state.sessionSeconds / state.sessionReviewed)) / state.reviewed).toFixed(1);
document.getElementById('pendingValue').textContent = state.pending;
document.getElementById('reviewedValue').textContent = state.reviewed;
document.getElementById('avgValue').textContent = `${state.avg}s`;
showToast(labels[type]);
state.currentCase = (state.currentCase + 1) % cases.length;
state.timer = 42 + Math.random() * 8;
hydrateCase();
}
function toggleTag(el){ el.classList.toggle('active'); }
document.addEventListener('keydown', (e) => {
if (!document.getElementById('app').classList.contains('hidden') && document.getElementById('dashboardScreen').classList.contains('hidden')) {
if (e.key.toLowerCase() === 'a') takeAction('dismiss');
if (e.key.toLowerCase() === 's') takeAction('escalate');
if (e.key.toLowerCase() === 'd') takeAction('approve');
}
if (document.getElementById('loginScreen').classList.contains('hidden') === false && e.key === 'Enter') enterDashboard();
});
renderViolations();
</script>
</body>
</html>