SRVCP commited on
Commit
d8b3608
·
verified ·
1 Parent(s): b74d1f5

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +498 -1590
index.html CHANGED
@@ -1,1607 +1,515 @@
1
- <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Traffic Violation Review System</title>
7
- <style>
8
- @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Sora:wght@300;400;600;700&display=swap');
9
-
10
- :root {
11
- /* Dark theme - industrial/utilitarian aesthetic */
12
- --bg-primary: #0a0b0d;
13
- --bg-secondary: #13151a;
14
- --bg-tertiary: #1c1f26;
15
- --bg-card: #1e2229;
16
- --bg-elevated: #24272f;
17
-
18
- /* Accent colors - high contrast for speed */
19
- --accent-approve: #00ff88;
20
- --accent-reject: #ff3366;
21
- --accent-escalate: #ffaa00;
22
- --accent-info: #00d4ff;
23
- --accent-warning: #ff6b00;
24
-
25
- /* Text */
26
- --text-primary: #ffffff;
27
- --text-secondary: #9ca3af;
28
- --text-tertiary: #6b7280;
29
- --text-dim: #4b5563;
30
-
31
- /* Borders */
32
- --border-subtle: rgba(255, 255, 255, 0.06);
33
- --border-medium: rgba(255, 255, 255, 0.12);
34
- --border-strong: rgba(255, 255, 255, 0.2);
35
-
36
- /* Confidence indicators */
37
- --confidence-high: #00ff88;
38
- --confidence-medium: #ffaa00;
39
- --confidence-low: #ff3366;
40
-
41
- /* Shadows */
42
- --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3);
43
- --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4);
44
- --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.5);
45
- --shadow-glow: 0 0 24px rgba(0, 212, 255, 0.2);
46
-
47
- /* Typography */
48
- --font-display: 'Sora', sans-serif;
49
- --font-mono: 'JetBrains Mono', monospace;
50
-
51
- /* Transitions */
52
- --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
53
- --transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
54
- --transition-slow: 400ms cubic-bezier(0.4, 0, 0.2, 1);
55
- }
56
-
57
- * {
58
- margin: 0;
59
- padding: 0;
60
- box-sizing: border-box;
61
- }
62
-
63
- body {
64
- font-family: var(--font-display);
65
- background: var(--bg-primary);
66
- color: var(--text-primary);
67
- overflow: hidden;
68
- height: 100vh;
69
- position: relative;
70
- }
71
-
72
- /* Animated grid background */
73
- body::before {
74
- content: '';
75
- position: fixed;
76
- inset: 0;
77
- background-image:
78
- linear-gradient(to right, rgba(255, 255, 255, 0.02) 1px, transparent 1px),
79
- linear-gradient(to bottom, rgba(255, 255, 255, 0.02) 1px, transparent 1px);
80
- background-size: 40px 40px;
81
- pointer-events: none;
82
- animation: gridPulse 20s ease-in-out infinite;
83
- }
84
-
85
- @keyframes gridPulse {
86
- 0%, 100% { opacity: 0.3; }
87
- 50% { opacity: 0.6; }
88
- }
89
-
90
- /* Main container */
91
- .review-container {
92
- display: flex;
93
- height: 100vh;
94
- position: relative;
95
- z-index: 1;
96
- }
97
-
98
- /* Left panel - Video and Evidence */
99
- .evidence-panel {
100
- flex: 1;
101
- padding: 24px;
102
- display: flex;
103
- flex-direction: column;
104
- gap: 20px;
105
- overflow-y: auto;
106
- }
107
-
108
- /* Video card with swipe functionality */
109
- .video-card {
110
- position: relative;
111
- border-radius: 16px;
112
- overflow: hidden;
113
- background: var(--bg-card);
114
- border: 2px solid var(--border-medium);
115
- box-shadow: var(--shadow-lg);
116
- transition: all var(--transition-base);
117
- cursor: grab;
118
- user-select: none;
119
- }
120
-
121
- .video-card.dragging {
122
- cursor: grabbing;
123
- transform: scale(0.98);
124
- }
125
-
126
- .video-card.swipe-approve {
127
- border-color: var(--accent-approve);
128
- box-shadow: 0 0 40px rgba(0, 255, 136, 0.3);
129
- }
130
-
131
- .video-card.swipe-reject {
132
- border-color: var(--accent-reject);
133
- box-shadow: 0 0 40px rgba(255, 51, 102, 0.3);
134
- }
135
-
136
- .video-card.swipe-escalate {
137
- border-color: var(--accent-escalate);
138
- box-shadow: 0 0 40px rgba(255, 170, 0, 0.3);
139
- }
140
-
141
- .video-wrapper {
142
- position: relative;
143
- aspect-ratio: 16 / 9;
144
- background: #000;
145
- }
146
-
147
- .video-player {
148
- width: 100%;
149
- height: 100%;
150
- object-fit: cover;
151
- }
152
-
153
- /* Video overlay controls */
154
- .video-overlay {
155
- position: absolute;
156
- inset: 0;
157
- background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent 50%);
158
- pointer-events: none;
159
- opacity: 0;
160
- transition: opacity var(--transition-fast);
161
- }
162
-
163
- .video-card:hover .video-overlay {
164
- opacity: 1;
165
- }
166
-
167
- .video-controls {
168
- position: absolute;
169
- bottom: 16px;
170
- left: 16px;
171
- right: 16px;
172
- display: flex;
173
- align-items: center;
174
- gap: 12px;
175
- pointer-events: all;
176
- }
177
-
178
- .control-btn {
179
- width: 40px;
180
- height: 40px;
181
- border-radius: 50%;
182
- background: rgba(255, 255, 255, 0.1);
183
- backdrop-filter: blur(10px);
184
- border: 1px solid rgba(255, 255, 255, 0.2);
185
- display: flex;
186
- align-items: center;
187
- justify-content: center;
188
- cursor: pointer;
189
- transition: all var(--transition-fast);
190
- color: white;
191
- }
192
-
193
- .control-btn:hover {
194
- background: rgba(255, 255, 255, 0.2);
195
- transform: scale(1.1);
196
- }
197
-
198
- .control-btn:active {
199
- transform: scale(0.95);
200
- }
201
-
202
- .timeline {
203
- flex: 1;
204
- height: 4px;
205
- background: rgba(255, 255, 255, 0.2);
206
- border-radius: 2px;
207
- position: relative;
208
- cursor: pointer;
209
- }
210
-
211
- .timeline-progress {
212
- height: 100%;
213
- background: var(--accent-info);
214
- border-radius: 2px;
215
- width: 0%;
216
- transition: width 100ms linear;
217
- }
218
-
219
- .timeline-marker {
220
- position: absolute;
221
- top: -4px;
222
- height: 12px;
223
- width: 3px;
224
- background: var(--accent-warning);
225
- border-radius: 2px;
226
- box-shadow: 0 0 8px rgba(255, 170, 0, 0.6);
227
- }
228
-
229
- /* Confidence badge */
230
- .confidence-badge {
231
- position: absolute;
232
- top: 16px;
233
- right: 16px;
234
- padding: 8px 16px;
235
- border-radius: 20px;
236
- font-size: 13px;
237
- font-weight: 600;
238
- font-family: var(--font-mono);
239
- backdrop-filter: blur(10px);
240
- border: 1px solid;
241
- display: flex;
242
- align-items: center;
243
- gap: 6px;
244
- animation: badgePulse 2s ease-in-out infinite;
245
- }
246
-
247
- @keyframes badgePulse {
248
- 0%, 100% { transform: scale(1); }
249
- 50% { transform: scale(1.05); }
250
- }
251
-
252
- .confidence-badge.high {
253
- background: rgba(0, 255, 136, 0.15);
254
- border-color: var(--confidence-high);
255
- color: var(--confidence-high);
256
- }
257
-
258
- .confidence-badge.medium {
259
- background: rgba(255, 170, 0, 0.15);
260
- border-color: var(--confidence-medium);
261
- color: var(--confidence-medium);
262
- }
263
-
264
- .confidence-badge.low {
265
- background: rgba(255, 51, 102, 0.15);
266
- border-color: var(--confidence-low);
267
- color: var(--confidence-low);
268
- }
269
-
270
- .confidence-dot {
271
- width: 6px;
272
- height: 6px;
273
- border-radius: 50%;
274
- background: currentColor;
275
- animation: dotPulse 1.5s ease-in-out infinite;
276
- }
277
-
278
- @keyframes dotPulse {
279
- 0%, 100% { opacity: 1; }
280
- 50% { opacity: 0.4; }
281
- }
282
-
283
- /* Swipe direction hints */
284
- .swipe-hint {
285
- position: absolute;
286
- top: 50%;
287
- transform: translateY(-50%);
288
- font-size: 48px;
289
- opacity: 0;
290
- transition: opacity var(--transition-fast);
291
- pointer-events: none;
292
- display: flex;
293
- flex-direction: column;
294
- align-items: center;
295
- gap: 8px;
296
- }
297
-
298
- .swipe-hint-left {
299
- left: 40px;
300
- }
301
-
302
- .swipe-hint-right {
303
- right: 40px;
304
- }
305
-
306
- .swipe-hint-up {
307
- top: 40px;
308
- left: 50%;
309
- transform: translateX(-50%);
310
- }
311
-
312
- .swipe-hint span {
313
- font-size: 14px;
314
- font-weight: 600;
315
- text-transform: uppercase;
316
- letter-spacing: 1px;
317
- }
318
-
319
- .video-card.swipe-approve .swipe-hint-right {
320
- opacity: 1;
321
- color: var(--accent-approve);
322
- }
323
-
324
- .video-card.swipe-reject .swipe-hint-left {
325
- opacity: 1;
326
- color: var(--accent-reject);
327
- }
328
-
329
- .video-card.swipe-escalate .swipe-hint-up {
330
- opacity: 1;
331
- color: var(--accent-escalate);
332
- }
333
-
334
- /* Key frames section */
335
- .keyframes-section {
336
- display: grid;
337
- grid-template-columns: repeat(4, 1fr);
338
- gap: 12px;
339
- }
340
-
341
- .keyframe {
342
- position: relative;
343
- aspect-ratio: 16 / 9;
344
- border-radius: 12px;
345
- overflow: hidden;
346
- border: 2px solid var(--border-subtle);
347
- cursor: pointer;
348
- transition: all var(--transition-fast);
349
- background: var(--bg-secondary);
350
- }
351
-
352
- .keyframe:hover {
353
- border-color: var(--accent-info);
354
- transform: scale(1.05);
355
- box-shadow: var(--shadow-glow);
356
- }
357
-
358
- .keyframe.active {
359
- border-color: var(--accent-info);
360
- box-shadow: var(--shadow-glow);
361
- }
362
-
363
- .keyframe img {
364
- width: 100%;
365
- height: 100%;
366
- object-fit: cover;
367
- }
368
-
369
- .keyframe-step {
370
- position: absolute;
371
- top: 8px;
372
- left: 8px;
373
- width: 24px;
374
- height: 24px;
375
- border-radius: 50%;
376
- background: var(--accent-info);
377
- color: var(--bg-primary);
378
- display: flex;
379
- align-items: center;
380
- justify-content: center;
381
- font-size: 12px;
382
- font-weight: 700;
383
- font-family: var(--font-mono);
384
- box-shadow: 0 2px 8px rgba(0, 212, 255, 0.4);
385
- }
386
-
387
- .keyframe-pulse {
388
- position: absolute;
389
- inset: 0;
390
- border-radius: 12px;
391
- animation: framePulse 2s ease-in-out infinite;
392
- pointer-events: none;
393
- }
394
-
395
- .keyframe.active .keyframe-pulse {
396
- border: 2px solid var(--accent-info);
397
- }
398
-
399
- @keyframes framePulse {
400
- 0%, 100% { opacity: 1; transform: scale(1); }
401
- 50% { opacity: 0.3; transform: scale(1.02); }
402
- }
403
-
404
- /* License plate card */
405
- .license-plate-card {
406
- background: var(--bg-card);
407
- border: 2px solid var(--border-medium);
408
- border-radius: 12px;
409
- padding: 20px;
410
- display: flex;
411
- justify-content: space-between;
412
- align-items: center;
413
- box-shadow: var(--shadow-md);
414
- }
415
-
416
- .plate-display {
417
- flex: 1;
418
- }
419
-
420
- .plate-label {
421
- font-size: 12px;
422
- color: var(--text-secondary);
423
- text-transform: uppercase;
424
- letter-spacing: 1px;
425
- margin-bottom: 8px;
426
- }
427
-
428
- .plate-number {
429
- font-size: 32px;
430
- font-weight: 700;
431
- font-family: var(--font-mono);
432
- letter-spacing: 4px;
433
- color: var(--text-primary);
434
- text-transform: uppercase;
435
- }
436
-
437
- .plate-confidence {
438
- text-align: right;
439
- }
440
-
441
- .plate-confidence-label {
442
- font-size: 12px;
443
- color: var(--text-secondary);
444
- text-transform: uppercase;
445
- letter-spacing: 1px;
446
- margin-bottom: 8px;
447
- }
448
-
449
- .plate-confidence-value {
450
- font-size: 28px;
451
- font-weight: 700;
452
- font-family: var(--font-mono);
453
- }
454
-
455
- /* Right panel - Context and Actions */
456
- .action-panel {
457
- width: 420px;
458
- background: var(--bg-secondary);
459
- border-left: 1px solid var(--border-medium);
460
- display: flex;
461
- flex-direction: column;
462
- overflow-y: auto;
463
- }
464
-
465
- /* Violation info header */
466
- .violation-header {
467
- padding: 24px;
468
- background: var(--bg-card);
469
- border-bottom: 1px solid var(--border-medium);
470
- }
471
-
472
- .violation-type {
473
- font-size: 20px;
474
- font-weight: 700;
475
- color: var(--text-primary);
476
- margin-bottom: 8px;
477
- text-transform: uppercase;
478
- letter-spacing: 0.5px;
479
- }
480
-
481
- .violation-meta {
482
- display: flex;
483
- flex-direction: column;
484
- gap: 4px;
485
- font-size: 13px;
486
- color: var(--text-secondary);
487
- }
488
-
489
- .violation-meta-item {
490
- display: flex;
491
- align-items: center;
492
- gap: 8px;
493
- }
494
-
495
- .meta-icon {
496
- width: 16px;
497
- height: 16px;
498
- opacity: 0.6;
499
- }
500
-
501
- /* Context section */
502
- .context-section {
503
- padding: 20px 24px;
504
- }
505
-
506
- .context-toggle {
507
- width: 100%;
508
- background: var(--bg-elevated);
509
- border: 1px solid var(--border-subtle);
510
- border-radius: 10px;
511
- padding: 16px;
512
- display: flex;
513
- justify-content: space-between;
514
- align-items: center;
515
- cursor: pointer;
516
- transition: all var(--transition-fast);
517
- color: var(--text-primary);
518
- font-size: 14px;
519
- font-weight: 600;
520
- }
521
-
522
- .context-toggle:hover {
523
- background: var(--bg-card);
524
- border-color: var(--border-medium);
525
- }
526
-
527
- .context-toggle.active {
528
- border-color: var(--accent-info);
529
- background: rgba(0, 212, 255, 0.05);
530
- }
531
-
532
- .context-content {
533
- max-height: 0;
534
- overflow: hidden;
535
- transition: max-height var(--transition-slow);
536
- }
537
-
538
- .context-content.expanded {
539
- max-height: 500px;
540
- }
541
-
542
- .context-grid {
543
- display: grid;
544
- gap: 12px;
545
- padding: 16px 0 0;
546
- }
547
-
548
- .context-item {
549
- display: flex;
550
- justify-content: space-between;
551
- padding: 12px;
552
- background: var(--bg-tertiary);
553
- border-radius: 8px;
554
- border: 1px solid var(--border-subtle);
555
- }
556
-
557
- .context-label {
558
- font-size: 13px;
559
- color: var(--text-secondary);
560
- }
561
-
562
- .context-value {
563
- font-size: 13px;
564
- font-weight: 600;
565
- color: var(--text-primary);
566
- font-family: var(--font-mono);
567
- }
568
-
569
- .context-alert {
570
- display: flex;
571
- align-items: center;
572
- gap: 8px;
573
- padding: 12px;
574
- background: rgba(255, 170, 0, 0.1);
575
- border: 1px solid var(--accent-warning);
576
- border-radius: 8px;
577
- color: var(--accent-warning);
578
- font-size: 13px;
579
- }
580
-
581
- /* Notes section */
582
- .notes-section {
583
- padding: 20px 24px;
584
- flex: 1;
585
- }
586
-
587
- .notes-header {
588
- display: flex;
589
- justify-content: space-between;
590
- align-items: center;
591
- margin-bottom: 12px;
592
- }
593
-
594
- .notes-label {
595
- font-size: 14px;
596
- font-weight: 600;
597
- color: var(--text-primary);
598
- }
599
-
600
- .voice-btn {
601
- width: 36px;
602
- height: 36px;
603
- border-radius: 50%;
604
- border: none;
605
- background: var(--accent-info);
606
- color: var(--bg-primary);
607
- display: flex;
608
- align-items: center;
609
- justify-content: center;
610
- cursor: pointer;
611
- transition: all var(--transition-fast);
612
- }
613
-
614
- .voice-btn:hover {
615
- transform: scale(1.1);
616
- box-shadow: 0 4px 16px rgba(0, 212, 255, 0.4);
617
- }
618
-
619
- .voice-btn.recording {
620
- background: var(--accent-reject);
621
- animation: recording-pulse 1s ease-in-out infinite;
622
- }
623
-
624
- @keyframes recording-pulse {
625
- 0%, 100% { box-shadow: 0 0 0 0 rgba(255, 51, 102, 0.7); }
626
- 50% { box-shadow: 0 0 0 10px rgba(255, 51, 102, 0); }
627
- }
628
-
629
- .notes-textarea {
630
- width: 100%;
631
- min-height: 120px;
632
- background: var(--bg-tertiary);
633
- border: 1px solid var(--border-subtle);
634
- border-radius: 10px;
635
- padding: 14px;
636
- color: var(--text-primary);
637
- font-size: 14px;
638
- font-family: var(--font-display);
639
- resize: vertical;
640
- transition: all var(--transition-fast);
641
- }
642
-
643
- .notes-textarea:focus {
644
- outline: none;
645
- border-color: var(--accent-info);
646
- background: var(--bg-card);
647
- }
648
-
649
- .recording-indicator {
650
- display: flex;
651
- align-items: center;
652
- gap: 8px;
653
- margin-top: 8px;
654
- color: var(--accent-reject);
655
- font-size: 13px;
656
- font-weight: 600;
657
- }
658
-
659
- .recording-dot {
660
- width: 8px;
661
- height: 8px;
662
- border-radius: 50%;
663
- background: var(--accent-reject);
664
- animation: dotPulse 1s ease-in-out infinite;
665
- }
666
-
667
- /* Decision buttons */
668
- .decision-section {
669
- padding: 24px;
670
- background: var(--bg-card);
671
- border-top: 1px solid var(--border-medium);
672
- }
673
-
674
- .decision-btn {
675
- width: 100%;
676
- padding: 18px;
677
- border: none;
678
- border-radius: 12px;
679
- font-size: 16px;
680
- font-weight: 700;
681
- text-transform: uppercase;
682
- letter-spacing: 1px;
683
- cursor: pointer;
684
- transition: all var(--transition-fast);
685
- display: flex;
686
- align-items: center;
687
- justify-content: center;
688
- gap: 10px;
689
- margin-bottom: 12px;
690
- position: relative;
691
- overflow: hidden;
692
- font-family: var(--font-display);
693
- }
694
-
695
- .decision-btn::before {
696
- content: '';
697
- position: absolute;
698
- inset: 0;
699
- background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.1), transparent);
700
- transform: translateX(-100%);
701
- transition: transform 0.6s;
702
- }
703
-
704
- .decision-btn:hover::before {
705
- transform: translateX(100%);
706
- }
707
-
708
- .decision-btn:active {
709
- transform: scale(0.97);
710
- }
711
-
712
- .btn-approve {
713
- background: var(--accent-approve);
714
- color: var(--bg-primary);
715
- box-shadow: 0 4px 16px rgba(0, 255, 136, 0.3);
716
- }
717
-
718
- .btn-approve:hover {
719
- background: #00dd77;
720
- box-shadow: 0 6px 24px rgba(0, 255, 136, 0.5);
721
- }
722
-
723
- .btn-reject {
724
- background: var(--accent-reject);
725
- color: white;
726
- box-shadow: 0 4px 16px rgba(255, 51, 102, 0.3);
727
- }
728
-
729
- .btn-reject:hover {
730
- background: #ee2255;
731
- box-shadow: 0 6px 24px rgba(255, 51, 102, 0.5);
732
- }
733
-
734
- .btn-escalate {
735
- background: var(--accent-escalate);
736
- color: var(--bg-primary);
737
- box-shadow: 0 4px 16px rgba(255, 170, 0, 0.3);
738
- }
739
-
740
- .btn-escalate:hover {
741
- background: #ee9900;
742
- box-shadow: 0 6px 24px rgba(255, 170, 0, 0.5);
743
- }
744
-
745
- .btn-shortcut {
746
- font-size: 11px;
747
- opacity: 0.7;
748
- font-weight: 400;
749
- letter-spacing: 0.5px;
750
- }
751
-
752
- /* Keyboard shortcuts help */
753
- .shortcuts-help {
754
- margin-top: 12px;
755
- padding: 16px;
756
- background: rgba(255, 255, 255, 0.02);
757
- border: 1px solid var(--border-subtle);
758
- border-radius: 10px;
759
- }
760
-
761
- .shortcuts-title {
762
- font-size: 12px;
763
- font-weight: 600;
764
- color: var(--text-secondary);
765
- margin-bottom: 10px;
766
- text-transform: uppercase;
767
- letter-spacing: 1px;
768
- }
769
-
770
- .shortcuts-grid {
771
- display: grid;
772
- grid-template-columns: 1fr 1fr;
773
- gap: 8px;
774
- font-size: 12px;
775
- color: var(--text-tertiary);
776
- }
777
-
778
- .shortcut-item {
779
- display: flex;
780
- align-items: center;
781
- gap: 6px;
782
- }
783
-
784
- .shortcut-key {
785
- padding: 2px 6px;
786
- background: var(--bg-tertiary);
787
- border: 1px solid var(--border-subtle);
788
- border-radius: 4px;
789
- font-family: var(--font-mono);
790
- font-size: 11px;
791
- font-weight: 600;
792
- color: var(--text-secondary);
793
- }
794
-
795
- /* Progress indicator */
796
- .progress-bar {
797
- position: fixed;
798
- top: 0;
799
- left: 0;
800
- right: 0;
801
- height: 3px;
802
- background: var(--bg-secondary);
803
- z-index: 1000;
804
- }
805
-
806
- .progress-fill {
807
- height: 100%;
808
- background: linear-gradient(90deg, var(--accent-info), var(--accent-approve));
809
- width: 0%;
810
- transition: width var(--transition-slow);
811
- }
812
-
813
- /* Stats overlay */
814
- .stats-overlay {
815
- position: fixed;
816
- top: 24px;
817
- left: 24px;
818
- background: rgba(0, 0, 0, 0.8);
819
- backdrop-filter: blur(20px);
820
- border: 1px solid var(--border-medium);
821
- border-radius: 12px;
822
- padding: 16px 20px;
823
- display: flex;
824
- gap: 24px;
825
- z-index: 100;
826
- box-shadow: var(--shadow-lg);
827
- }
828
-
829
- .stat-item {
830
- display: flex;
831
- flex-direction: column;
832
- gap: 4px;
833
- }
834
-
835
- .stat-label {
836
- font-size: 11px;
837
- color: var(--text-secondary);
838
- text-transform: uppercase;
839
- letter-spacing: 1px;
840
- }
841
-
842
- .stat-value {
843
- font-size: 20px;
844
- font-weight: 700;
845
- font-family: var(--font-mono);
846
- color: var(--text-primary);
847
- }
848
-
849
- .stat-value.highlight {
850
- color: var(--accent-info);
851
- }
852
-
853
- /* Review timer */
854
- .review-timer {
855
- position: fixed;
856
- bottom: 24px;
857
- left: 50%;
858
- transform: translateX(-50%);
859
- background: rgba(0, 0, 0, 0.9);
860
- backdrop-filter: blur(20px);
861
- border: 2px solid var(--border-medium);
862
- border-radius: 30px;
863
- padding: 12px 24px;
864
- display: flex;
865
- align-items: center;
866
- gap: 12px;
867
- z-index: 100;
868
- box-shadow: var(--shadow-lg);
869
- }
870
-
871
- .timer-icon {
872
- width: 20px;
873
- height: 20px;
874
- color: var(--accent-info);
875
- }
876
-
877
- .timer-value {
878
- font-size: 24px;
879
- font-weight: 700;
880
- font-family: var(--font-mono);
881
- color: var(--text-primary);
882
- min-width: 80px;
883
- text-align: center;
884
- }
885
-
886
- .timer-value.warning {
887
- color: var(--accent-warning);
888
- }
889
-
890
- .timer-value.danger {
891
- color: var(--accent-reject);
892
- animation: timerBlink 1s ease-in-out infinite;
893
- }
894
-
895
- @keyframes timerBlink {
896
- 0%, 100% { opacity: 1; }
897
- 50% { opacity: 0.5; }
898
- }
899
-
900
- .timer-target {
901
- font-size: 12px;
902
- color: var(--text-tertiary);
903
- font-weight: 500;
904
- }
905
-
906
- /* Success/completion animation */
907
- .completion-overlay {
908
- position: fixed;
909
- inset: 0;
910
- background: rgba(0, 0, 0, 0.95);
911
- display: none;
912
- align-items: center;
913
- justify-content: center;
914
- z-index: 2000;
915
- animation: fadeIn var(--transition-base);
916
- }
917
-
918
- .completion-overlay.show {
919
- display: flex;
920
- }
921
-
922
- @keyframes fadeIn {
923
- from { opacity: 0; }
924
- to { opacity: 1; }
925
- }
926
-
927
- .completion-content {
928
- text-align: center;
929
- animation: scaleIn var(--transition-slow);
930
- }
931
-
932
- @keyframes scaleIn {
933
- from { transform: scale(0.8); opacity: 0; }
934
- to { transform: scale(1); opacity: 1; }
935
- }
936
-
937
- .completion-icon {
938
- font-size: 80px;
939
- margin-bottom: 20px;
940
- }
941
-
942
- .completion-title {
943
- font-size: 32px;
944
- font-weight: 700;
945
- margin-bottom: 12px;
946
- }
947
-
948
- .completion-subtitle {
949
- font-size: 18px;
950
- color: var(--text-secondary);
951
- margin-bottom: 32px;
952
- }
953
-
954
- .completion-stats {
955
- display: flex;
956
- gap: 40px;
957
- justify-content: center;
958
- margin-bottom: 32px;
959
- }
960
-
961
- .completion-stat {
962
- text-align: center;
963
- }
964
-
965
- .completion-stat-value {
966
- font-size: 36px;
967
- font-weight: 700;
968
- font-family: var(--font-mono);
969
- margin-bottom: 8px;
970
- }
971
-
972
- .completion-stat-label {
973
- font-size: 13px;
974
- color: var(--text-secondary);
975
- text-transform: uppercase;
976
- letter-spacing: 1px;
977
- }
978
-
979
- .next-case-btn {
980
- padding: 18px 36px;
981
- background: var(--accent-info);
982
- border: none;
983
- border-radius: 12px;
984
- color: var(--bg-primary);
985
- font-size: 16px;
986
- font-weight: 700;
987
- text-transform: uppercase;
988
- letter-spacing: 1px;
989
- cursor: pointer;
990
- transition: all var(--transition-fast);
991
- font-family: var(--font-display);
992
- }
993
-
994
- .next-case-btn:hover {
995
- background: #00bbee;
996
- transform: translateY(-2px);
997
- box-shadow: 0 8px 24px rgba(0, 212, 255, 0.4);
998
- }
999
-
1000
- /* Scrollbar styling */
1001
- ::-webkit-scrollbar {
1002
- width: 8px;
1003
- }
1004
-
1005
- ::-webkit-scrollbar-track {
1006
- background: var(--bg-primary);
1007
- }
1008
-
1009
- ::-webkit-scrollbar-thumb {
1010
- background: var(--border-strong);
1011
- border-radius: 4px;
1012
- }
1013
-
1014
- ::-webkit-scrollbar-thumb:hover {
1015
- background: var(--border-medium);
1016
- }
1017
- </style>
1018
  </head>
1019
  <body>
1020
- <!-- Progress bar -->
1021
- <div class="progress-bar">
1022
- <div class="progress-fill" id="progressFill"></div>
 
 
 
 
 
1023
  </div>
1024
-
1025
- <!-- Stats overlay -->
1026
- <div class="stats-overlay">
1027
- <div class="stat-item">
1028
- <div class="stat-label">Reviewed Today</div>
1029
- <div class="stat-value" id="reviewedCount">127</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1030
  </div>
1031
- <div class="stat-item">
1032
- <div class="stat-label">Avg Time</div>
1033
- <div class="stat-value highlight" id="avgTime">8.4s</div>
 
 
1034
  </div>
1035
- <div class="stat-item">
1036
- <div class="stat-label">Remaining</div>
1037
- <div class="stat-value" id="remainingCount">43</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1038
  </div>
 
 
 
 
 
1039
  </div>
1040
-
1041
- <!-- Review timer -->
1042
- <div class="review-timer">
1043
- <svg class="timer-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1044
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
1045
- </svg>
1046
- <div class="timer-value" id="timerValue">00.0s</div>
1047
- <div class="timer-target">Target: <10s</div>
1048
  </div>
1049
-
1050
- <!-- Main review container -->
1051
- <div class="review-container">
1052
- <!-- Left panel -->
1053
- <div class="evidence-panel">
1054
- <!-- Video card with swipe -->
1055
- <div class="video-card" id="videoCard">
1056
- <div class="video-wrapper">
1057
- <video class="video-player" id="videoPlayer" loop>
1058
- <source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4" type="video/mp4">
1059
- </video>
1060
-
1061
- <div class="video-overlay">
1062
- <div class="video-controls">
1063
- <button class="control-btn" id="playPauseBtn">
1064
- <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
1065
- <path d="M8 5v14l11-7z"/>
1066
- </svg>
1067
- </button>
1068
- <div class="timeline" id="timeline">
1069
- <div class="timeline-progress" id="timelineProgress"></div>
1070
- <div class="timeline-marker" style="left: 35%;"></div>
1071
- </div>
1072
- <button class="control-btn" id="muteBtn">
1073
- <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
1074
- <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/>
1075
- </svg>
1076
- </button>
1077
- </div>
1078
- </div>
1079
-
1080
- <div class="confidence-badge high" id="confidenceBadge">
1081
- <span class="confidence-dot"></span>
1082
- <span id="confidenceText">94.2%</span>
1083
- </div>
1084
-
1085
- <!-- Swipe hints -->
1086
- <div class="swipe-hint swipe-hint-left">
1087
- ✕<br><span>Reject</span>
1088
- </div>
1089
- <div class="swipe-hint swipe-hint-right">
1090
- ✓<br><span>Approve</span>
1091
- </div>
1092
- <div class="swipe-hint swipe-hint-up">
1093
- ⬆<br><span>Escalate</span>
1094
- </div>
1095
- </div>
1096
- </div>
1097
-
1098
- <!-- Key frames -->
1099
- <div class="keyframes-section">
1100
- <div class="keyframe active">
1101
- <img src="https://placehold.co/320x180/1a1a2e/00ff88?text=Frame+1" alt="Key frame 1">
1102
- <div class="keyframe-step">1</div>
1103
- <div class="keyframe-pulse"></div>
1104
- </div>
1105
- <div class="keyframe">
1106
- <img src="https://placehold.co/320x180/1a1a2e/ffaa00?text=Violation" alt="Key frame 2">
1107
- <div class="keyframe-step">2</div>
1108
- </div>
1109
- <div class="keyframe">
1110
- <img src="https://placehold.co/320x180/1a1a2e/ff3366?text=License" alt="Key frame 3">
1111
- <div class="keyframe-step">3</div>
1112
- </div>
1113
- <div class="keyframe">
1114
- <img src="https://placehold.co/320x180/1a1a2e/00d4ff?text=Context" alt="Key frame 4">
1115
- <div class="keyframe-step">4</div>
1116
- </div>
1117
- </div>
1118
-
1119
- <!-- License plate -->
1120
- <div class="license-plate-card">
1121
- <div class="plate-display">
1122
- <div class="plate-label">License Plate</div>
1123
- <div class="plate-number" id="plateNumber">MH 02 AB 1234</div>
1124
- </div>
1125
- <div class="plate-confidence">
1126
- <div class="plate-confidence-label">OCR Confidence</div>
1127
- <div class="plate-confidence-value" style="color: var(--confidence-high);" id="plateConfidence">97.8%</div>
1128
- </div>
1129
- </div>
1130
  </div>
1131
-
1132
- <!-- Right panel -->
1133
- <div class="action-panel">
1134
- <!-- Violation header -->
1135
- <div class="violation-header">
1136
- <div class="violation-type" id="violationType">Red Light Running</div>
1137
- <div class="violation-meta">
1138
- <div class="violation-meta-item">
1139
- <svg class="meta-icon" fill="currentColor" viewBox="0 0 24 24">
1140
- <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
1141
- </svg>
1142
- <span id="location">MG Road Junction, Bangalore</span>
1143
- </div>
1144
- <div class="violation-meta-item">
1145
- <svg class="meta-icon" fill="currentColor" viewBox="0 0 24 24">
1146
- <path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z"/>
1147
- </svg>
1148
- <span id="timestamp">Today, 14:23:45</span>
1149
- </div>
1150
- <div class="violation-meta-item">
1151
- <svg class="meta-icon" fill="currentColor" viewBox="0 0 24 24">
1152
- <path d="M12 2C8.13 2 5 5.13 5 9c0 2.38 2.19 5.44 5 9.88 2.81-4.44 5-7.5 5-9.88 0-3.87-3.13-7-7-7z"/>
1153
- </svg>
1154
- <span id="camera">Camera: CAM_BLR_001</span>
1155
- </div>
1156
- </div>
1157
- </div>
1158
-
1159
- <!-- Context section -->
1160
- <div class="context-section">
1161
- <button class="context-toggle" id="contextToggle">
1162
- <span>Contextual Factors</span>
1163
- <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24" id="contextIcon">
1164
- <path d="M7 10l5 5 5-5z"/>
1165
- </svg>
1166
- </button>
1167
- <div class="context-content" id="contextContent">
1168
- <div class="context-grid">
1169
- <div class="context-item">
1170
- <span class="context-label">Weather</span>
1171
- <span class="context-value">Clear</span>
1172
- </div>
1173
- <div class="context-item">
1174
- <span class="context-label">Visibility</span>
1175
- <span class="context-value">5000m</span>
1176
- </div>
1177
- <div class="context-item">
1178
- <span class="context-label">Traffic Density</span>
1179
- <span class="context-value">Moderate</span>
1180
- </div>
1181
- <div class="context-item">
1182
- <span class="context-label">Time of Day</span>
1183
- <span class="context-value">Afternoon</span>
1184
- </div>
1185
- <div class="context-alert">
1186
- <svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
1187
- <path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
1188
- </svg>
1189
- <span>Road construction nearby</span>
1190
- </div>
1191
- </div>
1192
- </div>
1193
- </div>
1194
-
1195
- <!-- Notes section -->
1196
- <div class="notes-section">
1197
- <div class="notes-header">
1198
- <div class="notes-label">Notes</div>
1199
- <button class="voice-btn" id="voiceBtn">
1200
- <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
1201
- <path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/>
1202
- <path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
1203
- </svg>
1204
- </button>
1205
- </div>
1206
- <textarea class="notes-textarea" id="notesTextarea" placeholder="Add notes or use voice recording..."></textarea>
1207
- <div class="recording-indicator" id="recordingIndicator" style="display: none;">
1208
- <span class="recording-dot"></span>
1209
- <span>Recording...</span>
1210
- </div>
1211
- </div>
1212
-
1213
- <!-- Decision buttons -->
1214
- <div class="decision-section">
1215
- <button class="decision-btn btn-approve" id="approveBtn">
1216
- <svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
1217
- <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
1218
- </svg>
1219
- <span>Approve Citation</span>
1220
- <span class="btn-shortcut">→ or D</span>
1221
- </button>
1222
-
1223
- <button class="decision-btn btn-reject" id="rejectBtn">
1224
- <svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
1225
- <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
1226
- </svg>
1227
- <span>Dismiss</span>
1228
- <span class="btn-shortcut">← or A</span>
1229
- </button>
1230
-
1231
- <button class="decision-btn btn-escalate" id="escalateBtn">
1232
- <svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
1233
- <path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
1234
- </svg>
1235
- <span>Escalate to Senior</span>
1236
- <span class="btn-shortcut">↑ or W</span>
1237
- </button>
1238
-
1239
- <!-- Keyboard shortcuts -->
1240
- <div class="shortcuts-help">
1241
- <div class="shortcuts-title">Keyboard Shortcuts</div>
1242
- <div class="shortcuts-grid">
1243
- <div class="shortcut-item">
1244
- <span class="shortcut-key">Space</span>
1245
- <span>Play/Pause</span>
1246
- </div>
1247
- <div class="shortcut-item">
1248
- <span class="shortcut-key">M</span>
1249
- <span>Mute</span>
1250
- </div>
1251
- <div class="shortcut-item">
1252
- <span class="shortcut-key">→</span>
1253
- <span>Approve</span>
1254
- </div>
1255
- <div class="shortcut-item">
1256
- <span class="shortcut-key">←</span>
1257
- <span>Reject</span>
1258
- </div>
1259
- <div class="shortcut-item">
1260
- <span class="shortcut-key">↑</span>
1261
- <span>Escalate</span>
1262
- </div>
1263
- <div class="shortcut-item">
1264
- <span class="shortcut-key">C</span>
1265
- <span>Context</span>
1266
- </div>
1267
- </div>
1268
- </div>
1269
- </div>
1270
  </div>
 
1271
  </div>
1272
-
1273
- <!-- Completion overlay -->
1274
- <div class="completion-overlay" id="completionOverlay">
1275
- <div class="completion-content">
1276
- <div class="completion-icon" id="completionIcon"></div>
1277
- <div class="completion-title" id="completionTitle">Citation Approved</div>
1278
- <div class="completion-subtitle">Review completed successfully</div>
1279
- <div class="completion-stats">
1280
- <div class="completion-stat">
1281
- <div class="completion-stat-value" id="reviewTime">7.2s</div>
1282
- <div class="completion-stat-label">Review Time</div>
1283
- </div>
1284
- <div class="completion-stat">
1285
- <div class="completion-stat-value" id="sessionCount">128</div>
1286
- <div class="completion-stat-label">Session Total</div>
1287
- </div>
1288
- </div>
1289
- <button class="next-case-btn" id="nextCaseBtn">Next Case </button>
 
 
 
 
 
1290
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1291
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1292
 
1293
- <script>
1294
- // State
1295
- let reviewStartTime = Date.now();
1296
- let isRecording = false;
1297
- let isDragging = false;
1298
- let startX = 0;
1299
- let startY = 0;
1300
- let currentX = 0;
1301
- let currentY = 0;
1302
- let reviewedToday = 127;
1303
- let avgReviewTime = 8.4;
1304
- let remainingCases = 43;
1305
-
1306
- // Elements
1307
- const videoCard = document.getElementById('videoCard');
1308
- const videoPlayer = document.getElementById('videoPlayer');
1309
- const playPauseBtn = document.getElementById('playPauseBtn');
1310
- const muteBtn = document.getElementById('muteBtn');
1311
- const timeline = document.getElementById('timeline');
1312
- const timelineProgress = document.getElementById('timelineProgress');
1313
- const contextToggle = document.getElementById('contextToggle');
1314
- const contextContent = document.getElementById('contextContent');
1315
- const contextIcon = document.getElementById('contextIcon');
1316
- const voiceBtn = document.getElementById('voiceBtn');
1317
- const recordingIndicator = document.getElementById('recordingIndicator');
1318
- const approveBtn = document.getElementById('approveBtn');
1319
- const rejectBtn = document.getElementById('rejectBtn');
1320
- const escalateBtn = document.getElementById('escalateBtn');
1321
- const timerValue = document.getElementById('timerValue');
1322
- const completionOverlay = document.getElementById('completionOverlay');
1323
- const nextCaseBtn = document.getElementById('nextCaseBtn');
1324
-
1325
- // Timer update
1326
- function updateTimer() {
1327
- const elapsed = (Date.now() - reviewStartTime) / 1000;
1328
- timerValue.textContent = elapsed.toFixed(1) + 's';
1329
-
1330
- if (elapsed < 10) {
1331
- timerValue.className = 'timer-value';
1332
- } else if (elapsed < 15) {
1333
- timerValue.className = 'timer-value warning';
1334
- } else {
1335
- timerValue.className = 'timer-value danger';
1336
- }
1337
- }
1338
-
1339
- setInterval(updateTimer, 100);
1340
-
1341
- // Video controls
1342
- playPauseBtn.addEventListener('click', () => {
1343
- if (videoPlayer.paused) {
1344
- videoPlayer.play();
1345
- playPauseBtn.innerHTML = '<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/></svg>';
1346
- } else {
1347
- videoPlayer.pause();
1348
- playPauseBtn.innerHTML = '<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>';
1349
- }
1350
- });
1351
-
1352
- muteBtn.addEventListener('click', () => {
1353
- videoPlayer.muted = !videoPlayer.muted;
1354
- if (videoPlayer.muted) {
1355
- muteBtn.innerHTML = '<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/></svg>';
1356
- } else {
1357
- muteBtn.innerHTML = '<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/></svg>';
1358
- }
1359
- });
1360
-
1361
- videoPlayer.addEventListener('timeupdate', () => {
1362
- const progress = (videoPlayer.currentTime / videoPlayer.duration) * 100;
1363
- timelineProgress.style.width = progress + '%';
1364
- });
1365
-
1366
- timeline.addEventListener('click', (e) => {
1367
- const rect = timeline.getBoundingClientRect();
1368
- const pos = (e.clientX - rect.left) / rect.width;
1369
- videoPlayer.currentTime = pos * videoPlayer.duration;
1370
- });
1371
-
1372
- // Auto-play video
1373
- videoPlayer.play();
1374
-
1375
- // Context toggle
1376
- contextToggle.addEventListener('click', () => {
1377
- const isExpanded = contextContent.classList.toggle('expanded');
1378
- contextToggle.classList.toggle('active');
1379
- contextIcon.style.transform = isExpanded ? 'rotate(180deg)' : 'rotate(0)';
1380
- });
1381
-
1382
- // Voice recording
1383
- voiceBtn.addEventListener('click', () => {
1384
- isRecording = !isRecording;
1385
- voiceBtn.classList.toggle('recording', isRecording);
1386
- recordingIndicator.style.display = isRecording ? 'flex' : 'none';
1387
-
1388
- if (isRecording) {
1389
- // Simulate recording
1390
- setTimeout(() => {
1391
- const note = document.getElementById('notesTextarea');
1392
- note.value += (note.value ? '\n' : '') + '[Voice note recorded at ' + new Date().toLocaleTimeString() + ']';
1393
- voiceBtn.classList.remove('recording');
1394
- recordingIndicator.style.display = 'none';
1395
- isRecording = false;
1396
- }, 3000);
1397
- }
1398
- });
1399
-
1400
- // Swipe gesture
1401
- let swipeStartX = 0;
1402
- let swipeStartY = 0;
1403
-
1404
- videoCard.addEventListener('mousedown', (e) => {
1405
- isDragging = true;
1406
- swipeStartX = e.clientX;
1407
- swipeStartY = e.clientY;
1408
- videoCard.classList.add('dragging');
1409
- });
1410
-
1411
- document.addEventListener('mousemove', (e) => {
1412
- if (!isDragging) return;
1413
-
1414
- const deltaX = e.clientX - swipeStartX;
1415
- const deltaY = e.clientY - swipeStartY;
1416
-
1417
- videoCard.style.transform = `translateX(${deltaX}px) translateY(${deltaY}px) rotate(${deltaX * 0.05}deg)`;
1418
-
1419
- // Visual feedback
1420
- if (deltaX > 100) {
1421
- videoCard.className = 'video-card dragging swipe-approve';
1422
- } else if (deltaX < -100) {
1423
- videoCard.className = 'video-card dragging swipe-reject';
1424
- } else if (deltaY < -100) {
1425
- videoCard.className = 'video-card dragging swipe-escalate';
1426
- } else {
1427
- videoCard.className = 'video-card dragging';
1428
- }
1429
- });
1430
-
1431
- document.addEventListener('mouseup', (e) => {
1432
- if (!isDragging) return;
1433
-
1434
- const deltaX = e.clientX - swipeStartX;
1435
- const deltaY = e.clientY - swipeStartY;
1436
-
1437
- if (deltaX > 150) {
1438
- handleDecision('approve');
1439
- } else if (deltaX < -150) {
1440
- handleDecision('reject');
1441
- } else if (deltaY < -150) {
1442
- handleDecision('escalate');
1443
- } else {
1444
- videoCard.style.transform = '';
1445
- videoCard.className = 'video-card';
1446
- }
1447
-
1448
- isDragging = false;
1449
- });
1450
-
1451
- // Touch support
1452
- videoCard.addEventListener('touchstart', (e) => {
1453
- swipeStartX = e.touches[0].clientX;
1454
- swipeStartY = e.touches[0].clientY;
1455
- videoCard.classList.add('dragging');
1456
- });
1457
-
1458
- videoCard.addEventListener('touchmove', (e) => {
1459
- const deltaX = e.touches[0].clientX - swipeStartX;
1460
- const deltaY = e.touches[0].clientY - swipeStartY;
1461
-
1462
- videoCard.style.transform = `translateX(${deltaX}px) translateY(${deltaY}px) rotate(${deltaX * 0.05}deg)`;
1463
-
1464
- if (deltaX > 100) {
1465
- videoCard.className = 'video-card dragging swipe-approve';
1466
- } else if (deltaX < -100) {
1467
- videoCard.className = 'video-card dragging swipe-reject';
1468
- } else if (deltaY < -100) {
1469
- videoCard.className = 'video-card dragging swipe-escalate';
1470
- } else {
1471
- videoCard.className = 'video-card dragging';
1472
- }
1473
- });
1474
-
1475
- videoCard.addEventListener('touchend', (e) => {
1476
- const touch = e.changedTouches[0];
1477
- const deltaX = touch.clientX - swipeStartX;
1478
- const deltaY = touch.clientY - swipeStartY;
1479
-
1480
- if (deltaX > 150) {
1481
- handleDecision('approve');
1482
- } else if (deltaX < -150) {
1483
- handleDecision('reject');
1484
- } else if (deltaY < -150) {
1485
- handleDecision('escalate');
1486
- } else {
1487
- videoCard.style.transform = '';
1488
- videoCard.className = 'video-card';
1489
- }
1490
- });
1491
-
1492
- // Decision handlers
1493
- function handleDecision(decision) {
1494
- const reviewTime = ((Date.now() - reviewStartTime) / 1000).toFixed(1);
1495
-
1496
- // Update stats
1497
- reviewedToday++;
1498
- remainingCases--;
1499
- avgReviewTime = ((avgReviewTime * (reviewedToday - 1)) + parseFloat(reviewTime)) / reviewedToday;
1500
-
1501
- document.getElementById('reviewedCount').textContent = reviewedToday;
1502
- document.getElementById('avgTime').textContent = avgReviewTime.toFixed(1) + 's';
1503
- document.getElementById('remainingCount').textContent = remainingCases;
1504
-
1505
- // Update progress
1506
- const progress = (reviewedToday / (reviewedToday + remainingCases)) * 100;
1507
- document.getElementById('progressFill').style.width = progress + '%';
1508
-
1509
- // Show completion overlay
1510
- const icons = {
1511
- approve: '✓',
1512
- reject: '✕',
1513
- escalate: '⬆'
1514
- };
1515
-
1516
- const titles = {
1517
- approve: 'Citation Approved',
1518
- reject: 'Citation Dismissed',
1519
- escalate: 'Escalated to Senior Review'
1520
- };
1521
-
1522
- const colors = {
1523
- approve: 'var(--accent-approve)',
1524
- reject: 'var(--accent-reject)',
1525
- escalate: 'var(--accent-escalate)'
1526
- };
1527
-
1528
- document.getElementById('completionIcon').textContent = icons[decision];
1529
- document.getElementById('completionIcon').style.color = colors[decision];
1530
- document.getElementById('completionTitle').textContent = titles[decision];
1531
- document.getElementById('reviewTime').textContent = reviewTime + 's';
1532
- document.getElementById('sessionCount').textContent = reviewedToday;
1533
-
1534
- completionOverlay.classList.add('show');
1535
-
1536
- // Reset card position
1537
- setTimeout(() => {
1538
- videoCard.style.transform = '';
1539
- videoCard.className = 'video-card';
1540
- }, 300);
1541
- }
1542
-
1543
- approveBtn.addEventListener('click', () => handleDecision('approve'));
1544
- rejectBtn.addEventListener('click', () => handleDecision('reject'));
1545
- escalateBtn.addEventListener('click', () => handleDecision('escalate'));
1546
-
1547
- // Next case
1548
- nextCaseBtn.addEventListener('click', () => {
1549
- completionOverlay.classList.remove('show');
1550
- reviewStartTime = Date.now();
1551
-
1552
- // Load next case (simulated)
1553
- videoPlayer.currentTime = 0;
1554
- videoPlayer.play();
1555
-
1556
- // Clear notes
1557
- document.getElementById('notesTextarea').value = '';
1558
- });
1559
-
1560
- // Keyboard shortcuts
1561
- document.addEventListener('keydown', (e) => {
1562
- if (e.target.tagName === 'TEXTAREA') return;
1563
-
1564
- switch(e.key.toLowerCase()) {
1565
- case 'arrowright':
1566
- case 'd':
1567
- e.preventDefault();
1568
- handleDecision('approve');
1569
- break;
1570
- case 'arrowleft':
1571
- case 'a':
1572
- e.preventDefault();
1573
- handleDecision('reject');
1574
- break;
1575
- case 'arrowup':
1576
- case 'w':
1577
- e.preventDefault();
1578
- handleDecision('escalate');
1579
- break;
1580
- case ' ':
1581
- e.preventDefault();
1582
- playPauseBtn.click();
1583
- break;
1584
- case 'm':
1585
- e.preventDefault();
1586
- muteBtn.click();
1587
- break;
1588
- case 'c':
1589
- e.preventDefault();
1590
- contextToggle.click();
1591
- break;
1592
- }
1593
- });
1594
-
1595
- // Key frame navigation
1596
- document.querySelectorAll('.keyframe').forEach((frame, index) => {
1597
- frame.addEventListener('click', () => {
1598
- document.querySelectorAll('.keyframe').forEach(f => f.classList.remove('active'));
1599
- frame.classList.add('active');
1600
-
1601
- // Simulate jumping to that frame in video
1602
- videoPlayer.currentTime = (index / 4) * videoPlayer.duration;
1603
- });
1604
- });
1605
- </script>
1606
  </body>
1607
- </html>
 
1
+ <!doctype html>
2
  <html lang="en">
3
  <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>TrafficAI Review System Prototype</title>
7
+ <style>
8
+ :root{
9
+ --bg:#06111f;
10
+ --panel:#0d1a2b;
11
+ --panel2:#111d31;
12
+ --line:#22314a;
13
+ --text:#f6f7fb;
14
+ --sub:#9fb0c8;
15
+ --orange:#ff7a14;
16
+ --green:#22c55e;
17
+ --blue:#4688ff;
18
+ --purple:#8b5cf6;
19
+ --yellow:#f7c948;
20
+ --red:#ef4444;
21
+ }
22
+ *{box-sizing:border-box}
23
+ body{
24
+ margin:0;
25
+ font-family:Inter, Segoe UI, system-ui, -apple-system, sans-serif;
26
+ background: radial-gradient(circle at top right, #112342 0%, var(--bg) 42%);
27
+ color:var(--text);
28
+ }
29
+ .hidden{display:none !important}
30
+ .app{min-height:100vh;padding:20px}
31
+ .card, .panel{
32
+ background:linear-gradient(180deg, rgba(17,29,49,.95), rgba(10,18,31,.98));
33
+ border:1px solid var(--line);
34
+ border-radius:18px;
35
+ box-shadow:0 12px 32px rgba(0,0,0,.28);
36
+ }
37
+ .topbar{
38
+ display:flex;align-items:center;justify-content:space-between;
39
+ padding:16px 22px;margin-bottom:18px
40
+ }
41
+ .brand{display:flex;align-items:center;gap:14px}
42
+ .logo{width:42px;height:42px;border-radius:12px;background:var(--orange);display:grid;place-items:center;font-size:18px;font-weight:700}
43
+ .brand h1{font-size:18px;margin:0}
44
+ .brand p{margin:2px 0 0;color:var(--sub);font-size:13px}
45
+ .user-badge{display:flex;align-items:center;gap:14px}
46
+ .avatar{width:42px;height:42px;border-radius:50%;display:grid;place-items:center;background:linear-gradient(135deg,#3654ff,#8b5cf6);font-weight:700}
47
+ .metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:18px}
48
+ .metric{padding:20px 22px;position:relative;overflow:hidden}
49
+ .metric .accent{position:absolute;inset:0 auto 0 0;width:4px;background:var(--orange);opacity:.95}
50
+ .metric.green .accent{background:var(--green)} .metric.blue .accent{background:var(--blue)} .metric.purple .accent{background:var(--purple)}
51
+ .metric h3{margin:0 0 10px;color:#b7c4d9;font-size:14px;font-weight:600}
52
+ .metric .value{font-size:36px;font-weight:800;line-height:1}
53
+ .metric .note{font-size:13px;color:var(--sub);margin-top:8px}
54
+ .searchbar, .triage{padding:16px 18px;margin-bottom:18px}
55
+ .searchbar input{
56
+ width:100%;padding:14px 16px;border-radius:14px;border:1px solid var(--line);
57
+ background:#07131f;color:var(--text);font-size:15px;outline:none
58
+ }
59
+ .section-title{font-size:18px;font-weight:700;margin:4px 0 14px}
60
+ .triage-grid, .violations{display:grid;gap:16px}
61
+ .triage-grid{grid-template-columns:repeat(3,1fr)}
62
+ .lane{padding:18px;border-radius:16px;border:1px solid var(--line);background:rgba(8,17,31,.7)}
63
+ .lane h4{margin:0 0 8px;font-size:16px}
64
+ .lane p{margin:0;color:var(--sub);font-size:13px;line-height:1.4}
65
+ .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)}
66
+ .violations{grid-template-columns:repeat(4,1fr)}
67
+ .violation-card{padding:20px;cursor:pointer;transition:.2s transform,.2s border-color}
68
+ .violation-card:hover{transform:translateY(-2px);border-color:rgba(255,122,20,.45)}
69
+ .violation-card h4{margin:0 0 8px;font-size:18px}
70
+ .violation-meta{display:flex;justify-content:space-between;align-items:center;font-size:13px;color:var(--sub);margin:10px 0}
71
+ .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}
72
+ .bar{height:8px;border-radius:999px;background:#1b2d46;overflow:hidden}
73
+ .bar > span{display:block;height:100%;background:var(--orange);border-radius:inherit}
74
+ .login-wrap{min-height:100vh;display:grid;place-items:center;padding:24px}
75
+ .login{width:min(420px,92vw);padding:24px}
76
+ .login .brand{margin-bottom:18px}
77
+ label{display:block;color:#b7c4d9;font-size:13px;margin:12px 0 6px}
78
+ input, textarea{
79
+ width:100%;padding:13px 14px;border-radius:12px;border:1px solid var(--line);
80
+ background:#08131f;color:var(--text);outline:none;font-size:14px
81
+ }
82
+ .btn{padding:14px 18px;border:none;border-radius:14px;background:var(--orange);color:white;font-weight:700;cursor:pointer;font-size:14px}
83
+ .btn.secondary{background:transparent;border:1px solid var(--line);color:var(--text)}
84
+ .dashboard-shell,.review-shell{max-width:1440px;margin:0 auto}
85
+ .review-toolbar{display:flex;justify-content:space-between;align-items:center;padding:14px 20px;margin-bottom:18px}
86
+ .review-toolbar .left{display:flex;align-items:center;gap:18px}
87
+ .back{cursor:pointer;color:var(--sub)}
88
+ .review-toolbar h2{font-size:22px;margin:0}
89
+ .review-toolbar p{margin:2px 0 0;color:var(--sub);font-size:13px}
90
+ .stats{display:flex;gap:24px;align-items:center;font-size:13px;color:var(--sub)}
91
+ .stats strong{display:block;color:var(--text);font-size:16px}
92
+ .progress{width:140px;height:10px;background:#1a2a42;border-radius:999px;overflow:hidden}
93
+ .progress span{display:block;height:100%;width:2%;background:linear-gradient(90deg,var(--orange),#ffc56e)}
94
+ .review-grid{display:grid;grid-template-columns:1.55fr .72fr .72fr;gap:18px}
95
+ .review-col{padding:18px}
96
+ .chips{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
97
+ .chip{padding:6px 10px;border-radius:10px;font-size:12px;font-weight:600}
98
+ .chip.blue{background:rgba(70,136,255,.18);color:#87b0ff;border:1px solid rgba(70,136,255,.35)}
99
+ .chip.purple{background:rgba(139,92,246,.18);color:#c1a9ff;border:1px solid rgba(139,92,246,.35)}
100
+ .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}
101
+ .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));}
102
+ .playbar{display:flex;align-items:center;gap:14px;padding:14px 10px 2px;color:white;font-size:13px}
103
+ .playline{flex:1;height:5px;background:rgba(255,255,255,.28);border-radius:999px;overflow:hidden}
104
+ .playline span{display:block;width:33%;height:100%;background:var(--orange)}
105
+ .meta-list{display:grid;gap:10px;margin-top:14px;color:#d8e0ee}
106
+ .meta-item{display:flex;gap:10px;align-items:center;font-size:15px}
107
+ .evidence-card{padding:14px;border-radius:16px;background:#0a1323;border:1px solid var(--line);margin-bottom:12px}
108
+ .event-thumb{height:160px;border-radius:14px;background:linear-gradient(180deg,#20293b,#181f2e);display:grid;place-items:center;font-size:42px;font-weight:700}
109
+ .event-thumb.red{background:#e31f25}
110
+ .event-thumb.blue{background:linear-gradient(180deg,#4d73d6,#4065c6)}
111
+ .evidence-title{font-size:15px;font-weight:700;margin:12px 0 6px}
112
+ .muted{color:var(--sub);font-size:13px}
113
+ textarea{height:236px;resize:none}
114
+ .tags{display:flex;flex-wrap:wrap;gap:10px;margin-top:14px}
115
+ .tag{padding:8px 12px;border-radius:12px;border:1px solid var(--line);background:#0a1323;color:var(--text);cursor:pointer;font-size:13px}
116
+ .tag.active{border-color:rgba(255,122,20,.55);background:rgba(255,122,20,.14)}
117
+ .actions{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:18px}
118
+ .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}
119
+ .action small{font-size:13px;opacity:.85}
120
+ .dismiss{background:rgba(239,68,68,.08);border-color:rgba(239,68,68,.35);color:#ff9d9d}
121
+ .escalate{background:rgba(247,201,72,.09);border-color:rgba(247,201,72,.35);color:#ffe08d}
122
+ .approve{background:rgba(34,197,94,.08);border-color:rgba(34,197,94,.35);color:#86f2ae}
123
+ .tip{font-size:13px;color:var(--sub);margin-top:12px}
124
+ .notes-header{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:10px}
125
+ .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}
126
+ .voice-btn.recording{background:rgba(239,68,68,.16);border-color:rgba(239,68,68,.42);color:#ffb7b7}
127
+ .helper-copy{font-size:12px;line-height:1.45;color:var(--sub);margin-bottom:10px}
128
+ .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}
129
+ .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}
130
+ .transcript-preview strong{display:block;font-size:11px;color:var(--sub);margin-bottom:6px;text-transform:uppercase;letter-spacing:.04em}
131
+ .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}
132
+ .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}
133
+ .toast.show{display:block}
134
+ @media (max-width: 1180px){
135
+ .metrics,.violations,.review-grid,.triage-grid,.actions{grid-template-columns:1fr 1fr}
136
+ .review-grid .review-col:first-child{grid-column:1/-1}
137
+ }
138
+ @media (max-width: 820px){
139
+ .metrics,.violations,.triage-grid,.review-grid,.actions{grid-template-columns:1fr}
140
+ .video-box{font-size:72px;height:260px}
141
+ .topbar,.review-toolbar{flex-direction:column;align-items:flex-start;gap:14px}
142
+ }
143
+ </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  </head>
145
  <body>
146
+ <div id="loginScreen" class="login-wrap">
147
+ <div class="login card">
148
+ <div class="brand">
149
+ <div class="logo">🎥</div>
150
+ <div>
151
+ <h1>TrafficAI Review System</h1>
152
+ <p>Traffic Enforcement Portal</p>
153
+ </div>
154
  </div>
155
+ <label>Officer ID</label>
156
+ <input id="officerId" value="TPO-2847" />
157
+ <label>Password</label>
158
+ <input type="password" value="password" />
159
+ <div style="display:flex;gap:10px;margin-top:18px">
160
+ <button class="btn" onclick="enterDashboard()">Sign In</button>
161
+ <button class="btn secondary" onclick="enterDashboard()">Use Demo</button>
162
+ </div>
163
+ <p style="margin:14px 0 0;color:var(--sub);font-size:12px">Demo shortcut: press Enter to sign in.</p>
164
+ </div>
165
+ </div>
166
+
167
+ <div id="app" class="app hidden">
168
+ <div id="dashboardScreen" class="dashboard-shell">
169
+ <div class="topbar card">
170
+ <div class="brand">
171
+ <div class="logo">🎥</div>
172
+ <div>
173
+ <h1>TrafficAI Review System</h1>
174
+ <p>Traffic Enforcement Dashboard</p>
175
  </div>
176
+ </div>
177
+ <div class="user-badge">
178
+ <div style="text-align:right">
179
+ <div style="font-size:12px;color:var(--sub)">Officer</div>
180
+ <div id="officerLabel" style="font-size:22px;font-weight:800">TPO-2847</div>
181
  </div>
182
+ <div class="avatar">TP</div>
183
+ </div>
184
+ </div>
185
+
186
+ <div class="metrics">
187
+ <div class="metric card">
188
+ <div class="accent"></div>
189
+ <h3>Pending Reviews</h3>
190
+ <div class="value" id="pendingValue">1626</div>
191
+ <div class="note">Across all violation types</div>
192
+ </div>
193
+ <div class="metric green card">
194
+ <div class="accent"></div>
195
+ <h3>Today Reviewed</h3>
196
+ <div class="value" id="reviewedValue">127</div>
197
+ <div class="note">+23% from yesterday</div>
198
+ </div>
199
+ <div class="metric blue card">
200
+ <div class="accent"></div>
201
+ <h3>Avg Review Time</h3>
202
+ <div class="value" id="avgValue">7.8s</div>
203
+ <div class="note">Target: &lt; 10s</div>
204
+ </div>
205
+ <div class="metric purple card">
206
+ <div class="accent"></div>
207
+ <h3>System Accuracy</h3>
208
+ <div class="value">93.2%</div>
209
+ <div class="note">AI-human agreement</div>
210
+ </div>
211
+ </div>
212
+
213
+ <div class="searchbar card">
214
+ <input placeholder="Search violation types, case IDs, or locations..." />
215
+ </div>
216
+
217
+ <div class="triage card">
218
+ <div class="section-title">Suggested triage lanes</div>
219
+ <div class="triage-grid">
220
+ <div class="lane fast">
221
+ <h4>Fast lane</h4>
222
+ <p>High-confidence cases with complete evidence. Best path for sub-10 second review.</p>
223
+ </div>
224
+ <div class="lane standard">
225
+ <h4>Standard lane</h4>
226
+ <p>Normal review flow for cases that need officer validation but have no major risk flags.</p>
227
  </div>
228
+ <div class="lane exception">
229
+ <h4>Exception lane</h4>
230
+ <p>Poor visibility, emergency vehicle, unclear signage, or low OCR confidence.</p>
231
+ </div>
232
+ </div>
233
  </div>
234
+
235
+ <div>
236
+ <div class="section-title">Violation Types - Quick Access</div>
237
+ <div class="violations" id="violationGrid"></div>
 
 
 
 
238
  </div>
239
+ </div>
240
+
241
+ <div id="reviewScreen" class="review-shell hidden">
242
+ <div class="review-toolbar card">
243
+ <div class="left">
244
+ <div class="back" onclick="goDashboard()">← Dashboard</div>
245
+ <div>
246
+ <h2 id="reviewTitle">Red Light Running</h2>
247
+ <p id="caseCounter">Case 1 of 50</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  </div>
249
+ </div>
250
+ <div class="stats">
251
+ <div><span>Avg Review Time</span><strong id="sessionAvg">0.0s</strong></div>
252
+ <div><span>Reviewed</span><strong id="sessionReviewed">0</strong></div>
253
+ <div>
254
+ <span>Progress</span>
255
+ <div class="progress"><span id="reviewProgress"></span></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  </div>
257
+ </div>
258
  </div>
259
+
260
+ <div class="review-grid">
261
+ <div class="review-col panel">
262
+ <div class="chips">
263
+ <div class="chip blue" id="aiConfidence">AI: 83.5%</div>
264
+ <div class="chip purple" id="plateConfidence">Plate: 93%</div>
265
+ </div>
266
+ <div class="video-box" id="videoStage">Vehicle</div>
267
+ <div class="playbar">
268
+ <div></div>
269
+ <div class="playline"><span></span></div>
270
+ <div id="clipTime">0:03 / 0:08</div>
271
+ </div>
272
+ <div class="meta-list">
273
+ <div class="meta-item">📍 <span id="caseLocation">Banjara Hills Signal</span></div>
274
+ <div class="meta-item">📷 <span id="casePlate">TS 69 BP 4715</span></div>
275
+ <div class="meta-item">🕒 <span id="caseTimestamp">4/20/2026, 8:54:54 PM</span></div>
276
+ <div class="meta-item">🚦 Signal state: <strong>Red</strong> · Stop line crossed: <strong>Yes</strong></div>
277
+ </div>
278
+ <div class="actions">
279
+ <button class="action dismiss" onclick="takeAction('dismiss')">Dismiss <small>← A</small></button>
280
+ <button class="action escalate" onclick="takeAction('escalate')">Escalate <small>↑ S</small></button>
281
+ <button class="action approve" onclick="takeAction('approve')">Issue Citation <small>D →</small></button>
282
  </div>
283
+ <div class="tip">💡 Keyboard shortcuts: A = Dismiss · S = Escalate · D = Issue Citation</div>
284
+ </div>
285
+
286
+ <div class="review-col panel">
287
+ <div class="section-title" style="margin-top:0">Evidence Trail</div>
288
+ <div class="evidence-card">
289
+ <div class="event-thumb">Approach</div>
290
+ <div class="evidence-title">Vehicle approaching intersection</div>
291
+ <div class="muted">Context frame · 0.0s</div>
292
+ </div>
293
+ <div class="evidence-card">
294
+ <div class="event-thumb red">Violation</div>
295
+ <div class="evidence-title">Stop line crossed on red</div>
296
+ <div class="muted">Violation frame · 2.5s</div>
297
+ </div>
298
+ <div class="evidence-card">
299
+ <div class="event-thumb blue" style="font-size:32px">TS 69 BP</div>
300
+ <div class="evidence-title">Plate crop</div>
301
+ <div class="muted">OCR: TS69BP4715 · Confidence 93%</div>
302
+ </div>
303
+ </div>
304
+
305
+ <div class="review-col panel">
306
+ <div class="notes-header">
307
+ <div class="section-title" style="margin:0">Review Notes</div>
308
+ <button id="voiceBtn" class="voice-btn" onclick="toggleVoiceNote()">🎙 Start voice note</button>
309
+ </div>
310
+ <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>
311
+ <textarea id="notes" placeholder="Add notes about this case... or tap Start voice note"></textarea>
312
+ <div id="voiceStatus" class="voice-status">Ready for short dictation. Best used for exception cases that need audit-ready reasoning.</div>
313
+ <div id="transcriptPreview" class="transcript-preview"><strong>Latest transcript</strong><span id="transcriptText">No voice note captured yet.</span></div>
314
+ <div class="section-title" style="margin:16px 0 10px">Quick Tags</div>
315
+ <div class="tags">
316
+ <button class="tag" onclick="toggleTag(this)">Clear violation</button>
317
+ <button class="tag" onclick="toggleTag(this)">Unclear</button>
318
+ <button class="tag" onclick="toggleTag(this)">Emergency vehicle</button>
319
+ <button class="tag" onclick="toggleTag(this)">Poor visibility</button>
320
+ <button class="tag" onclick="toggleTag(this)">Plate mismatch</button>
321
+ </div>
322
+ <div class="footer-meta">
323
+ <div>Case ID: <span id="caseId">CASE-1776709784805-0</span></div>
324
+ <div>Current Time: <span id="timerValue">46.5s</span></div>
325
+ </div>
326
+ </div>
327
+ </div>
328
+ </div>
329
+ </div>
330
+
331
+ <div id="toast" class="toast"></div>
332
+
333
+ <script>
334
+ const violations = [
335
+ { name:'Red Light Running', count:234, time:'7.2s', accuracy:'94.5%', progress:94 },
336
+ { name:'Speeding', count:456, time:'6.8s', accuracy:'96.2%', progress:96 },
337
+ { name:'Illegal Parking', count:189, time:'8.1s', accuracy:'91.3%', progress:91 },
338
+ { name:'Wrong Way', count:98, time:'9.2s', accuracy:'89.7%', progress:89 },
339
+ { name:'No Helmet', count:167, time:'5.9s', accuracy:'97.1%', progress:97 },
340
+ { name:'Triple Riding', count:145, time:'7.5s', accuracy:'92.8%', progress:92 },
341
+ { name:'Mobile While Driving', count:203, time:'8.7s', accuracy:'88.4%', progress:88 },
342
+ { name:'No Seatbelt', count:134, time:'6.3s', accuracy:'95.6%', progress:95 }
343
+ ];
344
+
345
+ const cases = [
346
+ {
347
+ title:'Red Light Running', ai:'83.5%', plate:'93%', location:'Banjara Hills Signal', plateText:'TS 69 BP 4715',
348
+ timestamp:'4/20/2026, 8:54:54 PM', caseId:'CASE-1776709784805-0', stage:'Vehicle', clip:'0:03 / 0:08',
349
+ voiceNote:'Driver entered the intersection after the light turned red. Plate readable and stop line clearly crossed.'
350
+ },
351
+ {
352
+ title:'Speeding', ai:'91.2%', plate:'96%', location:'Outer Ring Road - Exit 4', plateText:'TS 08 CR 2201',
353
+ timestamp:'4/20/2026, 9:02:11 PM', caseId:'CASE-1776709784805-1', stage:'Speed', clip:'0:02 / 0:06',
354
+ voiceNote:'Radar overlay matches vehicle lane. Speed above posted limit and plate OCR is high confidence.'
355
+ },
356
+ {
357
+ title:'No Helmet', ai:'88.9%', plate:'90%', location:'Madhapur Main Road', plateText:'TS 11 DU 8877',
358
+ timestamp:'4/20/2026, 9:07:40 PM', caseId:'CASE-1776709784805-2', stage:'Rider', clip:'0:01 / 0:05',
359
+ voiceNote:'Rider head is fully visible without helmet. No obstruction and number plate remains readable.'
360
+ }
361
+ ];
362
+
363
+ let state = {
364
+ officer:'TPO-2847',
365
+ pending:1626,
366
+ reviewed:127,
367
+ avg:7.8,
368
+ currentCase:0,
369
+ sessionReviewed:0,
370
+ sessionSeconds:0,
371
+ timer:46.5,
372
+ recording:false
373
+ };
374
+
375
+ function renderViolations(){
376
+ const grid = document.getElementById('violationGrid');
377
+ grid.innerHTML = violations.map((v, i) => `
378
+ <div class="violation-card card" data-index="${i}">
379
+ <div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start">
380
+ <div>
381
+ <h4>${v.name}</h4>
382
+ <div style="color:var(--sub);font-size:13px">Click to review cases</div>
383
+ </div>
384
+ <div class="badge">${v.count}</div>
385
+ </div>
386
+ <div class="violation-meta"><span>Avg Time</span><strong style="color:var(--text)">${v.time}</strong></div>
387
+ <div class="violation-meta"><span>Accuracy</span><strong style="color:var(--text)">${v.accuracy}</strong></div>
388
+ <div class="bar"><span style="width:${v.progress}%"></span></div>
389
  </div>
390
+ `).join('');
391
+ grid.querySelectorAll('.violation-card').forEach(card => {
392
+ card.addEventListener('click', () => openReviewByIndex(Number(card.dataset.index)));
393
+ });
394
+ }
395
+
396
+ function showToast(message){
397
+ const toast = document.getElementById('toast');
398
+ toast.textContent = message;
399
+ toast.classList.add('show');
400
+ clearTimeout(showToast._t);
401
+ showToast._t = setTimeout(() => toast.classList.remove('show'), 1800);
402
+ }
403
+
404
+ function enterDashboard(){
405
+ state.officer = document.getElementById('officerId').value || 'TPO-2847';
406
+ document.getElementById('officerLabel').textContent = state.officer;
407
+ document.getElementById('loginScreen').classList.add('hidden');
408
+ document.getElementById('app').classList.remove('hidden');
409
+ renderViolations();
410
+ }
411
+
412
+ function goDashboard(){
413
+ document.getElementById('reviewScreen').classList.add('hidden');
414
+ document.getElementById('dashboardScreen').classList.remove('hidden');
415
+ }
416
+
417
+ function openReviewByIndex(index){
418
+ state.currentCase = index % cases.length;
419
+ hydrateCase();
420
+ document.getElementById('dashboardScreen').classList.add('hidden');
421
+ document.getElementById('reviewScreen').classList.remove('hidden');
422
+ }
423
+
424
+ function hydrateCase(){
425
+ const c = cases[state.currentCase];
426
+ document.getElementById('reviewTitle').textContent = c.title;
427
+ document.getElementById('caseCounter').textContent = `Case ${state.currentCase + 1} of 50`;
428
+ document.getElementById('aiConfidence').textContent = `AI: ${c.ai}`;
429
+ document.getElementById('plateConfidence').textContent = `Plate: ${c.plate}`;
430
+ document.getElementById('videoStage').textContent = c.stage;
431
+ document.getElementById('caseLocation').textContent = c.location;
432
+ document.getElementById('casePlate').textContent = c.plateText;
433
+ document.getElementById('caseTimestamp').textContent = c.timestamp;
434
+ document.getElementById('caseId').textContent = c.caseId;
435
+ document.getElementById('clipTime').textContent = c.clip;
436
+ document.getElementById('sessionReviewed').textContent = state.sessionReviewed;
437
+ document.getElementById('sessionAvg').textContent = state.sessionReviewed ? `${(state.sessionSeconds/state.sessionReviewed).toFixed(1)}s` : '0.0s';
438
+ document.getElementById('reviewProgress').style.width = `${Math.max(2, (state.sessionReviewed / 50) * 100)}%`;
439
+ document.getElementById('timerValue').textContent = `${state.timer.toFixed(1)}s`;
440
+ document.getElementById('notes').value = '';
441
+ document.getElementById('transcriptText').textContent = 'No voice note captured yet.';
442
+ document.getElementById('voiceStatus').textContent = 'Ready for short dictation. Best used for exception cases that need audit-ready reasoning.';
443
+ state.recording = false;
444
+ const voiceBtn = document.getElementById('voiceBtn');
445
+ voiceBtn.textContent = '🎙 Start voice note';
446
+ voiceBtn.classList.remove('recording');
447
+ document.querySelectorAll('.tag').forEach(t => t.classList.remove('active'));
448
+ }
449
+
450
+
451
+ function toggleVoiceNote(){
452
+ const btn = document.getElementById('voiceBtn');
453
+ const status = document.getElementById('voiceStatus');
454
+ const transcriptText = document.getElementById('transcriptText');
455
+ const notes = document.getElementById('notes');
456
+ const c = cases[state.currentCase];
457
+ if (!state.recording) {
458
+ state.recording = true;
459
+ btn.textContent = '⏺ Recording...';
460
+ btn.classList.add('recording');
461
+ status.textContent = 'Listening... speak a short reason such as “clear violation, plate readable, issue citation.”';
462
+ clearTimeout(toggleVoiceNote._timer);
463
+ toggleVoiceNote._timer = setTimeout(() => {
464
+ state.recording = false;
465
+ btn.textContent = '🎙 Add another voice note';
466
+ btn.classList.remove('recording');
467
+ status.textContent = 'Voice note captured and transcribed. This note will be saved with the audit trail.';
468
+ transcriptText.textContent = c.voiceNote;
469
+ notes.value = c.voiceNote;
470
+ showToast('Voice note transcribed');
471
+ }, 1200);
472
+ } else {
473
+ clearTimeout(toggleVoiceNote._timer);
474
+ state.recording = false;
475
+ btn.textContent = '🎙 Add voice note';
476
+ btn.classList.remove('recording');
477
+ status.textContent = 'Voice note stopped. Tap again to capture a new 3–5 second note.';
478
+ }
479
+ }
480
+
481
+ function takeAction(type){
482
+ const labels = {
483
+ approve:'Citation issued',
484
+ dismiss:'Case dismissed',
485
+ escalate:'Case escalated for manual review'
486
+ };
487
+ state.pending -= 1;
488
+ state.reviewed += 1;
489
+ state.sessionReviewed += 1;
490
+ state.sessionSeconds += type === 'escalate' ? 11.2 : (type === 'dismiss' ? 6.4 : 7.1);
491
+ state.avg = Math.max(6.8, ((state.avg * (state.reviewed - 1)) + (state.sessionSeconds / state.sessionReviewed)) / state.reviewed).toFixed(1);
492
+ document.getElementById('pendingValue').textContent = state.pending;
493
+ document.getElementById('reviewedValue').textContent = state.reviewed;
494
+ document.getElementById('avgValue').textContent = `${state.avg}s`;
495
+ showToast(labels[type]);
496
+ state.currentCase = (state.currentCase + 1) % cases.length;
497
+ state.timer = 42 + Math.random() * 8;
498
+ hydrateCase();
499
+ }
500
+
501
+ function toggleTag(el){ el.classList.toggle('active'); }
502
+
503
+ document.addEventListener('keydown', (e) => {
504
+ if (!document.getElementById('app').classList.contains('hidden') && document.getElementById('dashboardScreen').classList.contains('hidden')) {
505
+ if (e.key.toLowerCase() === 'a') takeAction('dismiss');
506
+ if (e.key.toLowerCase() === 's') takeAction('escalate');
507
+ if (e.key.toLowerCase() === 'd') takeAction('approve');
508
+ }
509
+ if (document.getElementById('loginScreen').classList.contains('hidden') === false && e.key === 'Enter') enterDashboard();
510
+ });
511
 
512
+ renderViolations();
513
+ </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514
  </body>
515
+ </html>