RajalashmiNagarajan commited on
Commit
86b4aeb
·
1 Parent(s): 6562178

adminpage update

Browse files
Files changed (36) hide show
  1. src.zip +2 -2
  2. src/app/case-details-page/case-details-page.component.css +1258 -799
  3. src/app/case-details-page/case-details-page.component.html +83 -69
  4. src/app/case-details-page/case-details-page.component.ts +115 -76
  5. src/app/case-details-summary-page/case-details-summary-page.component.css +2254 -349
  6. src/app/case-details-summary-page/case-details-summary-page.component.html +334 -80
  7. src/app/case-details-summary-page/case-details-summary-page.component.ts +1010 -440
  8. src/app/homepage/homepage.component.css +97 -38
  9. src/app/homepage/homepage.component.html +47 -37
  10. src/app/homepage/homepage.component.ts +1 -1
  11. src/app/infopage/infopage.component.css +0 -0
  12. src/app/infopage/infopage.component.html +227 -173
  13. src/app/infopage/infopage.component.ts +0 -0
  14. src/app/py-detect/py-detect.component.css +940 -1015
  15. src/app/py-detect/py-detect.component.html +291 -169
  16. src/app/py-detect/py-detect.component.ts +204 -204
  17. src/app/recordpage/recordpage.component.css +758 -1741
  18. src/app/recordpage/recordpage.component.html +231 -246
  19. src/app/recordpage/recordpage.component.ts +409 -409
  20. src/app/shared/case-store.service.ts +4 -0
  21. src/app/shared/infopage-sections.ts +34 -0
  22. src/assets/1-old.JPG +0 -3
  23. src/assets/9.JPG +0 -3
  24. src/assets/background-2.jpg +0 -3
  25. src/assets/background-21.jpg +0 -3
  26. src/assets/background.jpg +0 -3
  27. src/assets/background1.jpg +0 -3
  28. src/assets/background1234.jpg +0 -3
  29. src/{app/homepage/sign-up-1.zip → assets/coverimage.png} +2 -2
  30. src/assets/hero-jpg.jpg +0 -3
  31. src/assets/{hero.png → hero1.png} +0 -0
  32. src/assets/{home.png → home1.png} +0 -0
  33. src/assets/side-2scale-right.jpg +0 -3
  34. src/assets/signin.png +0 -3
  35. src/assets/signup.png +0 -3
  36. src/styles.css +2 -2
src.zip CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:1819db358c2c3e8e40f189817c01f35259ca5c611a797cd3ba3a62189f5c5a30
3
- size 34011296
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:33012a38b94a3ec9be05092233cda9e45e53e9dd90e00e9cfb27658cf088922b
3
+ size 38836504
src/app/case-details-page/case-details-page.component.css CHANGED
@@ -1,1046 +1,1505 @@
1
  @import '../recordpage/recordpage.component.css';
2
 
3
- body, html {
4
- overflow-y: hidden !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  }
6
 
7
- body, main.content {
 
 
 
 
8
  background: #f4f6fa;
9
- min-height: 100vh;
10
- margin: 0;
11
- padding: 0;
12
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
13
  }
14
 
 
 
 
 
 
 
 
 
15
 
16
- /* Modern UI header styles from infopage */
17
  .site-header {
18
- background: #011329;
19
- box-shadow: 0 2px 12px #38bdf844;
20
- margin-bottom: 0;
21
- position: relative;
22
- z-index: 10;
23
- padding-bottom: 0;
24
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  .header-inner {
27
  display: flex;
28
  align-items: center;
29
  justify-content: space-between;
30
- padding: 18px 32px 0 32px;
31
- position: relative;
 
 
 
32
  }
33
 
34
  .logo-cluster {
35
  display: flex;
36
  align-items: center;
37
- gap: 18px;
38
  }
39
 
40
  .logo-img-header {
41
- width: 54px;
42
- height: 54px;
43
  border-radius: 50%;
44
- background: #fff;
45
- box-shadow: 0 2px 8px rgba(0,0,0,0.18);
46
- padding: 4px;
47
- margin-top: -6px;
48
- margin-bottom: 1vh;
49
  }
50
 
 
 
 
 
51
  .py-detect-title-header {
52
- font-size: 2.1rem;
53
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
54
  font-weight: 900;
55
- letter-spacing: 6px;
56
  color: #38bdf8;
57
  display: flex;
58
  align-items: center;
59
  gap: 2px;
60
- margin-bottom: 1.5vh;
61
  }
62
 
63
- .py-detect-title-header .py-letter.p {
64
- color: #e3f6ff;
65
- text-shadow: 0 0 6px #38bdf8;
66
- }
67
-
68
- .py-detect-title-header .py-letter.y {
69
- color: #38bdf8;
70
- text-shadow: 0 0 6px #38bdf8;
71
- }
72
-
73
- .py-detect-title-header .py-shape {
74
- color: #e3f6ff;
75
- background: #e3f6ff;
76
- text-shadow: 0 0 6px #38bdf8;
77
- box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff;
78
- border: 2px solid #23272b;
79
- width: 18px;
80
- height: 4px;
81
- display: inline-block;
82
- margin: 0 8px;
83
- border-radius: 2px;
84
- }
85
-
86
- .py-detect-title-header .py-letter.d {
87
- color: #e3f6ff;
88
- text-shadow: 0 0 6px #38bdf8;
89
- }
90
-
91
- .py-detect-title-header .py-letter.e {
92
- color: #38bdf8;
93
- text-shadow: 0 0 6px #38bdf8;
94
- }
95
-
96
- .py-detect-title-header .py-letter.t {
97
- color: #e3f6ff;
98
- text-shadow: 0 0 6px #38bdf8;
99
- }
100
-
101
- .py-detect-title-header .py-letter.e2 {
102
- color: #38bdf8;
103
- text-shadow: 0 0 6px #38bdf8;
104
- }
105
-
106
  .py-detect-title-header .py-letter.c {
107
  color: #e3f6ff;
108
  text-shadow: 0 0 6px #38bdf8;
109
  }
110
 
 
 
 
111
  .py-detect-title-header .py-letter.t2 {
112
  color: #38bdf8;
113
  text-shadow: 0 0 6px #38bdf8;
114
  }
115
 
116
-
117
- .case-details-list {
118
- width: 100%;
119
- max-width: 1700px;
120
- margin: 0 auto;
121
- background: #fff;
122
- border-radius: 8px;
123
- box-shadow: 0 4px 32px rgba(30,41,59,0.12);
124
- padding: 32px 48px;
125
  }
126
 
127
- .case-details-item {
128
- border-bottom: 1px solid #edf2f7;
129
- padding: 24px 0;
 
130
  }
131
 
132
- .case-details-item:last-child {
133
- border-bottom: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  }
135
 
136
- h2 {
137
- color: #38bdf8;
138
- font-size: 2.1rem;
139
- font-weight: 800;
140
- margin-bottom: 32px;
141
- text-align: center;
142
- }
143
 
144
- h3 {
145
- color: #23272b;
146
- font-size: 1.2rem;
147
- font-weight: 700;
148
- margin-bottom: 12px;
 
 
 
 
 
 
 
 
 
 
 
149
  }
150
 
151
- /*p {
152
- margin: 4px 0;
153
- color: #334155;
154
- }*/
 
155
 
156
- hr {
157
- margin: 24px 0 0 0;
158
- border: none;
159
- border-top: 1px solid #e2e8f0;
160
  }
161
 
162
- .welcome-user {
163
- position: absolute;
164
- top: 24px;
165
- left: 32px;
166
- font-size: 1.1rem;
167
- color: #ffffff;
168
- font-weight: 600;
169
- z-index: 2;
170
  }
171
 
172
- .welcome-user-fixed {
173
- position: absolute;
174
- top: 24px;
175
- left: 32px;
176
- font-size: 1.1rem;
177
- color: #334155;
178
- font-weight: 600;
179
- z-index: 2;
180
  }
181
 
182
- .page-title-centered {
183
- text-align: center;
184
- margin-top: 24px;
185
- font-size: 2.1rem;
186
- font-weight: 800;
187
- color: #38bdf8;
 
 
 
 
 
 
 
188
  }
189
 
190
- .wrapper.records-center {
191
- position: relative;
192
- }
193
 
194
- .welcome-left {
195
- text-align: left;
196
- font-size: 2.1rem;
197
- font-weight: 800;
198
- color: #38bdf8;
199
- margin-bottom: 32px;
200
- margin-top: 0;
201
  }
202
 
203
- .table-wrap {
204
- position: relative;
 
 
 
205
  }
206
 
207
- .welcome-left {
208
- font-size: 2.1rem;
209
- font-weight: 800;
210
- color: #38bdf8;
211
- margin-bottom: 16px;
212
- text-align: left;
213
- position: relative;
214
- right: 32vw;
215
  }
216
 
217
- .modal-case-details {
218
- background: #f8fbfd;
219
- border-radius: 18px;
220
- box-shadow: 0 20px 60px rgba(0,0,0,.25);
221
- padding: 0;
222
- max-width: 540px;
223
- min-width: 340px;
224
  }
225
 
226
- .modal-header-case {
227
- background: #f5fafd;
228
- border-bottom: 1px solid #e2e8f0;
229
- padding: 24px 24px 12px 24px;
230
- }
231
 
232
- .modal-header-case h2 {
233
- font-size: 1.6rem;
234
- font-weight: 800;
235
- color: #23272b;
236
- margin: 0;
237
  }
238
 
239
- .modal-body-case {
240
- padding: 18px 24px 8px 24px;
241
- background: #f8fbfd;
242
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
- .modal-footer-case {
245
- background: #f5fafd;
246
- border-top: 1px solid #e2e8f0;
247
- padding: 16px 24px;
248
- text-align: left;
249
- }
250
 
251
- .detail-row {
252
- display: grid;
253
- grid-template-columns: 160px 1fr;
254
- gap: 8px;
255
- padding: 6px 0;
256
- color: #334155;
257
  }
258
 
259
- .detail-row span {
260
- color: #1e293b;
261
- font-weight: 700;
262
- min-width: 180px;
263
- font-size: 1.08em;
264
- margin-right: 32px;
265
- text-align: left;
266
- display: inline-block;
 
 
 
 
 
267
  }
268
 
269
- .detail-row b {
270
- color: #2563eb;
271
- font-weight: 700;
272
- word-break: break-word;
273
- font-size: 1.13em;
274
- margin-left: 8px;
275
- text-align: left;
276
- display: inline-block;
277
- }
278
 
279
- .bold-value {
280
- font-weight: 700 !important;
281
- color: #23272b !important;
282
  }
283
 
284
- .detail-block .label {
285
- font-weight: 700;
 
 
 
 
286
  margin-bottom: 6px;
287
- color: #23272b;
288
  }
289
 
290
- hr {
291
- margin: 18px 0 12px 0;
292
- border: none;
293
- border-top: 1px solid #e2e8f0;
 
294
  }
295
 
296
- /* Modal for case details (match record page style) */
297
- .modal-blur-overlay {
298
- position: fixed;
299
- inset: 0;
300
- z-index: 199;
301
- background: rgba(255,255,255,0.45);
302
- backdrop-filter: blur(8px);
303
- pointer-events: none;
304
- display: block;
305
- }
306
 
307
- .modal {
308
- background: #fff;
309
- border-radius: 10px;
310
- box-shadow: 0 2px 8px #0001, 0 1.5px 0 #e5e7eb;
311
- border: 1.5px solid #e5e7eb;
312
- overflow: hidden;
313
- position: fixed;
314
- top: 50%;
315
- left: 50%;
316
- transform: translate(-50%, -50%);
317
- z-index: 1001;
318
- width: 90%;
319
- max-width: 600px;
320
- display: flex;
321
- flex-direction: column;
322
- animation: fadeIn 0.3s ease-out;
323
- }
324
 
325
- .modal-header {
326
- background: #f8fafc;
327
- border-radius: 10px 10px 0 0;
328
- border-bottom: 1.5px solid #e5e7eb;
329
- padding: 16px;
 
 
 
330
  display: flex;
331
- justify-content: space-between;
332
  align-items: center;
 
 
 
333
  }
334
 
335
- .modal-header h2 {
336
- font-size: 1.4rem;
337
- font-weight: 700;
338
- margin: 0;
339
- color: #23272b;
340
  }
341
 
342
- .modal-body {
343
- padding: 16px;
344
- background: #fff;
345
- flex: 1;
346
- overflow-y: auto;
347
- color: #23272b;
348
  }
349
 
350
- .modal-footer {
351
- background: #f8fafc;
352
- border-radius: 0 0 10px 10px;
353
- border-top: 1.5px solid #e5e7eb;
354
- padding: 12px;
355
- text-align: right;
356
  }
357
 
358
- .btn {
359
- background: #38bdf8;
360
- color: #fff;
361
- padding: 10px 20px;
362
- border: none;
363
- border-radius: 4px;
364
- font-size: 1rem;
365
- cursor: pointer;
366
- transition: background 0.3s;
367
  }
368
 
369
- .btn:hover {
370
- background: #34b3e0;
 
 
 
 
 
 
 
371
  }
372
 
373
- .actions {
 
374
  display: flex;
375
- flex-direction: row;
376
- justify-content: center;
377
  align-items: center;
378
- gap: 18px;
379
- flex-wrap: nowrap;
380
- width: 100%;
 
 
 
381
  }
382
 
383
- .btn.view {
384
- background: #64748b;
385
- color: #fff;
386
- border: none;
387
- border-radius: 12px;
388
- font-weight: 600;
389
- font-size: 1.08rem;
390
- padding: 12px 32px;
391
- box-shadow: 0 2px 8px #2563eb22;
392
- transition: background 0.2s, box-shadow 0.2s;
393
- }
394
- .btn.view:hover {
395
- background: #1d4ed8;
396
- box-shadow: 0 4px 16px #2563eb44;
397
- }
398
 
399
- .btn.edit {
400
- background: #8b5cf6;
401
- color: #fff;
402
- border: none;
403
- border-radius: 12px;
404
- font-weight: 600;
405
- font-size: 1.08rem;
406
- padding: 12px 32px;
407
- box-shadow: 0 2px 8px #8b5cf622;
408
- transition: background 0.2s, box-shadow 0.2s;
409
- margin-left: 0;
410
- }
411
- .btn.edit:hover {
412
- background: #7c3aed;
413
- box-shadow: 0 4px 16px #8b5cf644;
414
- }
 
 
 
 
415
 
416
- .actions-col {
417
- text-align: center;
418
- vertical-align: middle;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  }
420
 
421
- .records th.actions-col,
422
- .records td.actions {
423
- width: 24% !important;
424
- min-width: 313px;
425
- max-width: 320px;
426
  }
427
 
428
- .next-action-col {
429
- text-align: left;
430
- min-width: 120px;
431
- padding-left: 0;
432
- padding-right: 0;
433
- font-weight: 500;
434
- color: #2563eb;
435
- }
436
 
437
- @media (max-width: 700px) {
438
- .actions .btn {
439
- min-width: 120px;
440
- max-width: 160px;
441
- font-size: 1rem;
442
- padding: 8px 10px;
443
  }
444
- .records th.actions-col,
445
- .records td.actions {
446
- min-width: 140px;
447
- max-width: 180px;
448
- width: 36% !important;
449
  }
 
 
 
 
 
 
 
 
 
 
 
450
  }
451
 
452
- @media (max-width: 900px) {
453
- .btn.view, .btn.edit {
454
- padding: 10px 16px;
455
- font-size: 1rem;
 
456
  }
457
- .records th.actions-col,
458
- .records td.actions {
459
- min-width: 120px;
460
- max-width: 180px;
461
- width: 36% !important;
 
 
 
 
 
 
 
462
  }
463
- }
464
 
 
 
 
465
 
 
 
 
 
 
 
 
466
 
467
- /* table layout for case details page */
468
- .record-table {
469
- width: 100%;
470
- table-layout: auto;
471
- border-collapse: collapse;
472
- overflow-x: hidden;
473
- box-sizing: border-box;
474
- }
475
 
476
- .record-table th, .record-table td {
477
- padding-left: 12px;
478
- padding-right: 12px;
479
- word-break: break-word;
480
- white-space: normal;
481
- box-sizing: border-box;
482
- border-bottom: 1px solid #e5e7eb; /* consistent row border */
483
- }
484
 
485
- .record-table tr:last-child td {
486
- border-bottom: none;
 
 
 
 
 
 
 
 
 
487
  }
488
 
489
- th.actions, td.actions {
490
- text-align: left;
491
- padding-left: 0;
492
- padding-right: 0;
493
- }
494
 
495
- td.actions {
496
- vertical-align: middle;
497
- }
498
 
499
- .icon-btn.view {
500
- margin: 0;
501
- padding: 0;
502
- background: none;
503
- border: none;
504
- cursor: pointer;
505
  color: #2563eb;
506
- font-size: 1.4em;
507
- display: flex;
508
- align-items: center;
509
- justify-content: flex-start;
510
  }
511
 
512
- .custom-active-cases-label {
513
- font-weight: 800;
514
- color: #2196f3;
515
- font-size: 2rem;
516
- margin-bottom: 18px;
517
- margin-top: 0;
518
- text-align: left;
519
- position: relative;
520
- right: 32vw;
 
 
 
 
 
 
521
  }
522
 
523
- .filter-bar select,
524
- .filter-bar .filter-date {
525
- padding: 6px 18px;
526
- border-radius: 6px;
527
- border: 1.5px solid #cbd5e1;
528
- font-size: 1em;
529
- color: #2563eb;
530
- background: #fff;
531
- font-weight: 600;
532
- outline: none;
533
- transition: border 0.15s;
534
- min-width: 160px;
535
- margin-right: 4px;
536
  }
537
- .filter-bar select:focus,
538
- .filter-bar .filter-date:focus {
539
- border: 1.5px solid #38bdf8;
 
540
  }
541
- .date-group {
542
- display: inline-flex;
543
- align-items: center;
544
- font-family: inherit;
545
- color: #a86a00;
546
- font-size: 1.08em;
547
- font-weight: 500;
548
- margin-right: 8px;
549
- gap: 2px;
550
  }
551
- .date-label {
552
- margin: 0 4px 0 4px;
 
553
  display: inline-flex;
554
  align-items: center;
555
- gap: 2px;
556
- }
557
- .calendar-ico {
558
- font-size: 1.1em;
559
- margin-left: 2px;
560
- vertical-align: middle;
561
- }
562
- .filter-date {
563
- border: none;
564
- background: transparent;
565
- color: #a86a00;
566
- font-size: 1em;
567
  font-weight: 600;
568
- outline: none;
569
- min-width: 90px;
570
- margin-left: 2px;
571
  }
572
- .filter-date::-webkit-input-placeholder { color: #a86a00; }
573
- .filter-date:-moz-placeholder { color: #a86a00; }
574
- .filter-date::-moz-placeholder { color: #a86a00; }
575
- .filter-date:-ms-input-placeholder { color: #a86a00; }
576
 
577
- .fullpage-details {
578
- width: 100vw;
579
- min-height: 100vh;
580
- background: #f8fafc;
581
- padding: 32px 0 32px 0;
582
- position: relative;
583
- z-index: 1;
584
  }
585
- .details-content {
586
- max-width:
587
- ;
588
- margin: 0 auto;
589
- background: #fff;
590
- border-radius: 18px;
591
- box-shadow: 0 8px 32px rgba(30,41,59,0.12);
592
- padding: 32px 48px;
593
- }
594
- .btn.back-btn {
595
- margin: 0 0 24px 0;
596
- background: #64748b;
597
- color: #fff;
598
- border-radius: 8px;
599
- font-weight: 600;
600
- font-size: 1.1rem;
601
- padding: 10px 28px;
602
- border: none;
603
- cursor: pointer;
604
  }
605
 
606
- .fullpage-popup-overlay {
607
- position: fixed;
608
- top: 0; left: 0; right: 0; bottom: 0;
609
- width: 100vw;
610
- height: 100vh;
611
- background: #f8fafc;
612
- z-index: 9999;
613
- display: flex;
614
- align-items: flex-start;
615
- justify-content: center;
616
- overflow-y: auto;
617
- overscroll-behavior: contain;
618
  }
619
- body.popup-open {
620
- overflow: hidden !important;
 
 
 
621
  }
622
 
623
- .fullpage-popup-content {
624
- background: #fff;
625
- border-radius: 18px;
626
- box-shadow: 0 8px 32px rgba(30,41,59,0.18);
627
- margin: 40px 0;
628
- max-width: 1200px;
629
- width: 90vw;
630
- min-height: 80vh;
631
- padding: 32px 48px;
632
- position: relative;
633
  }
634
 
635
- /* --- Section Card Styles --- */
636
- .details-section-card {
637
- background: #fff;
638
- border-radius: 22px;
639
- box-shadow: 0 4px 24px rgba(30,41,59,0.10);
640
- border-left: 6px solid #38bdf8;
641
- border-right: 2px solid #e0f2fe;
642
- padding: 32px 36px 32px 36px;
643
- margin-bottom: 0;
644
- margin-top: 32px;
645
- animation: fadeInUp 0.6s cubic-bezier(.23,1.01,.32,1) both;
 
 
 
 
 
 
646
  }
647
- @keyframes fadeInUp {
648
- from { opacity: 0; transform: translateY(32px); }
649
- to { opacity: 1; transform: translateY(0); }
 
 
 
650
  }
651
 
652
- .section-title {
653
- font-size: 1.5rem;
654
- font-weight: 700;
655
- color: #2563eb;
656
- margin-bottom: 18px;
657
- letter-spacing: 0.5px;
658
  }
659
 
660
- .subgroup-pills {
 
 
 
 
 
661
  display: flex;
662
- gap: 18px;
663
- margin-bottom: 28px;
664
- flex-wrap: wrap;
 
 
665
  }
666
- .subgroup-pills button {
667
- background: linear-gradient(90deg, #2563eb 0%, #38bdf8 100%);
668
- color: #fff;
669
- font-weight: 700;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
670
  border: none;
671
- border-radius: 22px;
672
- padding: 10px 32px;
673
- font-size: 1.08em;
674
- box-shadow: 0 2px 12px rgba(56,189,248,0.13);
675
  cursor: pointer;
676
- transition: background 0.18s, box-shadow 0.18s, transform 0.18s;
677
- outline: none;
678
- margin-bottom: 4px;
 
 
 
679
  }
680
- .subgroup-pills button.active,
681
- .subgroup-pills button:focus {
682
- background: linear-gradient(90deg, #38bdf8 0%, #2563eb 100%);
683
- box-shadow: 0 4px 16px rgba(56,189,248,0.18);
684
- transform: translateY(-2px) scale(1.04);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
685
  }
686
 
687
- .fields-table-2col {
 
688
  display: flex;
689
- flex-direction: row;
690
- gap: 0;
691
- margin-top: 8px;
692
- margin-bottom: 8px;
693
- }
694
- .fields-col {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
695
  display: flex;
696
- flex-direction: column;
697
- gap: 6px;
698
- }
699
- .fields-col-labels {
700
- min-width: 260px;
701
- text-align: left;
702
- }
703
- .fields-col-values {
704
- min-width: 220px;
705
- text-align: right;
706
  }
707
- .field-label {
708
- color: #22223b;
 
 
 
 
 
709
  font-weight: 500;
710
- font-size: 1.08em;
711
- padding: 2px 0;
712
- }
713
- .field-value {
714
- color: #22223b;
715
- font-weight: 700;
716
- font-size: 1.08em;
717
- padding: 2px 0;
718
- letter-spacing: 0.5px;
719
  }
720
 
721
- /* --- Card Container and Title --- */
722
- .case-details-title {
723
- font-size: 2.2rem;
724
- font-weight: 700;
725
- color: #22223b;
726
- margin-bottom: 32px;
727
- margin-left: 8px;
728
  display: flex;
729
  align-items: center;
730
- justify-content: space-between;
731
- position: relative;
 
732
  }
733
 
734
- .btn.close-btn {
735
- background: none;
736
- color: #64748b;
737
- border: none;
738
- font-size: 2.2rem;
739
- font-weight: 700;
740
- cursor: pointer;
741
- position: absolute;
742
- right: 0;
743
- top: 0;
744
- line-height: 1;
745
- padding: 0 16px;
746
- transition: color 0.15s;
 
 
 
 
 
 
 
747
  }
748
- .btn.close-btn:hover {
749
- color: #2563eb;
 
 
 
750
  }
751
 
752
- .btn.close-btn-bottom {
753
- position: fixed;
754
- right: 326px;
755
- top: 46px;
756
- background: #2563eb;
757
- color: #fff;
758
- border: none;
759
- border-radius: 50%;
760
- width: 48px;
761
- height: 48px;
762
- font-size: 2.2rem;
763
- font-weight: 700;
764
- box-shadow: 0 4px 16px rgba(56,189,248,0.18);
765
- cursor: pointer;
766
- z-index: 10001;
767
  display: flex;
768
  align-items: center;
769
- justify-content: center;
770
- transition: background 0.18s, color 0.18s, box-shadow 0.18s;
 
 
771
  }
772
- .btn.close-btn-bottom:hover {
773
- background: #38bdf8;
774
- color: #22223b;
775
  }
776
 
777
- .progress-col {
778
- text-align: left;
779
- min-width: 90px;
780
- padding-left: 0;
781
- padding-right: 0;
782
- }
 
 
 
 
 
 
 
 
 
 
 
783
 
784
- .progress-dot {
785
- width: 18px;
786
- height: 18px;
787
- border-radius: 50%;
788
- display: inline-block;
789
- margin-right: 6px;
790
- vertical-align: middle;
791
- box-shadow: 0 2px 8px rgba(56,189,248,0.10);
792
- }
793
- .progress-dot.blue {
794
- background: linear-gradient(135deg, #2563eb 0%, #38bdf8 100%);
795
- }
796
- .progress-dot.green {
797
- background: linear-gradient(135deg, #34d399 0%, #6ee7b7 100%);
798
- }
799
- .progress-check {
800
- font-size: 1.2em;
801
- color: #34d399;
802
- background: #d1fae5;
803
- border-radius: 4px;
804
- padding: 2px 4px;
805
- display: inline-block;
806
- margin-right: 6px;
807
- vertical-align: middle;
808
- }
809
- .progress-value {
810
- font-weight: 700;
811
- font-size: 1em;
812
- color: #22223b;
813
- vertical-align: middle;
814
  }
815
 
816
- .priority-pill {
817
- display: inline-flex;
 
 
 
 
 
818
  align-items: center;
819
- font-weight: bold;
820
- font-size: 0.98em;
821
- padding: 2px 12px 2px 8px;
822
- border-radius: 16px;
823
- margin-right: 2px;
824
- letter-spacing: 0.02em;
825
  }
826
- .priority-high {
827
- background: #fee2e2;
828
- color: #b91c1c;
 
 
 
 
 
 
 
 
 
 
 
 
829
  }
830
- .priority-medium {
831
- background: #fef9c3;
832
- color: #ca8a04;
 
 
 
 
 
 
 
 
 
 
833
  }
834
- .priority-low {
835
- background: #dcfce7;
836
- color: #15803d;
 
 
 
 
 
 
 
 
 
 
 
 
837
  }
838
 
 
839
  .evidence-upload-section {
840
  margin-top: 32px;
841
- padding: 16px;
842
- background: #f3f4f6;
843
  border-radius: 12px;
844
- box-shadow: 0 1px 4px rgba(0,0,0,0.04);
845
- }
846
- .evidence-upload-section h3 {
847
- margin-bottom: 12px;
848
- font-size: 1.1em;
849
- color: #2563eb;
850
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
851
  .evidence-list {
852
- margin-top: 10px;
853
  }
 
854
  .evidence-file {
855
  display: flex;
856
  align-items: center;
857
- font-size: 0.98em;
858
- margin-bottom: 6px;
859
- color: #374151;
860
- }
861
- .evidence-file i {
862
- margin-right: 8px;
863
- color: #2563eb;
864
  }
865
 
 
 
 
 
866
 
867
- /* Footer */
868
- footer {
869
- background: linear-gradient(to right, #011022, #01030a);
870
- color: #fff;
871
- text-align: center;
872
- padding: 10px 0px;
873
- position: fixed;
874
- bottom: 0;
875
- left: 0;
876
- width: 100%;
877
- z-index: 100;
878
- margin-top: 0;
 
 
879
  }
880
 
881
- /* Additional Styles for Go Detect button/icon */
882
- .icon-btn.detect {
883
- background: #e3f2fd;
884
- color: #1976d2;
885
- border-radius: 50%;
886
- box-shadow: 0 2px 8px rgba(25, 118, 210, 0.15);
887
- margin-left: 4px;
888
- position: relative;
889
- transition: box-shadow 0.2s, transform 0.2s;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
890
  }
891
- .icon-btn.detect:enabled:hover {
892
- box-shadow: 0 4px 16px rgba(25, 118, 210, 0.25);
893
- transform: scale(1.08);
 
 
 
 
 
 
 
 
 
 
894
  }
895
- .icon-btn.detect i {
896
- font-size: 1.35em;
897
- animation: detectPulse 1.2s infinite;
 
 
 
 
 
 
 
 
 
898
  }
899
- @keyframes detectPulse {
900
- 0% { transform: scale(1); color: #1976d2; }
901
- 50% { transform: scale(1.25); color: #42a5f5; }
902
- 100% { transform: scale(1); color: #1976d2; }
 
 
 
 
903
  }
904
 
905
- .header-actions {
906
  display: flex;
907
  align-items: center;
908
- gap: 16px;
 
909
  }
910
- .header-btn.detect-header-btn {
911
- background: #1976d2;
912
- color: #fff;
913
- border: none;
914
- border-radius: 8px;
915
- padding: 8px 20px;
916
- font-size: 1.08em;
 
 
 
 
 
 
 
 
 
 
917
  font-weight: 500;
918
- box-shadow: 0 2px 8px rgba(25, 118, 210, 0.10);
919
- cursor: pointer;
920
- transition: background 0.2s, transform 0.2s;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
921
  display: flex;
922
  align-items: center;
923
- gap: 8px;
924
- position: relative;
925
- }
926
- .header-btn.detect-header-btn:hover {
927
- background: #1565c0;
928
- transform: scale(1.04);
929
  }
930
- .header-btn.detect-header-btn i {
931
- font-size: 1.2em;
932
- animation: detectPulse 1.2s infinite;
933
  }
934
 
935
- /* Enhanced styles for the Go Detect button */
936
- .detect-btn {
937
- background: linear-gradient(90deg, #1976d2 0%, #38bdf8 100%);
938
- color: #fff;
939
- border: none;
940
- border-radius: 24px;
941
- font-weight: 700;
942
- font-size: 1.08em;
943
- padding: 5px 28px;
944
- box-shadow: 0 2px 12px rgba(25, 118, 210, 0.13);
945
- cursor: pointer;
946
- transition: background 0.18s, box-shadow 0.18s, transform 0.18s;
947
- outline: none;
948
- margin-left: 4px;
949
  position: relative;
950
- overflow: hidden;
951
- z-index: 1;
952
- animation: detectBtnPulse 1.6s infinite alternate;
953
- }
954
- .detect-btn:enabled:hover,
955
- .detect-btn:focus {
956
- background: linear-gradient(90deg, #38bdf8 0%, #1976d2 100%);
957
- box-shadow: 0 4px 24px rgba(56,189,248,0.18);
958
- transform: scale(1.06);
959
  }
960
- @keyframes detectBtnPulse {
961
- 0% { box-shadow: 0 2px 12px rgba(25, 118, 210, 0.13); }
962
- 100% { box-shadow: 0 8px 32px rgba(56,189,248,0.22); }
 
 
 
 
 
 
 
 
963
  }
964
- .detect-btn::after {
965
- content: '';
966
- position: absolute;
967
- left: 0; top: 0;
968
- width: 100%; height: 100%;
969
- border-radius: 24px;
970
- background: linear-gradient(90deg, rgba(56,189,248,0.12) 0%, rgba(25,118,210,0.08) 100%);
971
- opacity: 0.5;
972
- z-index: -1;
973
- pointer-events: none;
974
- animation: detectBtnGlow 2.2s infinite alternate;
975
  }
976
- @keyframes detectBtnGlow {
977
- 0% { opacity: 0.5; }
978
- 100% { opacity: 0.9; }
 
 
 
 
979
  }
980
 
981
- .header-actions-right {
982
- position: absolute;
983
- right: 32px;
984
- top: 27px;
 
 
 
985
  display: flex;
986
  align-items: center;
987
- z-index: 100;
988
  }
989
 
990
- .logout-btn {
991
- font-size: 0.85rem;
992
- padding: 0.18rem 0.7rem;
993
- border-radius: 5px;
994
- min-width: unset;
995
- min-height: unset;
996
- box-shadow: 0 1px 4px #d1d5db22;
997
- display: inline-flex;
998
- align-items: center;
999
- gap: 6px;
1000
  }
1001
- .logout-btn:hover {
1002
- background: linear-gradient(90deg, #23272b 0%, #ef4444 100%);
1003
- color: #fff;
1004
- box-shadow: 0 2px 24px #ef444488;
1005
- transform: scale(1.04);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1006
  }
1007
- .logout-icon {
1008
- font-size: 1.2em;
1009
- margin-right: 6px;
 
 
1010
  }
1011
 
1012
- .summary-value.blue { color: #2563eb; }
1013
- .summary-value.green { color: #22c55e; }
1014
- .summary-value.red { color: #ef4444; }
 
 
 
1015
 
1016
- .sort {
1017
- display: inline-block;
1018
- width:0;
1019
- height:0;
1020
- border-left:4px solid transparent;
1021
- border-right:4px solid transparent;
1022
- margin-left:6px;
1023
- vertical-align: middle;
1024
  }
1025
- .sort.asc {
1026
- border-bottom:6px solid #4a5568;
 
 
 
 
 
 
1027
  }
1028
- .sort.desc {
1029
- border-top:6px solid #4a5568;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1030
  }
1031
- .sort.neutral {
1032
- border-top:6px solid #b0b0b0;
1033
- border-bottom:6px solid #b0b0b0;
1034
- opacity:0.5;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1035
  }
1036
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1037
 
 
 
 
 
 
1038
 
 
 
 
 
 
1039
 
 
 
 
 
1040
 
 
 
 
 
1041
 
 
 
 
 
 
 
1042
 
 
 
 
1043
 
 
 
 
1044
 
 
 
 
1045
 
 
 
 
 
1046
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  @import '../recordpage/recordpage.component.css';
2
 
3
+ /* ===== CSS Variables & Reset ===== */
4
+ :root {
5
+ --primary-blue: #1E3A8A;
6
+ --primary-blue-light: #2563eb;
7
+ --accent-green: #22c55e;
8
+ --accent-red: #ef4444;
9
+ --accent-purple: #7c3aed;
10
+ --text-dark: #1f2937;
11
+ --text-light: #6b7280;
12
+ --border-color: #e5e7eb;
13
+ --bg-light: #f8fafc;
14
+ --bg-white: #ffffff;
15
+ }
16
+
17
+ * {
18
+ margin: 0;
19
+ padding: 0;
20
+ box-sizing: border-box;
21
  }
22
 
23
+ html, body {
24
+ height: 100%;
25
+ width: 100%;
26
+ overflow: hidden;
27
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
28
  background: #f4f6fa;
 
 
 
 
29
  }
30
 
31
+ /* ===== App Container ===== */
32
+ .app-container {
33
+ display: flex;
34
+ flex-direction: column;
35
+ height: 100vh;
36
+ width: 100%;
37
+ overflow: hidden;
38
+ }
39
 
40
+ /* ===== Header Styles ===== */
41
  .site-header {
42
+ background: linear-gradient(135deg, #011329 0%, #0a2540 100%);
43
+ box-shadow: 0 15px 20px -5px rgba(0, 0, 0, 0.1), 0 6px 8px -4px rgba(0, 0, 0, 0.1);
44
+ height: 60px;
45
+ flex-shrink: 0;
46
+ position: sticky;
47
+ top: 0;
48
+ z-index: 1000;
49
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
50
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
51
+ -webkit-backdrop-filter: blur(8px);
52
+ backdrop-filter: blur(8px);
53
+ background: rgba(1, 19, 41, 0.95);
54
+ }
55
+
56
+ .site-header::before {
57
+ content: '';
58
+ position: absolute;
59
+ top: 0;
60
+ left: 0;
61
+ right: 0;
62
+ bottom: 0;
63
+ background: linear-gradient(135deg, rgba(30, 58, 138, 0.9) 0%, rgba(1, 19, 41, 0.95) 100%);
64
+ z-index: -1;
65
+ }
66
 
67
  .header-inner {
68
  display: flex;
69
  align-items: center;
70
  justify-content: space-between;
71
+ height: 100%;
72
+ padding: 0 32px;
73
+ max-width: 1400px;
74
+ margin: 0 auto;
75
+ width: 100%;
76
  }
77
 
78
  .logo-cluster {
79
  display: flex;
80
  align-items: center;
81
+ gap: 12px;
82
  }
83
 
84
  .logo-img-header {
85
+ width: 48px;
86
+ height: 48px;
87
  border-radius: 50%;
88
+ background: white;
89
+ padding: 6px;
90
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
91
+ cursor: pointer;
92
+ transition: transform 0.2s;
93
  }
94
 
95
+ .logo-img-header:hover {
96
+ transform: scale(1.05);
97
+ }
98
+
99
  .py-detect-title-header {
100
+ font-size: 28px;
 
101
  font-weight: 900;
102
+ letter-spacing: 3px;
103
  color: #38bdf8;
104
  display: flex;
105
  align-items: center;
106
  gap: 2px;
 
107
  }
108
 
109
+ .py-detect-title-header .py-letter.p,
110
+ .py-detect-title-header .py-letter.d,
111
+ .py-detect-title-header .py-letter.t,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  .py-detect-title-header .py-letter.c {
113
  color: #e3f6ff;
114
  text-shadow: 0 0 6px #38bdf8;
115
  }
116
 
117
+ .py-detect-title-header .py-letter.y,
118
+ .py-detect-title-header .py-letter.e,
119
+ .py-detect-title-header .py-letter.e2,
120
  .py-detect-title-header .py-letter.t2 {
121
  color: #38bdf8;
122
  text-shadow: 0 0 6px #38bdf8;
123
  }
124
 
125
+ .py-shape {
126
+ display: inline-block;
127
+ width: 16px;
128
+ height: 4px;
129
+ background: #e3f6ff;
130
+ margin: 0 6px;
131
+ border-radius: 2px;
132
+ box-shadow: 0 0 6px #38bdf8;
 
133
  }
134
 
135
+ .header-actions-right {
136
+ display: flex;
137
+ align-items: center;
138
+ gap: 12px;
139
  }
140
 
141
+ .back-small {
142
+ font-weight: 600;
143
+ font-size: 11px;
144
+ padding: 6px 12px;
145
+ border-radius: 6px;
146
+ border: none;
147
+ cursor: pointer;
148
+ display: flex;
149
+ align-items: center;
150
+ gap: 6px;
151
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
152
+ position: relative;
153
+ overflow: hidden;
154
+ backdrop-filter: blur(8px);
155
+ background: white;
156
+ color: #374151;
157
+ border: 1px solid #d1d5db;
158
  }
159
 
160
+ .back-small:hover {
161
+ background: #f9fafb;
162
+ transform: translateY(-1px);
163
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
164
+ }
 
 
165
 
166
+ .logout-btn {
167
+ font-weight: 600;
168
+ font-size: 11px;
169
+ padding: 6px 12px;
170
+ border-radius: 6px;
171
+ border: none;
172
+ cursor: pointer;
173
+ display: flex;
174
+ align-items: center;
175
+ gap: 6px;
176
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
177
+ position: relative;
178
+ overflow: hidden;
179
+ backdrop-filter: blur(8px);
180
+ background: linear-gradient(135deg, #ef4444, #dc2626);
181
+ color: white;
182
  }
183
 
184
+ .logout-btn:hover {
185
+ background: linear-gradient(135deg, #dc2626, #ef4444);
186
+ transform: translateY(-1px);
187
+ box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
188
+ }
189
 
190
+ .logout-icon {
191
+ font-size: 16px;
 
 
192
  }
193
 
194
+ /* ===== Main Content Area ===== */
195
+ .main-content {
196
+ flex: 1;
197
+ overflow: hidden;
198
+ display: flex;
199
+ flex-direction: column;
 
 
200
  }
201
 
202
+ .content-container {
203
+ flex: 1;
204
+ overflow: hidden;
205
+ padding: 20px;
206
+ max-width: 1882px;
207
+ margin: 0 auto;
208
+ width: 100%;
 
209
  }
210
 
211
+ /* ===== Record Card ===== */
212
+ .record-card {
213
+ background: white;
214
+ border-radius: 12px;
215
+ box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px;
216
+ overflow: hidden;
217
+ display: flex;
218
+ flex-direction: column;
219
+ height: 90%;
220
+ min-height: 0;
221
+ width: 96vw;
222
+ margin-left: 1px;
223
+ margin-top: 6px;
224
  }
225
 
 
 
 
226
 
227
+ /* ===== Analytics Blue Section ===== */
228
+ .analytics-blue {
229
+ background: #1E3A8A;
230
+ padding: 20px 32px;
231
+ flex-shrink: 0;
 
 
232
  }
233
 
234
+ .record-header {
235
+ display: flex;
236
+ justify-content: space-between;
237
+ align-items: center;
238
+ margin-bottom: 20px;
239
  }
240
 
241
+ .record-title-group {
242
+ display: flex;
243
+ align-items: center;
244
+ gap: 16px;
 
 
 
 
245
  }
246
 
247
+ .record-title {
248
+ font-size: 20px;
249
+ font-weight: 700;
250
+ color: white;
251
+ display: flex;
252
+ align-items: center;
253
+ gap: 10px;
254
  }
255
 
256
+ .record-title i {
257
+ font-size: 18px;
258
+ color: #38bdf8;
259
+ }
 
260
 
261
+ .record-header-actions {
262
+ display: flex;
263
+ align-items: center;
264
+ gap: 16px;
 
265
  }
266
 
267
+ .record-search {
268
+ width: 280px;
269
+ padding: 10px 16px 10px 42px;
270
+ border-radius: 6px;
271
+ border: 1px solid rgba(255, 255, 255, 0.3);
272
+ background: rgba(255, 255, 255, 0.1);
273
+ color: white;
274
+ font-size: 14px;
275
+ transition: all 0.3s;
276
+ }
277
+
278
+ .record-search:focus {
279
+ outline: none;
280
+ border-color: #38bdf8;
281
+ background: rgba(255, 255, 255, 0.15);
282
+ box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.1);
283
+ }
284
 
285
+ .record-search::placeholder {
286
+ color: rgba(255, 255, 255, 0.7);
287
+ }
 
 
 
288
 
289
+ .analytics-cards {
290
+ display: flex;
291
+ gap: 20px;
292
+ flex-shrink: 0;
 
 
293
  }
294
 
295
+ .summary-card {
296
+ flex: 1;
297
+ background: rgba(255, 255, 255, 0.95);
298
+ border-radius: 10px;
299
+ padding: 18px;
300
+ display: flex;
301
+ justify-content: space-between;
302
+ align-items: center;
303
+ transition: all 0.3s;
304
+ cursor: pointer;
305
+ border: 1px solid rgba(255, 255, 255, 0.2);
306
+ backdrop-filter: blur(10px);
307
+ min-height: 85px;
308
  }
309
 
310
+ .summary-card:hover {
311
+ transform: translateY(-2px);
312
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
313
+ }
 
 
 
 
 
314
 
315
+ .summary-left {
316
+ display: flex;
317
+ flex-direction: column;
318
  }
319
 
320
+ .summary-label {
321
+ font-size: 18px;
322
+ font-weight: 900;
323
+ color: #6b7280;
324
+ text-transform: uppercase;
325
+ letter-spacing: 1px;
326
  margin-bottom: 6px;
 
327
  }
328
 
329
+ .summary-value {
330
+ font-size: 30px;
331
+ font-weight: 700;
332
+ font-family: 'Segoe UI', sans-serif;
333
+ line-height: 1;
334
  }
335
 
336
+ .summary-value.blue {
337
+ color: #2563eb;
338
+ }
 
 
 
 
 
 
 
339
 
340
+ .summary-value.green {
341
+ color: #22c55e;
342
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
 
344
+ .summary-value.red {
345
+ color: #ef4444;
346
+ }
347
+
348
+ .summary-icon {
349
+ width: 42px;
350
+ height: 42px;
351
+ border-radius: 8px;
352
  display: flex;
 
353
  align-items: center;
354
+ justify-content: center;
355
+ font-size: 18px;
356
+ color: white;
357
  }
358
 
359
+ .summary-card.total .summary-icon {
360
+ background: linear-gradient(135deg, #1E3A8A, #1e40af);
 
 
 
361
  }
362
 
363
+ .summary-card.open .summary-icon {
364
+ background: linear-gradient(135deg, #16a34a, #15803d);
 
 
 
 
365
  }
366
 
367
+ .summary-card.closed .summary-icon {
368
+ background: linear-gradient(135deg, #dc2626, #b91c1c);
 
 
 
 
369
  }
370
 
371
+ .summary-card.review .summary-icon {
372
+ background: linear-gradient(135deg, #f59e0b, #d97706);
 
 
 
 
 
 
 
373
  }
374
 
375
+ /* ===== Record Meta ===== */
376
+ .record-meta {
377
+ padding: 14px 32px;
378
+ background: #f8fafc;
379
+ border-bottom: 1px solid var(--border-color);
380
+ color: #6b7280;
381
+ font-size: 14px;
382
+ font-weight: 500;
383
+ flex-shrink: 0;
384
  }
385
 
386
+ /* ===== Filter Bar ===== */
387
+ .filter-bar {
388
  display: flex;
 
 
389
  align-items: center;
390
+ gap: 16px;
391
+ padding: 16px 32px;
392
+ background: #f8fafc;
393
+ border-bottom: 1px solid var(--border-color);
394
+ flex-shrink: 0;
395
+ flex-wrap: wrap;
396
  }
397
 
398
+ .filter-bar select {
399
+ padding: 9px 16px;
400
+ border-radius: 6px;
401
+ border: 1px solid var(--border-color);
402
+ background: white;
403
+ color: #374151;
404
+ font-size: 14px;
405
+ font-weight: 500;
406
+ min-width: 150px;
407
+ cursor: pointer;
408
+ transition: all 0.2s;
409
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
410
+ }
 
 
411
 
412
+ .filter-bar select:focus {
413
+ outline: none;
414
+ border-color: #2563eb;
415
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
416
+ }
417
+
418
+ .filter-bar button {
419
+ padding: 9px 18px;
420
+ border-radius: 6px;
421
+ border: none;
422
+ font-size: 14px;
423
+ font-weight: 600;
424
+ cursor: pointer;
425
+ display: flex;
426
+ align-items: center;
427
+ gap: 8px;
428
+ transition: all 0.2s;
429
+ background: #2563eb;
430
+ color: white;
431
+ }
432
 
433
+ .filter-bar button:hover {
434
+ background: #1d4ed8;
435
+ transform: translateY(-1px);
436
+ box-shadow: 0 2px 8px rgba(37, 99, 235, 0.3);
437
+ }
438
+
439
+ .filter-bar button:last-of-type {
440
+ background: #E5E7EB;
441
+ color: #374151;
442
+ border: 1px solid #D1D5DB;
443
+ }
444
+
445
+ .filter-bar button:last-of-type:hover {
446
+ background: #D1D5DB;
447
+ transform: translateY(-1px);
448
+ box-shadow: 0 2px 8px rgba(107, 114, 128, 0.2);
449
+ }
450
+
451
+ /* ===== Table Container ===== */
452
+ .table-container {
453
+ flex: 1;
454
+ overflow: hidden;
455
+ display: flex;
456
+ flex-direction: column;
457
+ min-height: 0;
458
  }
459
 
460
+ .table-wrapper {
461
+ flex: 1;
462
+ overflow: auto;
463
+ padding: 15px 32px;
464
+ background: white;
465
  }
466
 
467
+ .table-wrapper::-webkit-scrollbar {
468
+ width: 8px;
469
+ height: 8px;
470
+ }
 
 
 
 
471
 
472
+ .table-wrapper::-webkit-scrollbar-track {
473
+ background: #f1f1f1;
474
+ border-radius: 4px;
 
 
 
475
  }
476
+
477
+ .table-wrapper::-webkit-scrollbar-thumb {
478
+ background: #c1c1c1;
479
+ border-radius: 4px;
 
480
  }
481
+
482
+ .table-wrapper::-webkit-scrollbar-thumb:hover {
483
+ background: #a8a8a8;
484
+ }
485
+
486
+ .record-table {
487
+ width: 100%;
488
+ border-collapse: separate;
489
+ border-spacing: 0;
490
+ background: white;
491
+ min-height: 118px;
492
  }
493
 
494
+ .record-table thead {
495
+ position: sticky;
496
+ top: 0;
497
+ z-index: 10;
498
+ background: #f8fafc;
499
  }
500
+
501
+ .record-table th {
502
+ padding: 18px 16px;
503
+ text-align: left;
504
+ font-weight: 600;
505
+ color: #374151;
506
+ font-size: 13px;
507
+ border-bottom: 2px solid var(--border-color);
508
+ white-space: nowrap;
509
+ position: relative;
510
+ cursor: pointer;
511
+ user-select: none;
512
  }
 
513
 
514
+ .record-table th:hover {
515
+ background: #f1f5f9;
516
+ }
517
 
518
+ .record-table td {
519
+ padding: 16px 16px;
520
+ border-bottom: 1px solid var(--border-color);
521
+ color: #374151;
522
+ font-size: 14px;
523
+ white-space: nowrap;
524
+ }
525
 
526
+ .record-table tbody tr {
527
+ transition: background 0.2s;
528
+ }
 
 
 
 
 
529
 
530
+ .record-table tbody tr:hover {
531
+ background: #f8fafc;
532
+ }
 
 
 
 
 
533
 
534
+ /* Sort arrows */
535
+ .sort {
536
+ position: absolute;
537
+ right: 16px;
538
+ top: 50%;
539
+ transform: translateY(-50%);
540
+ width: 0;
541
+ height: 0;
542
+ border-left: 5px solid transparent;
543
+ border-right: 5px solid transparent;
544
+ opacity: 0.5;
545
  }
546
 
547
+ .sort.asc {
548
+ border-bottom: 8px solid #374151;
549
+ }
 
 
550
 
551
+ .sort.desc {
552
+ border-top: 8px solid #374151;
553
+ }
554
 
555
+ .sort.neutral {
556
+ border-top: 8px solid #9ca3af;
557
+ }
558
+
559
+ /* Case ID link */
560
+ .mono a {
561
  color: #2563eb;
562
+ text-decoration: none;
563
+ font-weight: 600;
564
+ cursor: pointer;
565
+ transition: color 0.2s;
566
  }
567
 
568
+ .mono a:hover {
569
+ color: #1d4ed8;
570
+ text-decoration: underline;
571
+ }
572
+
573
+ /* Priority pills */
574
+ .priority-pill {
575
+ display: inline-flex;
576
+ align-items: center;
577
+ font-weight: bold;
578
+ font-size: 12px;
579
+ padding: 4px 12px;
580
+ border-radius: 20px;
581
+ text-transform: uppercase;
582
+ letter-spacing: 0.5px;
583
  }
584
 
585
+ .priority-high {
586
+ background: #fee2e2;
587
+ color: #b91c1c;
 
 
 
 
 
 
 
 
 
 
588
  }
589
+
590
+ .priority-medium {
591
+ background: #fef9c3;
592
+ color: #ca8a04;
593
  }
594
+
595
+ .priority-low {
596
+ background: #dcfce7;
597
+ color: #15803d;
 
 
 
 
 
598
  }
599
+
600
+ /* Status labels */
601
+ .status-label {
602
  display: inline-flex;
603
  align-items: center;
604
+ gap: 8px;
605
+ padding: 6px 14px;
606
+ border-radius: 20px;
607
+ font-size: 13px;
 
 
 
 
 
 
 
 
608
  font-weight: 600;
609
+ min-width: 110px;
610
+ justify-content: center;
 
611
  }
 
 
 
 
612
 
613
+ .status-open {
614
+ background: #dcfce7;
615
+ color: #166534;
 
 
 
 
616
  }
617
+
618
+ .status-under {
619
+ background: #fef3c7;
620
+ color: #92400e;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
  }
622
 
623
+ .status-closed {
624
+ background: #fee2e2;
625
+ color: #991b1b;
 
 
 
 
 
 
 
 
 
626
  }
627
+
628
+ /* Progress indicators */
629
+ .progress-col {
630
+ text-align: left;
631
+ min-width: 90px;
632
  }
633
 
634
+ .progress-dot {
635
+ width: 12px;
636
+ height: 12px;
637
+ border-radius: 50%;
638
+ display: inline-block;
639
+ margin-right: 8px;
640
+ vertical-align: middle;
 
 
 
641
  }
642
 
643
+ .progress-dot.blue {
644
+ background: linear-gradient(135deg, #2563eb, #38bdf8);
645
+ }
646
+
647
+ .progress-dot.green {
648
+ background: linear-gradient(135deg, #34d399, #6ee7b7);
649
+ }
650
+
651
+ .progress-check {
652
+ font-size: 14px;
653
+ color: #34d399;
654
+ background: #d1fae5;
655
+ border-radius: 4px;
656
+ padding: 2px 6px;
657
+ display: inline-block;
658
+ margin-right: 8px;
659
+ vertical-align: middle;
660
  }
661
+
662
+ .progress-value {
663
+ font-weight: 600;
664
+ font-size: 14px;
665
+ color: #374151;
666
+ vertical-align: middle;
667
  }
668
 
669
+ /* Action buttons */
670
+ .actions {
671
+ display: flex;
672
+ gap: 8px;
673
+ align-items: center;
 
674
  }
675
 
676
+ .icon-btn {
677
+ width: 34px;
678
+ height: 34px;
679
+ border-radius: 6px;
680
+ border: 1px solid var(--border-color);
681
+ background: white;
682
  display: flex;
683
+ align-items: center;
684
+ justify-content: center;
685
+ cursor: pointer;
686
+ transition: all 0.2s;
687
+ position: relative;
688
  }
689
+
690
+ .icon-btn.view {
691
+ color: #2563eb;
692
+ border-color: #dbeafe;
693
+ }
694
+
695
+ .icon-btn.view:hover {
696
+ background: #dbeafe;
697
+ border-color: #2563eb;
698
+ }
699
+
700
+ .icon-btn.upload {
701
+ color: #7c3aed;
702
+ border-color: #ede9fe;
703
+ }
704
+
705
+ .icon-btn.upload:hover {
706
+ background: #ede9fe;
707
+ border-color: #7c3aed;
708
+ }
709
+
710
+ .detect-btn {
711
+ background: linear-gradient(135deg, #1976d2, #38bdf8);
712
+ color: white;
713
  border: none;
714
+ border-radius: 6px;
715
+ font-weight: 600;
716
+ font-size: 14px;
717
+ padding: 8px 16px;
718
  cursor: pointer;
719
+ display: flex;
720
+ align-items: center;
721
+ justify-content: center;
722
+ transition: all 0.2s;
723
+ position: relative;
724
+ overflow: hidden;
725
  }
726
+
727
+ .detect-btn:hover:not(:disabled) {
728
+ background: linear-gradient(135deg, #1565c0, #2563eb);
729
+ transform: translateY(-1px);
730
+ box-shadow: 0 2px 8px rgba(37, 99, 235, 0.3);
731
+ }
732
+
733
+ .detect-btn:disabled {
734
+ opacity: 0.5;
735
+ cursor: not-allowed;
736
+ }
737
+
738
+ /* Empty state */
739
+ .empty {
740
+ text-align: center;
741
+ padding: 60px 20px;
742
+ color: #9ca3af;
743
+ font-size: 16px;
744
+ background: white;
745
  }
746
 
747
+ /* ===== Pagination Controls ===== */
748
+ /*.pagination-controls {
749
  display: flex;
750
+ justify-content: center;
751
+ align-items: center;
752
+ margin: 20px 0;
753
+ gap: 10px;
754
+ }
755
+
756
+ .pagination-controls button {
757
+ border: none;
758
+ background: #f3f4f6;
759
+ color: #333;
760
+ border-radius: 8px;
761
+ padding: 0 16px;
762
+ min-width: 40px;
763
+ min-height: 40px;
764
+ font-size: 14px;
765
+ font-weight: 500;
766
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
767
+ transition: background 0.2s, color 0.2s, transform 0.2s;
768
+ cursor: pointer;
769
+ outline: none;
770
+ }
771
+
772
+ .pagination-controls button:hover:not(:disabled),
773
+ .pagination-controls button:focus:not(:disabled) {
774
+ background: #e3eafe;
775
+ color: #1976d2;
776
+ transform: scale(1.08);
777
+ }
778
+
779
+ .pagination-controls button.active {
780
+ background: #1976d2;
781
+ color: #fff;
782
+ font-weight: bold;
783
+ box-shadow: 0 0 0 2px #90caf9;
784
+ }
785
+
786
+ .pagination-controls span {
787
+ font-size: 14px;
788
+ color: #888;
789
+ padding: 0 8px;
790
+ }*/
791
+
792
+ /* ===== Bottom Controls ===== */
793
+ /*.bottom-controls {
794
  display: flex;
795
+ justify-content: flex-start;
796
+ align-items: center;
797
+ padding: 16px 32px;
798
+ background: #f8fafc;
799
+ border-top: 1px solid var(--border-color);
800
+ flex-shrink: 0;
801
+ gap: 24px;
 
 
 
802
  }
803
+
804
+ .results-info {
805
+ display: flex;
806
+ align-items: center;
807
+ gap: 8px;
808
+ color: #6b7280;
809
+ font-size: 14px;
810
  font-weight: 500;
 
 
 
 
 
 
 
 
 
811
  }
812
 
813
+ .page-size-selector {
 
 
 
 
 
 
814
  display: flex;
815
  align-items: center;
816
+ gap: 8px;
817
+ color: #6b7280;
818
+ font-size: 14px;
819
  }
820
 
821
+ .page-size-selector select {
822
+ padding: 6px 12px;
823
+ border-radius: 4px;
824
+ border: 1px solid var(--border-color);
825
+ background: white;
826
+ color: #374151;
827
+ font-size: 14px;
828
+ cursor: pointer;*/
829
+ /* }*/
830
+
831
+
832
+ /* ===== Bottom Controls ===== */
833
+ .bottom-controls {
834
+ display: flex;
835
+ justify-content: space-between;
836
+ align-items: center;
837
+ padding: 16px 32px;
838
+ background: #f8fafc;
839
+ border-top: 1px solid var(--border-color);
840
+ flex-shrink: 0;
841
  }
842
+
843
+ .results-summary {
844
+ display: flex;
845
+ align-items: center;
846
+ gap: 20px;
847
  }
848
 
849
+ .results-info {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
850
  display: flex;
851
  align-items: center;
852
+ gap: 8px;
853
+ color: #6b7280;
854
+ font-size: 14px;
855
+ font-weight: 500;
856
  }
857
+
858
+ .results-info i {
859
+ color: #2563eb;
860
  }
861
 
862
+ .page-size-selector {
863
+ display: flex;
864
+ align-items: center;
865
+ gap: 8px;
866
+ color: #6b7280;
867
+ font-size: 14px;
868
+ }
869
+
870
+ .page-size-selector select {
871
+ padding: 6px 12px;
872
+ border-radius: 4px;
873
+ border: 1px solid var(--border-color);
874
+ background: white;
875
+ color: #374151;
876
+ font-size: 14px;
877
+ cursor: pointer;
878
+ }
879
 
880
+ .pagination-controls {
881
+ display: flex;
882
+ align-items: center;
883
+ gap: 6px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
884
  }
885
 
886
+ .page-btn {
887
+ width: 34px;
888
+ height: 34px;
889
+ border-radius: 6px;
890
+ border: 1px solid var(--border-color);
891
+ background: white;
892
+ display: flex;
893
  align-items: center;
894
+ justify-content: center;
895
+ cursor: pointer;
896
+ transition: all 0.2s;
897
+ color: #374151;
 
 
898
  }
899
+
900
+ .page-btn:hover:not(:disabled) {
901
+ background: #f3f4f6;
902
+ border-color: #9ca3af;
903
+ }
904
+
905
+ .page-btn:disabled {
906
+ opacity: 0.5;
907
+ cursor: not-allowed;
908
+ }
909
+
910
+ .page-numbers {
911
+ display: flex;
912
+ align-items: center;
913
+ gap: 4px;
914
  }
915
+
916
+ .page-number {
917
+ min-width: 32px;
918
+ height: 32px;
919
+ padding: 0 8px;
920
+ border-radius: 6px;
921
+ border: 1px solid var(--border-color);
922
+ background: white;
923
+ color: #374151;
924
+ font-size: 14px;
925
+ font-weight: 500;
926
+ cursor: pointer;
927
+ transition: all 0.2s;
928
  }
929
+
930
+ .page-number:hover {
931
+ background: #f3f4f6;
932
+ }
933
+
934
+ .page-number.active {
935
+ background: #2563eb;
936
+ color: white;
937
+ border-color: #2563eb;
938
+ }
939
+
940
+ .page-ellipsis {
941
+ padding: 0 8px;
942
+ color: #9ca3af;
943
+ font-size: 14px;
944
  }
945
 
946
+ /* ===== Evidence Upload Section ===== */
947
  .evidence-upload-section {
948
  margin-top: 32px;
949
+ padding: 24px 32px;
950
+ background: #f8fafc;
951
  border-radius: 12px;
952
+ border: 1px solid var(--border-color);
 
 
 
 
 
953
  }
954
+
955
+ .evidence-upload-section h3 {
956
+ margin-bottom: 20px;
957
+ font-size: 18px;
958
+ font-weight: 600;
959
+ color: #374151;
960
+ display: flex;
961
+ align-items: center;
962
+ gap: 10px;
963
+ }
964
+
965
+ .evidence-upload-section h3 i {
966
+ color: #2563eb;
967
+ }
968
+
969
  .evidence-list {
970
+ margin-top: 16px;
971
  }
972
+
973
  .evidence-file {
974
  display: flex;
975
  align-items: center;
976
+ padding: 12px 16px;
977
+ background: white;
978
+ border-radius: 8px;
979
+ border: 1px solid var(--border-color);
980
+ margin-bottom: 8px;
981
+ transition: all 0.2s;
 
982
  }
983
 
984
+ .evidence-file:hover {
985
+ background: #f9fafb;
986
+ border-color: #d1d5db;
987
+ }
988
 
989
+ .evidence-file i {
990
+ margin-right: 12px;
991
+ color: #2563eb;
992
+ font-size: 16px;
993
+ }
994
+
995
+ .evidence-title {
996
+ font-size: 16px;
997
+ font-weight: 600;
998
+ color: #374151;
999
+ margin-bottom: 16px;
1000
+ display: flex;
1001
+ align-items: center;
1002
+ gap: 10px;
1003
  }
1004
 
1005
+ .evidence-type-tabs {
1006
+ display: flex;
1007
+ gap: 12px;
1008
+ margin-bottom: 20px;
1009
+ }
1010
+
1011
+ .evidence-type-tabs button {
1012
+ padding: 8px 20px;
1013
+ border-radius: 6px;
1014
+ border: 1px solid var(--border-color);
1015
+ background: white;
1016
+ color: #374151;
1017
+ font-size: 14px;
1018
+ font-weight: 500;
1019
+ cursor: pointer;
1020
+ transition: all 0.2s;
1021
+ }
1022
+
1023
+ .evidence-type-tabs button.active,
1024
+ .evidence-type-tabs button:hover {
1025
+ background: #2563eb;
1026
+ color: white;
1027
+ border-color: #2563eb;
1028
+ }
1029
+
1030
+ .evidence-actions {
1031
+ margin-bottom: 20px;
1032
  }
1033
+
1034
+ .evidence-file-label {
1035
+ display: inline-flex;
1036
+ align-items: center;
1037
+ gap: 10px;
1038
+ padding: 10px 20px;
1039
+ background: #2563eb;
1040
+ color: white;
1041
+ border-radius: 6px;
1042
+ font-size: 14px;
1043
+ font-weight: 500;
1044
+ cursor: pointer;
1045
+ transition: all 0.2s;
1046
  }
1047
+
1048
+ .evidence-file-label:hover {
1049
+ background: #1d4ed8;
1050
+ transform: translateY(-1px);
1051
+ box-shadow: 0 2px 8px rgba(37, 99, 235, 0.3);
1052
+ }
1053
+
1054
+ .evidence-list-block {
1055
+ background: white;
1056
+ border-radius: 8px;
1057
+ border: 1px solid var(--border-color);
1058
+ padding: 20px;
1059
  }
1060
+
1061
+ .evidence-list-title {
1062
+ font-size: 16px;
1063
+ font-weight: 600;
1064
+ color: #374151;
1065
+ margin-bottom: 16px;
1066
+ padding-bottom: 12px;
1067
+ border-bottom: 2px solid var(--border-color);
1068
  }
1069
 
1070
+ .evidence-file-row {
1071
  display: flex;
1072
  align-items: center;
1073
+ padding: 12px 16px;
1074
+ border-bottom: 1px solid var(--border-color);
1075
  }
1076
+
1077
+ .evidence-file-row:last-child {
1078
+ border-bottom: none;
1079
+ }
1080
+
1081
+ .evidence-file-icon {
1082
+ margin-right: 12px;
1083
+ color: #2563eb;
1084
+ font-size: 16px;
1085
+ width: 24px;
1086
+ text-align: center;
1087
+ }
1088
+
1089
+ .evidence-file-name {
1090
+ flex: 1;
1091
+ color: #374151;
1092
+ font-size: 14px;
1093
  font-weight: 500;
1094
+ }
1095
+
1096
+ .evidence-view-link {
1097
+ color: #2563eb;
1098
+ text-decoration: none;
1099
+ font-size: 14px;
1100
+ font-weight: 500;
1101
+ transition: color 0.2s;
1102
+ }
1103
+
1104
+ .evidence-view-link:hover {
1105
+ color: #1d4ed8;
1106
+ text-decoration: underline;
1107
+ }
1108
+
1109
+ .evidence-hr {
1110
+ margin: 20px 0;
1111
+ border: none;
1112
+ border-top: 1px solid var(--border-color);
1113
+ }
1114
+
1115
+ /* ===== Full-page Overlay Styles ===== */
1116
+ .fullpage-popup-overlay {
1117
+ position: fixed;
1118
+ top: 0;
1119
+ left: 0;
1120
+ right: 0;
1121
+ bottom: 0;
1122
+ width: 100vw;
1123
+ height: 100vh;
1124
+ background: rgba(0, 0, 0, 0.5);
1125
+ z-index: 9999;
1126
  display: flex;
1127
  align-items: center;
1128
+ justify-content: center;
1129
+ padding: 40px;
1130
+ overflow: auto;
 
 
 
1131
  }
1132
+
1133
+ body.popup-open {
1134
+ overflow: hidden !important;
1135
  }
1136
 
1137
+ .fullpage-popup-content {
1138
+ background: white;
1139
+ border-radius: 12px;
1140
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
1141
+ max-width: 1200px;
1142
+ width: 100%;
1143
+ max-height: 90vh;
1144
+ overflow: auto;
1145
+ padding: 32px;
 
 
 
 
 
1146
  position: relative;
 
 
 
 
 
 
 
 
 
1147
  }
1148
+
1149
+ .case-details-title {
1150
+ font-size: 24px;
1151
+ font-weight: 700;
1152
+ color: #1f2937;
1153
+ margin-bottom: 32px;
1154
+ padding-bottom: 16px;
1155
+ border-bottom: 2px solid var(--border-color);
1156
+ display: flex;
1157
+ justify-content: space-between;
1158
+ align-items: center;
1159
  }
1160
+
1161
+ /* ===== Details Section Cards ===== */
1162
+ .details-sections {
1163
+ display: flex;
1164
+ flex-direction: column;
1165
+ gap: 24px;
1166
+ margin-bottom: 32px;
 
 
 
 
1167
  }
1168
+
1169
+ .details-section-card {
1170
+ background: white;
1171
+ border-radius: 10px;
1172
+ border: 1px solid var(--border-color);
1173
+ padding: 24px;
1174
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
1175
  }
1176
 
1177
+ .section-title {
1178
+ font-size: 18px;
1179
+ font-weight: 600;
1180
+ color: #1f2937;
1181
+ margin-bottom: 20px;
1182
+ padding-bottom: 12px;
1183
+ border-bottom: 2px solid #e5e7eb;
1184
  display: flex;
1185
  align-items: center;
1186
+ gap: 10px;
1187
  }
1188
 
1189
+ .subgroup-pills {
1190
+ display: flex;
1191
+ gap: 12px;
1192
+ margin-bottom: 24px;
1193
+ flex-wrap: wrap;
 
 
 
 
 
1194
  }
1195
+
1196
+ .subgroup-pills button {
1197
+ padding: 8px 20px;
1198
+ border-radius: 20px;
1199
+ border: 1px solid var(--border-color);
1200
+ background: white;
1201
+ color: #374151;
1202
+ font-size: 14px;
1203
+ font-weight: 500;
1204
+ cursor: pointer;
1205
+ transition: all 0.2s;
1206
+ }
1207
+
1208
+ .subgroup-pills button.active,
1209
+ .subgroup-pills button:hover {
1210
+ background: #2563eb;
1211
+ color: white;
1212
+ border-color: #2563eb;
1213
+ }
1214
+
1215
+ .fields-table-2col {
1216
+ display: grid;
1217
+ grid-template-columns: 1fr 1fr;
1218
+ gap: 24px;
1219
  }
1220
+
1221
+ .fields-col {
1222
+ display: flex;
1223
+ flex-direction: column;
1224
+ gap: 16px;
1225
  }
1226
 
1227
+ .field-label {
1228
+ font-size: 14px;
1229
+ font-weight: 600;
1230
+ color: #6b7280;
1231
+ margin-bottom: 4px;
1232
+ }
1233
 
1234
+ .field-value {
1235
+ font-size: 15px;
1236
+ font-weight: 500;
1237
+ color: #1f2937;
1238
+ line-height: 1.5;
 
 
 
1239
  }
1240
+
1241
+ .raw-formdata-table {
1242
+ background: #f9fafb;
1243
+ border-radius: 8px;
1244
+ border: 1px solid var(--border-color);
1245
+ padding: 20px;
1246
+ max-height: 400px;
1247
+ overflow: auto;
1248
  }
1249
+
1250
+ .raw-formdata-table table {
1251
+ width: 100%;
1252
+ border-collapse: collapse;
1253
+ }
1254
+
1255
+ .raw-formdata-table tr {
1256
+ border-bottom: 1px solid #e5e7eb;
1257
+ }
1258
+
1259
+ .raw-formdata-table tr:last-child {
1260
+ border-bottom: none;
1261
+ }
1262
+
1263
+ .raw-formdata-table td {
1264
+ padding: 12px 16px;
1265
+ vertical-align: top;
1266
+ }
1267
+
1268
+ .raw-formdata-table td:first-child {
1269
+ font-weight: 600;
1270
+ color: #374151;
1271
+ width: 35%;
1272
+ background: #f3f4f6;
1273
+ }
1274
+
1275
+ .raw-formdata-table td:last-child {
1276
+ background: white;
1277
+ }
1278
+
1279
+ .raw-formdata-table pre {
1280
+ margin: 0;
1281
+ font-family: inherit;
1282
+ font-size: 13px;
1283
+ line-height: 1.5;
1284
+ white-space: pre-wrap;
1285
+ word-break: break-word;
1286
+ }
1287
+
1288
+ .small-btn {
1289
+ padding: 8px 16px;
1290
+ border-radius: 6px;
1291
+ border: 1px solid var(--border-color);
1292
+ background: white;
1293
+ color: #374151;
1294
+ font-size: 14px;
1295
+ font-weight: 500;
1296
+ cursor: pointer;
1297
+ transition: all 0.2s;
1298
+ }
1299
+
1300
+ .small-btn:hover {
1301
+ background: #f3f4f6;
1302
+ border-color: #9ca3af;
1303
+ }
1304
+
1305
+ /* ===== Footer ===== */
1306
+ footer {
1307
+ background: linear-gradient(135deg, rgba(30, 58, 138, 0.9) 0%, rgba(1, 19, 41, 0.95) 100%);
1308
+ color: #fff;
1309
+ text-align: center;
1310
+ padding: 12px 0;
1311
+ position: fixed;
1312
+ bottom: 0;
1313
+ left: 0;
1314
+ width: 100%;
1315
+ z-index: 1000;
1316
+ }
1317
+
1318
+ /* ===== Responsive Design ===== */
1319
+ @media (max-width: 1200px) {
1320
+ .header-inner,
1321
+ .analytics-blue,
1322
+ .record-meta,
1323
+ .filter-bar,
1324
+ .table-wrapper,
1325
+ .bottom-controls {
1326
+ padding-left: 24px;
1327
+ padding-right: 24px;
1328
+ }
1329
+
1330
+ .content-container {
1331
+ padding: 16px;
1332
+ }
1333
+
1334
+ .fullpage-popup-content {
1335
+ padding: 24px;
1336
+ }
1337
  }
1338
+
1339
+ @media (max-width: 992px) {
1340
+ .analytics-cards {
1341
+ flex-direction: column;
1342
+ gap: 16px;
1343
+ }
1344
+
1345
+ .record-header {
1346
+ flex-direction: column;
1347
+ align-items: stretch;
1348
+ gap: 16px;
1349
+ }
1350
+
1351
+ .record-header-actions {
1352
+ width: 100%;
1353
+ }
1354
+
1355
+ .record-search {
1356
+ width: 100%;
1357
+ }
1358
+
1359
+ .filter-bar {
1360
+ gap: 12px;
1361
+ }
1362
+
1363
+ .filter-bar select {
1364
+ flex: 1;
1365
+ min-width: 120px;
1366
+ }
1367
+
1368
+ .table-wrapper {
1369
+ padding: 0 16px;
1370
+ overflow-x: auto;
1371
+ }
1372
+
1373
+ .record-table {
1374
+ min-width: 1200px;
1375
+ }
1376
+
1377
+ .fields-table-2col {
1378
+ grid-template-columns: 1fr;
1379
+ gap: 16px;
1380
+ }
1381
  }
1382
 
1383
+ @media (max-width: 768px) {
1384
+ .header-inner {
1385
+ padding: 0 16px;
1386
+ flex-direction: column;
1387
+ height: auto;
1388
+ padding: 12px 16px;
1389
+ gap: 12px;
1390
+ }
1391
+
1392
+ .py-detect-title-header {
1393
+ font-size: 22px;
1394
+ letter-spacing: 2px;
1395
+ }
1396
+
1397
+ .header-actions-right {
1398
+ width: 100%;
1399
+ justify-content: space-between;
1400
+ }
1401
+
1402
+ .analytics-blue,
1403
+ .record-meta,
1404
+ .filter-bar,
1405
+ .table-wrapper,
1406
+ .bottom-controls {
1407
+ padding-left: 16px;
1408
+ padding-right: 16px;
1409
+ }
1410
+
1411
+ .summary-value {
1412
+ font-size: 22px;
1413
+ }
1414
 
1415
+ .summary-icon {
1416
+ width: 40px;
1417
+ height: 40px;
1418
+ font-size: 18px;
1419
+ }
1420
 
1421
+ .bottom-controls {
1422
+ flex-direction: column;
1423
+ gap: 16px;
1424
+ align-items: stretch;
1425
+ }
1426
 
1427
+ .pagination-controls {
1428
+ width: 100%;
1429
+ justify-content: center;
1430
+ }
1431
 
1432
+ .actions {
1433
+ flex-direction: column;
1434
+ gap: 4px;
1435
+ }
1436
 
1437
+ .icon-btn,
1438
+ .detect-btn {
1439
+ width: 32px;
1440
+ height: 32px;
1441
+ font-size: 12px;
1442
+ }
1443
 
1444
+ .fullpage-popup-overlay {
1445
+ padding: 20px;
1446
+ }
1447
 
1448
+ .fullpage-popup-content {
1449
+ padding: 20px;
1450
+ }
1451
 
1452
+ .case-details-title {
1453
+ font-size: 20px;
1454
+ }
1455
 
1456
+ .section-title {
1457
+ font-size: 16px;
1458
+ }
1459
+ }
1460
 
1461
+ @media (max-width: 480px) {
1462
+ .py-detect-title-header {
1463
+ font-size: 18px;
1464
+ letter-spacing: 1px;
1465
+ }
1466
+
1467
+ .header-actions-right {
1468
+ flex-direction: column;
1469
+ gap: 8px;
1470
+ }
1471
+
1472
+ .summary-card {
1473
+ padding: 16px;
1474
+ }
1475
+
1476
+ .summary-value {
1477
+ font-size: 20px;
1478
+ }
1479
+
1480
+ .summary-icon {
1481
+ width: 36px;
1482
+ height: 36px;
1483
+ font-size: 16px;
1484
+ }
1485
+
1486
+ .filter-bar {
1487
+ flex-direction: column;
1488
+ align-items: stretch;
1489
+ }
1490
+
1491
+ .filter-bar select,
1492
+ .filter-bar button {
1493
+ width: 100%;
1494
+ }
1495
+
1496
+ .subgroup-pills {
1497
+ flex-direction: column;
1498
+ align-items: stretch;
1499
+ }
1500
+
1501
+ .subgroup-pills button {
1502
+ width: 100%;
1503
+ text-align: center;
1504
+ }
1505
+ }
src/app/case-details-page/case-details-page.component.html CHANGED
@@ -19,6 +19,31 @@
19
  </div>
20
  <div class="header-actions-right">
21
  <button class="back-small" *ngIf="selectedCase" (click)="closeAndReturn()">← Back to {{ getReturnLabel() }}</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  <button class="logout-btn" (click)="logout()">
23
  <span class="logout-icon">⎋</span> Logout
24
  </button>
@@ -26,7 +51,12 @@
26
  </div>
27
  </div>
28
 
29
- <div class="record-card">
 
 
 
 
 
30
  <div class="analytics-panel">
31
  <div class="analytics-blue">
32
  <div class="record-header">
@@ -81,6 +111,7 @@
81
  </div>
82
 
83
  <div class="filter-bar">
 
84
  <select [(ngModel)]="filterStatus">
85
  <option value="">Status</option>
86
  <option *ngFor="let status of statusTypes">{{ status }}</option>
@@ -94,6 +125,9 @@
94
  </div>
95
 
96
  <!-- Table/list view always visible -->
 
 
 
97
  <table class="record-table">
98
  <thead>
99
  <tr>
@@ -202,68 +236,48 @@
202
  </tr>
203
  </tbody>
204
  </table>
205
-
206
- <!-- Pagination Controls -->
207
- <div class="pagination-controls" style="display:flex;justify-content:center;align-items:center;margin:20px0;gap:10px;">
208
- <style>
209
- .pagination-controls button {
210
- border: none;
211
- background: #f3f4f6;
212
- color: #333;
213
- border-radius:8px;
214
- padding:016px;
215
- min-width:40px;
216
- min-height:40px;
217
- font-size:1.1em;
218
- font-weight:500;
219
- box-shadow:02px 8px rgba(0,0,0,0.04);
220
- transition: background 0.2s, color 0.2s, transform 0.2s;
221
- cursor: pointer;
222
- outline: none;
223
- }
224
- .pagination-controls button:hover:not(:disabled),
225
- .pagination-controls button:focus:not(:disabled) {
226
- background: #e3eafe;
227
- color: #1976d2;
228
- transform: scale(1.08);
229
- }
230
- .pagination-controls button.active {
231
- background: #1976d2;
232
- color: #fff;
233
- font-weight: bold;
234
- box-shadow:0002px #90caf9;
235
- animation: pulseActive1s infinite;
236
- }
237
- @keyframes pulseActive {
238
- 0% { box-shadow:0002px #90caf9; }
239
- 50% { box-shadow:0006px #90caf9; }
240
- 100% { box-shadow:0002px #90caf9; }
241
- }
242
- .pagination-controls span {
243
- font-size:1.2em;
244
- color: #888;
245
- padding:08px;
246
- }
247
- </style>
248
- <button (click)="prevPage()" [disabled]="currentPage ===1">«</button>
249
- <ng-container *ngFor="let page of getPagination()">
250
- <button *ngIf="page !== '...'" (click)="goToPage(page)" [class.active]="currentPage === page">{{ page }}</button>
251
- <span *ngIf="page === '...'">...</span>
252
- </ng-container>
253
- <button (click)="nextPage()" [disabled]="currentPage === totalPages">»</button>
254
  </div>
255
- </div>
256
 
257
- <!-- Results summary and page size selector -->
258
- <div style="display:flex;align-items:center;justify-content:flex-start;gap:24px;margin-bottom:16px;">
259
- <span style="font-size:1.1em;">Results: {{ resultsStart }} - {{ resultsEnd }} of {{ resultsTotal }}</span>
260
- <select [(ngModel)]="pageSize" (change)="onPageSizeChange(pageSize)" style="padding:4px 12px;border-radius:8px;font-size:1em;">
 
 
 
 
 
 
261
  <option *ngFor="let size of pageSizeOptions" [value]="size">{{ size }}</option>
262
  </select>
263
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
 
265
- <!-- Evidence Upload Section for Investigators only, below the table -->
266
- <div *ngIf="isInvestigator() && selectedCase" class="evidence-upload-section">
267
  <h3>Upload Evidence for Case: {{ selectedCase.caseId }}</h3>
268
  <input type="file" multiple (change)="onEvidenceUpload($event)" />
269
  <div class="evidence-list" *ngIf="uploadedEvidence.length">
@@ -271,10 +285,10 @@
271
  <i class="fas fa-file-upload"></i> {{ file.name }}
272
  </div>
273
  </div>
274
- </div>
275
 
276
- <!-- Evidence Upload Panel for selected case -->
277
- <div *ngIf="isInvestigator() && evidencePanelCase && evidencePanelCase.caseId" class="evidence-upload-section">
278
  <hr class="evidence-hr" />
279
  <div class="evidence-title">
280
  <i class="fas fa-folder-open evidence-folder"></i>
@@ -301,10 +315,10 @@
301
  </div>
302
  </div>
303
  <hr class="evidence-hr" />
304
- </div>
305
 
306
- <!-- Full-page overlay for details (no evidence upload here) -->
307
- <div *ngIf="selectedCase" class="fullpage-popup-overlay">
308
  <div class="fullpage-popup-content">
309
  <div class="case-details-title">Case Details</div>
310
  <div class="details-sections">
@@ -364,10 +378,10 @@
364
  <button class="btn close-btn-bottom" (click)="closeAndReturn()" title="Close">&times;</button>
365
  </div>
366
  </div>
367
- </div>
368
 
369
- <!-- Footer from provided design -->
370
- <footer>
371
  <p>©2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
372
- </footer>
373
- <!-- End of record-card -->
 
19
  </div>
20
  <div class="header-actions-right">
21
  <button class="back-small" *ngIf="selectedCase" (click)="closeAndReturn()">← Back to {{ getReturnLabel() }}</button>
22
+
23
+ <!-- Profile display: initials and dropdown with name/email (matches infopage) -->
24
+ <div #profileDisplay class="profile-display" [class.open]="showProfileMenu" style="margin-left:12px; display:flex;align-items:center; cursor:pointer; position:relative;" (click)="toggleProfileMenu($event)">
25
+ <div class="profile-avatar" style="width:2.2em; height:2.2em; border-radius:50%; background: linear-gradient(135deg, #1E3A8A, #2563eb); color: #fff; display: flex; align-items: center; justify-content: center; font-weight:700; font-size:0.95em; overflow: hidden;">
26
+ <img *ngIf="currentUser?.avatarUrl" [src]="currentUser?.avatarUrl" alt="avatar" style="width:100%;height:100%;object-fit:cover;" />
27
+ <span *ngIf="!currentUser?.avatarUrl">{{ getUserInitials() }}</span>
28
+ </div>
29
+ <div *ngIf="showProfileMenu" class="profile-menu" [ngStyle]="profileMenuStyle" style="background:#222;color:#fff;border-radius:8px;padding:8px10px;min-width:220px;box-shadow:06px18px rgba(0,0,0,0.25);">
30
+ <div style="display:flex;align-items:center;gap:10px;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,0.06);margin-bottom:8px;">
31
+ <div style="width:42px;height:42px;border-radius:50%;background:#2b6ea6;display:flex;align-items:center;justify-content:center;font-weight:700;color:#fff;overflow:hidden;">
32
+ <img *ngIf="currentUser?.avatarUrl" [src]="currentUser?.avatarUrl" alt="avatar" style="width:100%;height:100%;object-fit:cover;" />
33
+ <span *ngIf="!currentUser?.avatarUrl">{{ getUserInitials() }}</span>
34
+ </div>
35
+ <div style="font-size:0.95em;">
36
+ <div>{{ currentUser?.name }}</div>
37
+ <div style="font-size:0.8em;opacity:0.9;">{{ currentUser?.email }}</div>
38
+ </div>
39
+ </div>
40
+ <button (click)="logout()" style="width:100%;display:flex;align-items:center;gap:8px;padding:8px10px;border:none;border-radius:6px;background:transparent;color:#fff;cursor:pointer;">
41
+ <i class="fas fa-sign-out-alt" style="width:18px;text-align:center;"></i>
42
+ Logout
43
+ </button>
44
+ </div>
45
+ </div>
46
+
47
  <button class="logout-btn" (click)="logout()">
48
  <span class="logout-icon">⎋</span> Logout
49
  </button>
 
51
  </div>
52
  </div>
53
 
54
+
55
+ <!-- Main Content Area -->
56
+ <div class="main-content">
57
+ <div class="content-container">
58
+ <!-- Record Card -->
59
+ <div class="record-card">
60
  <div class="analytics-panel">
61
  <div class="analytics-blue">
62
  <div class="record-header">
 
111
  </div>
112
 
113
  <div class="filter-bar">
114
+ <span class="filter-icon"><i class="fas fa-filter"></i></span>
115
  <select [(ngModel)]="filterStatus">
116
  <option value="">Status</option>
117
  <option *ngFor="let status of statusTypes">{{ status }}</option>
 
125
  </div>
126
 
127
  <!-- Table/list view always visible -->
128
+ <!-- Table Container -->
129
+ <div class="table-container">
130
+ <div class="table-wrapper">
131
  <table class="record-table">
132
  <thead>
133
  <tr>
 
236
  </tr>
237
  </tbody>
238
  </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  </div>
240
+ </div>
241
 
242
+ <!-- Bottom Controls -->
243
+ <div class="bottom-controls">
244
+ <div class="results-summary">
245
+ <div class="results-info">
246
+ <i class="fas fa-list-ol"></i>
247
+ <span>Results: {{ resultsStart }} - {{ resultsEnd }} of {{ resultsTotal }}</span>
248
+ </div>
249
+ <div class="page-size-selector">
250
+ <span>Show:</span>
251
+ <select [(ngModel)]="pageSize" (change)="onPageSizeChange(pageSize)">
252
  <option *ngFor="let size of pageSizeOptions" [value]="size">{{ size }}</option>
253
  </select>
254
+ </div>
255
+ </div>
256
+
257
+ <div class="pagination-controls">
258
+ <button class="page-btn prev" (click)="prevPage()" [disabled]="currentPage ===1">
259
+ <i class="fas fa-chevron-left"></i>
260
+ </button>
261
+ <div class="page-numbers">
262
+ <ng-container *ngFor="let page of getPagination()">
263
+ <button *ngIf="page !== '...'"
264
+ class="page-number"
265
+ [class.active]="currentPage === page"
266
+ (click)="goToPage(page)">
267
+ {{ page }}
268
+ </button>
269
+ <span *ngIf="page === '...'" class="page-ellipsis">...</span>
270
+ </ng-container>
271
+ </div>
272
+ <button class="page-btn next" (click)="nextPage()" [disabled]="currentPage === totalPages">
273
+ <i class="fas fa-chevron-right"></i>
274
+ </button>
275
+ </div>
276
+ </div>
277
+ </div>
278
 
279
+ <!-- Evidence Upload Section for Investigators only, below the table -->
280
+ <div *ngIf="isInvestigator() && selectedCase" class="evidence-upload-section">
281
  <h3>Upload Evidence for Case: {{ selectedCase.caseId }}</h3>
282
  <input type="file" multiple (change)="onEvidenceUpload($event)" />
283
  <div class="evidence-list" *ngIf="uploadedEvidence.length">
 
285
  <i class="fas fa-file-upload"></i> {{ file.name }}
286
  </div>
287
  </div>
288
+ </div>
289
 
290
+ <!-- Evidence Upload Panel for selected case -->
291
+ <div *ngIf="isInvestigator() && evidencePanelCase && evidencePanelCase.caseId" class="evidence-upload-section">
292
  <hr class="evidence-hr" />
293
  <div class="evidence-title">
294
  <i class="fas fa-folder-open evidence-folder"></i>
 
315
  </div>
316
  </div>
317
  <hr class="evidence-hr" />
318
+ </div>
319
 
320
+ <!-- Full-page overlay for details (no evidence upload here) -->
321
+ <div *ngIf="selectedCase" class="fullpage-popup-overlay">
322
  <div class="fullpage-popup-content">
323
  <div class="case-details-title">Case Details</div>
324
  <div class="details-sections">
 
378
  <button class="btn close-btn-bottom" (click)="closeAndReturn()" title="Close">&times;</button>
379
  </div>
380
  </div>
381
+ </div>
382
 
383
+ <!-- Footer from provided design -->
384
+ <footer>
385
  <p>©2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
386
+ </footer>
387
+ <!-- End of record-card -->
src/app/case-details-page/case-details-page.component.ts CHANGED
@@ -1,6 +1,7 @@
1
  import { Component, OnInit } from '@angular/core';
2
  import { Router, ActivatedRoute } from '@angular/router';
3
  import { CaseStoreService, PoliceCase } from '../shared/case-store.service';
 
4
 
5
  type EvidenceType = 'Document' | 'Photo';
6
 
@@ -37,8 +38,8 @@ export class CaseDetailsPageComponent implements OnInit {
37
  bVal = b.crime || '';
38
  break;
39
  case 'dateTime':
40
- aVal = a.dateTime ? new Date(a.dateTime).getTime() :0;
41
- bVal = b.dateTime ? new Date(b.dateTime).getTime() :0;
42
  break;
43
  case 'status':
44
  aVal = a.status || '';
@@ -48,8 +49,8 @@ export class CaseDetailsPageComponent implements OnInit {
48
  aVal = '';
49
  bVal = '';
50
  }
51
- if (aVal < bVal) return this.sortDir === 'asc' ? -1 :1;
52
- if (aVal > bVal) return this.sortDir === 'asc' ?1 : -1;
53
  return 0;
54
  });
55
  return sorted.slice(start, start + this.pageSize);
@@ -91,37 +92,37 @@ export class CaseDetailsPageComponent implements OnInit {
91
  filterDateTo: string = '';
92
 
93
  sections: any = {
94
- crime: {
95
- title: 'Crime Details',
96
- subgroups: {
97
- 'Identification & Timing': ['Case ID', 'FIR / Ref #', 'Crime Type', 'Case Category', 'Date & Time (Entry)', 'Occurred From', 'Occurred To', 'Time Reported', 'Time Discovered', 'Country', 'State', 'District', 'Number of Victims', 'Brief Description'],
98
- 'Location & People': ['Location', 'Jurisdiction / PS', 'Scene Type', 'Reported By', 'Reported Contact', 'Witness Count', 'Victim Name', 'Victim Contact', 'Victim Summary', 'Suspected Offender Known?', 'Suspect Link'],
99
- 'Offence & Context': ['Legal Sections / Charges', 'Offence Category', 'Offence Description', 'Suspected Motive', 'Confirmed Motive', 'Weapon Involved', 'Property Loss / Damage'],
100
- 'Evidence & Scene': ['Evidence Collected', 'Forensic Tests Required', 'Scene Condition', 'Photos / Video?', 'CCTV Present?', 'CCTV Sources / IDs', 'Physical Evidence (list)', 'Chain of Custody?', 'Digital Evidence', 'Evidence Storage Reference'],
101
- 'Operational Notes': ['Investigating Officer', 'Duty Person', 'Supervising Officer', 'Patrol Notes', 'Arrest Made', 'Arrest Location', 'Initial Actions Taken', 'riskLevel', 'Confidentiality'],
102
  'Status & Linkage': ['Biometric / Forensic IDs', 'DNA Ref ID', 'Fingerprint ID', 'Case Status', 'Linked Cases', 'arrestCount', 'Case Priority', 'Follow-up Date', 'Court Case ID', 'Next Hearing Date', 'Final Summary'],
103
  'Remark': ['Remark']
104
- }
105
  },
106
- suspect: {
107
- title: 'Suspect Details',
108
- subgroups: {
109
- 'Identity': ['Suspect ID', 'Suspect Name', 'Alias / Nickname', 'Age', 'Gender', 'Nationality', 'Nationality ID / Passport Number', 'Languages', 'Address', 'Known Aliases', 'Government ID'],
110
- 'Physical Description': ['Height (cm)', 'Weight (kg)', 'Build', 'Hair Color', 'Eye Color', 'Distinguishing Marks', 'Tattoo Details', 'Scar Details', 'Photo Upload'],
111
- 'Background': ['Employment', 'Education', 'Occupation', 'Company', 'Workplace Address', 'Marital Status', 'Known Habits', 'Known Financial Details'],
112
- 'Known Associates': ['Associate Names', 'Gang Affiliation', 'Family Connections', 'Social Media Handles'],
113
  'Prior Records': ['Criminal History', 'Prior Arrests', 'Probation/Parole Status'],
114
  'Remark': ['Remark']
115
- }
116
  },
117
- notes: {
118
- title: 'Evidence and Documents',
119
- subgroups: {
120
- 'Investigation Notes': ['Initial Findings', 'Detailed Notes', 'Status', 'Version History / Updates'],
121
- 'Evidence Files': ['Evidence Photos', 'Evidence Videos', 'Evidence Documents'],
122
  'Links and Recommendation': ['Links to Evidence', 'Final Recommendations'],
123
  'Remark': ['Remark']
124
- }
125
  }
126
  };
127
 
@@ -145,13 +146,22 @@ export class CaseDetailsPageComponent implements OnInit {
145
  sortKey: string = 'dateTime';
146
  sortDir: 'asc' | 'desc' = 'desc';
147
 
 
 
 
 
 
 
148
  constructor(private caseStore: CaseStoreService, private router: Router, private route: ActivatedRoute) {}
149
 
150
  ngOnInit(): void {
151
  // Load cases first
152
  this.cases = this.caseStore.getPoliceCases();
153
  this.username = localStorage.getItem('username') || sessionStorage.getItem('username') || '';
154
-
 
 
 
155
  // If navigated with a case in history state, use it (faster)
156
  try {
157
  const navState = (history && (history as any).state) || null;
@@ -187,6 +197,13 @@ export class CaseDetailsPageComponent implements OnInit {
187
  });
188
  }
189
 
 
 
 
 
 
 
 
190
  get filteredCases(): PoliceCase[] {
191
  let filtered = this.cases;
192
 
@@ -456,7 +473,7 @@ export class CaseDetailsPageComponent implements OnInit {
456
  // If not found on mapped path, try raw formData saved with the case
457
  if (value === null || value === undefined || value === '') {
458
  try {
459
- const fd = this.getFormDataArray(sc);
460
  const norm = (s: any) => {
461
  if (s === null || s === undefined) return '';
462
  let t = String(s).toLowerCase();
@@ -469,7 +486,7 @@ export class CaseDetailsPageComponent implements OnInit {
469
 
470
  // Try exact key match first
471
  if (fd && fd.length) {
472
- let kv = fd.find(k => k && String(k.key).toLowerCase() === String(field).toLowerCase());
473
  if (kv) value = kv.value;
474
 
475
  // Try normalized matches: label -> stored key, and mapped path names
@@ -478,7 +495,7 @@ export class CaseDetailsPageComponent implements OnInit {
478
  const pathName = Array.isArray(path) ? path[path.length -1] : String(path);
479
  const pathNorm = norm(pathName);
480
 
481
- kv = fd.find(k => k && (norm(k.key) === fieldNorm || norm(k.key) === pathNorm || norm(k.key).includes(fieldNorm) || fieldNorm.includes(norm(k.key))));
482
  if (kv) value = kv.value;
483
  }
484
  }
@@ -529,6 +546,19 @@ export class CaseDetailsPageComponent implements OnInit {
529
  return String(value);
530
  }
531
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
  getProgressValue(caseObj: any): number {
533
  let total = 0, filled = 0;
534
  ['crime', 'suspect', 'notes'].forEach(sectionKey => {
@@ -674,43 +704,63 @@ export class CaseDetailsPageComponent implements OnInit {
674
  }
675
  }
676
 
677
- logout(): void {
678
- // Implement your logout logic here (clear session, etc.)
679
- // For now, just redirect to home/login
680
- window.location.href = '/';
681
- }
682
-
683
- // New helper: return keys in saved formData (preserve order)
684
- getFormDataKeys(caseObj: any): string[] {
685
- if (!caseObj || !caseObj.formData) return [];
686
  try {
687
- const fd = caseObj.formData;
688
- if (Array.isArray(fd)) {
689
- return fd.map((kv: any) => kv && kv.key ? String(kv.key) : '');
690
- }
691
- return Object.keys(fd);
692
- } catch {
693
- return [];
694
  }
695
  }
696
 
697
- // New helper: detect if formData is key/value array
698
- isFormDataArray(fd: any): boolean {
699
- return Array.isArray(fd) && fd.length >0 && fd.every((item: any) => item && Object.prototype.hasOwnProperty.call(item, 'key'));
 
 
 
700
  }
701
 
702
- // Return formData as array of {key,value}
703
- getFormDataArray(caseObj: any): Array<{ key: string; value: any }> {
704
- if (!caseObj || !caseObj.formData) return [];
705
- const fd = caseObj.formData;
706
- if (this.isFormDataArray(fd)) return fd as Array<{ key: string; value: any }>;
707
- if (typeof fd === 'object') return Object.keys(fd).map(k => ({ key: k, value: fd[k] }));
708
- return [{ key: 'value', value: fd }];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
709
  }
710
 
 
 
 
 
 
 
 
 
711
  copyFormData(): void {
712
  if (!this.selectedCase) return;
713
- // Use the key/value array representation for copying so UI and clipboard match
714
  const kv = this.getFormDataArray(this.selectedCase);
715
  const json = JSON.stringify(kv, null,2);
716
 
@@ -741,47 +791,36 @@ export class CaseDetailsPageComponent implements OnInit {
741
  });
742
  }
743
 
 
744
  editCase(): void {
745
  if (!this.selectedCase) return;
746
- // Send the stored key/value array back to Infopage as a flat object
747
  const kv = this.getFormDataArray(this.selectedCase);
748
  const flat: any = {};
749
  kv.forEach(item => { flat[item.key] = item.value; });
750
- // Navigate to infopage route with state carrying the flat form
751
- this.router.navigate(['/infopage'], { state: { prefillFormData: flat } });
752
  }
753
 
754
- // Navigation back to origin
755
  closeAndReturn(): void {
756
  this.closeDetails();
757
- // Use returnTo if available, otherwise default to '/record'
758
  const target = this.returnTo || '/record';
759
 
760
- // Debug info for runtime troubleshooting
761
- console.log('closeAndReturn called. returnTo=', this.returnTo, 'history.state=', (history && (history as any).state) || {});
762
-
763
- // Normalize and handle case-details with id if available in history.state
764
  const hist = (history && (history as any).state) || {};
765
- const returnId = hist && (hist.returnId || hist.caseId || hist.case) ? (hist.returnId || hist.caseId || (hist.case && hist.case.caseId)) : null;
766
 
767
  try {
768
  if (target.includes('case-details')) {
769
  if (returnId) {
770
- // navigate to specific case details page
771
  this.router.navigate(['/case-details', returnId]);
772
  return;
773
  }
774
- // navigate to case-details root
775
  this.router.navigate(['/case-details']);
776
  return;
777
  }
778
- } catch (e) {
779
- // fallback to generic navigation below
780
- }
781
 
782
- // For other targets ensure absolute URL and navigate
783
  const normalized = target.startsWith('/') ? target : ('/' + target);
784
- // Use navigateByUrl to avoid relative navigation issues
785
  this.router.navigateByUrl(normalized);
786
  }
787
 
@@ -793,7 +832,7 @@ export class CaseDetailsPageComponent implements OnInit {
793
  if (t.includes('/case-details') || t === 'case-details') return 'Case Details';
794
  if (t.includes('/infopage') || t === 'infopage') return 'Info Page';
795
  if (t.includes('/')) return 'Previous Page';
796
- } catch {}
797
  return 'Previous Page';
798
  }
799
  }
 
1
  import { Component, OnInit } from '@angular/core';
2
  import { Router, ActivatedRoute } from '@angular/router';
3
  import { CaseStoreService, PoliceCase } from '../shared/case-store.service';
4
+ import { HostListener, ElementRef, ViewChild } from '@angular/core';
5
 
6
  type EvidenceType = 'Document' | 'Photo';
7
 
 
38
  bVal = b.crime || '';
39
  break;
40
  case 'dateTime':
41
+ aVal = a.dateTime ? new Date(a.dateTime).getTime() : 0;
42
+ bVal = b.dateTime ? new Date(b.dateTime).getTime() : 0;
43
  break;
44
  case 'status':
45
  aVal = a.status || '';
 
49
  aVal = '';
50
  bVal = '';
51
  }
52
+ if (aVal < bVal) return this.sortDir === 'asc' ? -1 : 1;
53
+ if (aVal > bVal) return this.sortDir === 'asc' ? 1 : -1;
54
  return 0;
55
  });
56
  return sorted.slice(start, start + this.pageSize);
 
92
  filterDateTo: string = '';
93
 
94
  sections: any = {
95
+ crime: {
96
+ title: 'Crime Details',
97
+ subgroups: {
98
+ 'Identification & Timing': ['Case ID', 'FIR / Ref #', 'Crime Type', 'Case Category', 'Date & Time (Entry)', 'Occurred From', 'Occurred To', 'Time Reported', 'Time Discovered', 'Country', 'State', 'District', 'Number of Victims', 'Brief Description'],
99
+ 'Location & People': ['Location', 'Jurisdiction / PS', 'Scene Type', 'Reported By', 'Reported Contact', 'Witness Count', 'Victim Name', 'Victim Contact', 'Victim Summary', 'Suspected Offender Known?', 'Suspect Link'],
100
+ 'Offence & Context': ['Legal Sections / Charges', 'Offence Category', 'Offence Description', 'Suspected Motive', 'Confirmed Motive', 'Weapon Involved', 'Property Loss / Damage'],
101
+ 'Evidence & Scene': ['Evidence Collected', 'Forensic Tests Required', 'Scene Condition', 'Photos / Video?', 'CCTV Present?', 'CCTV Sources / IDs', 'Physical Evidence (list)', 'Chain of Custody?', 'Digital Evidence', 'Evidence Storage Reference'],
102
+ 'Operational Notes': ['Investigating Officer', 'Duty Person', 'Supervising Officer', 'Patrol Notes', 'Arrest Made', 'Arrest Location', 'Initial Actions Taken', 'riskLevel', 'Confidentiality'],
103
  'Status & Linkage': ['Biometric / Forensic IDs', 'DNA Ref ID', 'Fingerprint ID', 'Case Status', 'Linked Cases', 'arrestCount', 'Case Priority', 'Follow-up Date', 'Court Case ID', 'Next Hearing Date', 'Final Summary'],
104
  'Remark': ['Remark']
105
+ }
106
  },
107
+ suspect: {
108
+ title: 'Suspect Details',
109
+ subgroups: {
110
+ 'Identity': ['Suspect ID', 'Suspect Name', 'Alias / Nickname', 'Age', 'Gender', 'Nationality', 'Nationality ID / Passport Number', 'Languages', 'Address', 'Known Aliases', 'Government ID'],
111
+ 'Physical Description': ['Height (cm)', 'Weight (kg)', 'Build', 'Hair Color', 'Eye Color', 'Distinguishing Marks', 'Tattoo Details', 'Scar Details', 'Photo Upload'],
112
+ 'Background': ['Employment', 'Education', 'Occupation', 'Company', 'Workplace Address', 'Marital Status', 'Known Habits', 'Known Financial Details'],
113
+ 'Known Associates': ['Associate Names', 'Gang Affiliation', 'Family Connections', 'Social Media Handles'],
114
  'Prior Records': ['Criminal History', 'Prior Arrests', 'Probation/Parole Status'],
115
  'Remark': ['Remark']
116
+ }
117
  },
118
+ notes: {
119
+ title: 'Evidence and Documents',
120
+ subgroups: {
121
+ 'Investigation Notes': ['Initial Findings', 'Detailed Notes', 'Status', 'Version History / Updates'],
122
+ 'Evidence Files': ['Evidence Photos', 'Evidence Videos', 'Evidence Documents'],
123
  'Links and Recommendation': ['Links to Evidence', 'Final Recommendations'],
124
  'Remark': ['Remark']
125
+ }
126
  }
127
  };
128
 
 
146
  sortKey: string = 'dateTime';
147
  sortDir: 'asc' | 'desc' = 'desc';
148
 
149
+ // New: current user/profile menu state
150
+ currentUser: { name: string; email?: string; avatarUrl?: string } | null = null;
151
+ showProfileMenu: boolean = false;
152
+ profileMenuStyle: Record<string, string> = {};
153
+ @ViewChild('profileDisplay', { read: ElementRef }) profileDisplayRef!: ElementRef;
154
+
155
  constructor(private caseStore: CaseStoreService, private router: Router, private route: ActivatedRoute) {}
156
 
157
  ngOnInit(): void {
158
  // Load cases first
159
  this.cases = this.caseStore.getPoliceCases();
160
  this.username = localStorage.getItem('username') || sessionStorage.getItem('username') || '';
161
+
162
+ // Load current user for header/profile display
163
+ this.loadCurrentUser();
164
+
165
  // If navigated with a case in history state, use it (faster)
166
  try {
167
  const navState = (history && (history as any).state) || null;
 
197
  });
198
  }
199
 
200
+ // close profile menu when clicking outside
201
+ @HostListener('document:click', ['$event'])
202
+ handleDoc(ev: Event) {
203
+ const target = ev.target as HTMLElement;
204
+ if (this.showProfileMenu && !target.closest('.profile-display')) this.showProfileMenu = false;
205
+ }
206
+
207
  get filteredCases(): PoliceCase[] {
208
  let filtered = this.cases;
209
 
 
473
  // If not found on mapped path, try raw formData saved with the case
474
  if (value === null || value === undefined || value === '') {
475
  try {
476
+ const fd: Array<any> = this.getFormDataArray(sc);
477
  const norm = (s: any) => {
478
  if (s === null || s === undefined) return '';
479
  let t = String(s).toLowerCase();
 
486
 
487
  // Try exact key match first
488
  if (fd && fd.length) {
489
+ let kv = fd.find((k: any) => k && String(k.key).toLowerCase() === String(field).toLowerCase());
490
  if (kv) value = kv.value;
491
 
492
  // Try normalized matches: label -> stored key, and mapped path names
 
495
  const pathName = Array.isArray(path) ? path[path.length -1] : String(path);
496
  const pathNorm = norm(pathName);
497
 
498
+ kv = fd.find((k: any) => k && (norm(k.key) === fieldNorm || norm(k.key) === pathNorm || norm(k.key).includes(fieldNorm) || fieldNorm.includes(norm(k.key))));
499
  if (kv) value = kv.value;
500
  }
501
  }
 
546
  return String(value);
547
  }
548
 
549
+ // Helpers to work with saved formData shape
550
+ isFormDataArray(fd: any): boolean {
551
+ return Array.isArray(fd) && fd.length >0 && fd.every((item: any) => item && typeof item === 'object' && Object.prototype.hasOwnProperty.call(item, 'key'));
552
+ }
553
+
554
+ getFormDataArray(caseObj: any): Array<{ key: string; value: any }> {
555
+ if (!caseObj || !caseObj.formData) return [];
556
+ const fd = caseObj.formData;
557
+ if (this.isFormDataArray(fd)) return fd as Array<{ key: string; value: any }>;
558
+ if (typeof fd === 'object') return Object.keys(fd).map(k => ({ key: k, value: fd[k] }));
559
+ return [{ key: 'value', value: fd }];
560
+ }
561
+
562
  getProgressValue(caseObj: any): number {
563
  let total = 0, filled = 0;
564
  ['crime', 'suspect', 'notes'].forEach(sectionKey => {
 
704
  }
705
  }
706
 
707
+ // Helper to load current user from localStorage key used by Infopage
708
+ private loadCurrentUser(): void {
 
 
 
 
 
 
 
709
  try {
710
+ const raw = localStorage.getItem('pydetect-current-user');
711
+ if (raw) this.currentUser = JSON.parse(raw);
712
+ else this.currentUser = { name: 'Rathi', email: 'rathi@gmail.com' };
713
+ } catch (e) {
714
+ this.currentUser = { name: 'Rathi' };
 
 
715
  }
716
  }
717
 
718
+ getUserInitials(): string {
719
+ if (!this.currentUser || !this.currentUser.name) return 'A';
720
+ const parts = this.currentUser.name.trim().split(/\s+/);
721
+ const first = parts[0] ? parts[0].charAt(0).toUpperCase() : 'A';
722
+ const second = parts.length > 1 ? (parts[1].charAt(0) || '').toUpperCase() : '';
723
+ return `${first}${second}`;
724
  }
725
 
726
+ toggleProfileMenu(ev?: Event): void {
727
+ if (ev) ev.stopPropagation();
728
+ this.showProfileMenu = !this.showProfileMenu;
729
+ if (this.showProfileMenu) {
730
+ try {
731
+ const el = this.profileDisplayRef && this.profileDisplayRef.nativeElement;
732
+ if (el) {
733
+ const rect = el.getBoundingClientRect();
734
+ const menuWidth = 260; // desired menu width
735
+ const gap = 8; // gap between icon bottom and menu
736
+ const leftUnclamped = Math.round(rect.right - menuWidth);
737
+ const left = Math.min(Math.max(leftUnclamped, 8), Math.max(window.innerWidth - menuWidth - 8, 8));
738
+ const top = Math.round(rect.bottom + gap);
739
+ this.profileMenuStyle = {
740
+ position: 'fixed',
741
+ top: `${top}px`,
742
+ left: `${left}px`,
743
+ 'min-width': `${menuWidth}px`,
744
+ 'z-index': '9999',
745
+ padding: '10px'
746
+ };
747
+ }
748
+ } catch (e) { }
749
+ } else {
750
+ this.profileMenuStyle = {};
751
+ }
752
  }
753
 
754
+ logout(): void {
755
+ try { localStorage.removeItem('pydetect-current-user'); } catch { }
756
+ this.showProfileMenu = false;
757
+ // simple redirect for now
758
+ window.location.href = '/';
759
+ }
760
+
761
+ // Copy form data to clipboard (used in template)
762
  copyFormData(): void {
763
  if (!this.selectedCase) return;
 
764
  const kv = this.getFormDataArray(this.selectedCase);
765
  const json = JSON.stringify(kv, null,2);
766
 
 
791
  });
792
  }
793
 
794
+ // Navigate to infopage to edit the selected case
795
  editCase(): void {
796
  if (!this.selectedCase) return;
 
797
  const kv = this.getFormDataArray(this.selectedCase);
798
  const flat: any = {};
799
  kv.forEach(item => { flat[item.key] = item.value; });
800
+ this.router.navigate(['/infopage'], { state: { prefillFormData: flat, case: this.selectedCase } });
 
801
  }
802
 
803
+ // Close the overlay/details and navigate back to origin
804
  closeAndReturn(): void {
805
  this.closeDetails();
 
806
  const target = this.returnTo || '/record';
807
 
808
+ // Try to derive returnId from history state
 
 
 
809
  const hist = (history && (history as any).state) || {};
810
+ const returnId = hist && (hist.returnId || hist.caseId || (hist.case && hist.case.caseId)) ? (hist.returnId || hist.caseId || (hist.case && hist.case.caseId)) : null;
811
 
812
  try {
813
  if (target.includes('case-details')) {
814
  if (returnId) {
 
815
  this.router.navigate(['/case-details', returnId]);
816
  return;
817
  }
 
818
  this.router.navigate(['/case-details']);
819
  return;
820
  }
821
+ } catch (e) { /* fallthrough */ }
 
 
822
 
 
823
  const normalized = target.startsWith('/') ? target : ('/' + target);
 
824
  this.router.navigateByUrl(normalized);
825
  }
826
 
 
832
  if (t.includes('/case-details') || t === 'case-details') return 'Case Details';
833
  if (t.includes('/infopage') || t === 'infopage') return 'Info Page';
834
  if (t.includes('/')) return 'Previous Page';
835
+ } catch { }
836
  return 'Previous Page';
837
  }
838
  }
src/app/case-details-summary-page/case-details-summary-page.component.css CHANGED
@@ -1,76 +1,260 @@
1
- :root {
2
- --masthead-min-height: 140px;
 
 
 
 
3
  }
4
 
5
- /* Base */
6
- body {
7
- background: linear-gradient(135deg, #e0e7ef 0%, #38bdf8 100%);
8
- min-height: 100vh;
9
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
 
 
 
 
10
  }
11
 
12
- /* Layout */
13
- .case-details-summary-layout {
14
- display: flex;
15
- min-height: 90vh; /* unified (was 88vh in an earlier duplicate) */
16
- background: linear-gradient(120deg, #e0f2fe 0%, #f4f6fa 100%);
 
 
 
 
17
  }
18
 
19
- .masthead {
20
- display: flex;
21
- align-items: center;
22
- gap: 24px;
23
- padding: 24px 24px 12px;
24
- min-height: var(--masthead-min-height);
25
- background: transparent;
26
- margin-bottom: 48px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
 
29
- /* Header (from infopage) */
30
  .site-header {
31
- background: #011329;
32
- box-shadow: 0 2px 12px #38bdf844;
33
- margin-bottom: 0;
34
- position: relative;
35
- z-index: 10;
36
- padding-bottom: 0;
 
 
 
 
 
37
  }
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  .header-inner {
40
  display: flex;
41
  align-items: center;
42
  justify-content: space-between;
43
- padding: 18px 32px 0 32px;
44
- position: relative;
 
 
 
45
  }
46
 
47
  .logo-cluster {
48
  display: flex;
49
  align-items: center;
50
- gap: 18px;
 
 
 
 
 
 
 
 
51
  }
52
 
53
  .logo-img-header {
54
- width: 54px;
55
- height: 54px;
56
  border-radius: 50%;
57
- background: #fff;
58
- box-shadow: 0 2px 8px rgba(0,0,0,0.18);
59
- padding: 4px;
60
- margin-top: -6px;
61
- margin-bottom: 1vh;
62
  }
63
 
 
 
 
 
64
  .py-detect-title-header {
65
- font-size: 2.1rem;
66
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
67
  font-weight: 900;
68
- letter-spacing: 6px;
69
  color: #38bdf8;
70
  display: flex;
71
  align-items: center;
72
  gap: 2px;
73
- margin-bottom: 1.5vh;
74
  }
75
 
76
  .py-detect-title-header .py-letter.p,
@@ -89,393 +273,2114 @@ body {
89
  text-shadow: 0 0 6px #38bdf8;
90
  }
91
 
92
- .py-detect-title-header .py-shape {
93
- color: #e3f6ff;
94
- background: #e3f6ff;
95
- text-shadow: 0 0 6px #38bdf8;
96
- box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff;
97
- border: 2px solid #23272b;
98
- width: 18px;
99
- height: 4px;
100
- display: inline-block;
101
- margin: 0 8px;
102
- border-radius: 2px;
103
- }
104
 
105
- /* Sidebar */
106
- .sidebar {
107
- width: 370px;
108
- height: 84vh;
109
- color: #fff;
110
- box-shadow: 0 8px 32px #2563eb33;
111
  display: flex;
112
- flex-direction: column;
113
- padding-top: 4px;
114
- border-top-right-radius: 0;
115
- border-bottom-right-radius: 0;
116
- margin-right: 0;
117
- overflow-y: auto;
118
- background: linear-gradient(to right, #011022, #01030a);
119
  }
120
 
121
- .sidebar ul {
122
- list-style: none;
123
- padding: 14px;
124
- margin: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  }
126
 
127
- .sidebar > ul > li > button {
128
- border-radius: 12px !important;
129
- margin-bottom: 18px;
130
- padding: 18px 32px;
131
- box-shadow: 0 4px 16px #38bdf855;
132
- cursor: pointer !important;
133
- transition: all 0.3s ease !important;
134
- letter-spacing: 1px;
135
- text-align: left;
136
- outline: none;
137
- position: relative !important;
138
- display: flex !important;
139
- align-items: center !important;
140
- gap: 8px !important;
141
- min-width: 150px !important;
142
- justify-content: center !important;
143
- background: linear-gradient(135deg, rgba(255,255,255,0.10), rgba(255,255,255,0.05)) !important;
144
- border: 1px solid rgba(0,212,255,0.3) !important;
145
- backdrop-filter: blur(10px) !important;
146
- color: #e0e6ed !important;
147
- font-weight: 900 !important;
148
- font-size: 1.18rem !important;
149
- }
150
-
151
- .sidebar > ul > li > button.active,
152
- .sidebar > ul > li > button:focus {
153
- background: linear-gradient(90deg, #38bdf8 0%, #bfcfe7 100%);
154
- box-shadow: 0 8px 32px #2563eb55;
155
- transform: scale(1.06);
156
- }
157
 
158
- .sidebar > ul > li > button:hover {
159
- background: linear-gradient(90deg, #0ea5e9 0%, #bfcfe7 100%);
160
- }
 
 
 
161
 
162
- .sidebar ul ul {
163
- background: rgba(255, 255, 255, 0.18);
164
- border-radius: 14px;
165
- margin-top: 2px;
166
- box-shadow: 0 2px 8px #38bdf822;
167
- padding: 8px 0;
168
- animation: slideDown 0.35s cubic-bezier(.77, 0, .175, 1);
169
- }
170
-
171
- .sidebar ul ul li {
172
- background: linear-gradient(90deg, #f8fafc 0%, #e0e7ef 100%);
173
- color: #010610;
174
- font-size: 1.05em;
175
- font-weight: 700;
176
- border-radius: 10px;
177
- margin: 6px 18px;
178
- padding: 12px 22px;
179
- box-shadow: 0 2px 8px #2563eb11;
180
- cursor: pointer;
181
- transition: background 0.18s, color 0.18s, box-shadow 0.18s, transform 0.18s;
182
- border: 2px solid transparent;
183
  }
184
 
185
- .sidebar ul ul li.active,
186
- .sidebar ul ul li:focus {
187
- background: linear-gradient(90deg, #38bdf8 0%, #0c0c0c 100%);
188
- color: #fff;
189
- border: 2px solid #2563eb;
190
- box-shadow: 0 4px 16px #38bdf855;
191
- transform: scale(1.04);
192
- }
193
 
194
- .sidebar ul ul li:hover {
195
- background: linear-gradient(90deg, #e0f2fe 0%, #38bdf8 100%);
196
- color: #2563eb;
197
- border: 2px solid #38bdf8;
198
- }
199
 
200
- /* Main */
201
- .main-content {
202
- flex: 1;
203
- padding: 6px 6px;
204
- background: linear-gradient(120deg, #f4f6fa 0%, #e0e7ef 100%);
205
- min-height: calc(100vh - var(--masthead-min-height) - 48px);
206
- border-radius: 0;
207
- box-shadow: 0 8px 32px #2563eb22;
208
- animation: fadeIn 0.5s cubic-bezier(.77, 0, .175, 1);
209
- overflow-y: auto;
210
  }
211
 
212
- /* Slide panel */
213
- .slide-panel {
214
- background: #fff;
215
- border-radius: 0;
216
- box-shadow: 0 4px 24px #23272b18;
217
- padding: 38px 54px;
218
- min-height: 340px;
219
- transition: transform 0.3s cubic-bezier(.77, 0, .175, 1), box-shadow 0.2s;
220
- transform: translateX(0);
221
- animation: fadeIn 0.5s cubic-bezier(.77, 0, .175, 1);
 
 
 
 
 
 
222
  }
223
 
224
- .slide-panel.open {
225
- transform: translateX(0);
226
- box-shadow: 0 12px 48px #2563eb22;
 
 
 
227
  }
228
 
229
- /* Tabs */
230
- .tabs {
231
- display: flex;
232
- gap: 18px;
233
- margin-bottom: 32px;
234
- }
235
 
236
- .tabs button {
237
- background: #f8fafc;
238
- color: #2563eb;
239
- border: none;
240
- border-radius: 8px;
241
- font-weight: 700;
242
- font-size: 1.08em;
243
- padding: 12px 32px;
244
- cursor: pointer;
245
- box-shadow: 0 2px 8px #2563eb11;
246
- transition: background 0.25s, color 0.25s, transform 0.18s;
247
- }
248
-
249
- .tabs button.active,
250
- .tabs button:focus {
251
- background: #2563eb;
252
- color: #fff;
253
- box-shadow: 0 4px 16px #38bdf855;
254
- transform: scale(1.04);
255
- }
256
 
257
- .tabs button:hover {
258
- background: #0ea5e9;
259
- color: #fff;
260
- }
 
 
 
 
 
 
 
 
 
 
261
 
262
- /* Table */
263
- table {
264
- width: 100%;
265
- border-collapse: collapse;
266
- margin: 24px 0;
267
  }
268
 
269
- th {
270
- background: linear-gradient(180deg, #38bdf8 0%, #2563eb 100%);
271
- color: #fff;
272
- padding: 16px;
273
- text-align: left;
274
- font-weight: 700;
275
- font-size: 1.1em;
276
- border-top-left-radius: 8px;
277
- border-top-right-radius: 8px;
278
  }
279
 
280
- td {
281
- background: #fff;
282
- padding: 16px;
283
- border-bottom: 2px solid #e0e7ef;
284
- color: #50575d;
285
- font-size: 1em;
286
- vertical-align: middle;
 
287
  }
288
 
289
- tr:hover td {
290
- background: #f1f9ff;
 
 
 
 
291
  }
292
 
293
- tr:last-child td {
294
- border-bottom: none;
 
 
 
 
 
295
  }
296
 
297
- /* Footer */
298
- footer {
299
- background: linear-gradient(to right, #011022, #01030a);
300
- color: #fff;
301
- text-align: center;
302
- padding: 10px 0;
303
- position: fixed;
304
- bottom: 0;
305
- left: 0;
306
- width: 100%;
307
  z-index: 100;
308
- margin-top: 0;
 
309
  }
310
 
311
- /* Cards */
312
- .details-card {
313
- background: #fff;
314
- border-radius: 0;
315
- box-shadow: 0 4px 32px #2563eb22;
316
- padding: 5px 5px;
317
- margin-top: -5px;
318
- margin-bottom: 32px;
319
- max-width: 1510px;
320
- width: 100%;
321
- animation: fadeIn 0.6s cubic-bezier(.77, 0, .175, 1);
322
- }
323
 
324
- .details-header {
325
- display: flex;
326
- align-items: center;
327
- gap: 18px;
328
- margin-bottom: 32px;
329
  }
330
 
331
- .details-header h2 {
332
- font-size: 2.3em;
333
- font-weight: 900;
334
- color: #2563eb;
335
- margin: 0;
336
- letter-spacing: 1.5px;
337
- text-shadow: 1px 0 #fff;
338
  }
339
 
340
- .subheader-pill {
341
- background: linear-gradient(90deg, #000000 0%, #0066ff 100%);
342
- color: #fff;
343
- font-weight: 700;
344
- font-size: 1.1em;
345
- border-radius: 16px;
346
- padding: 8px 22px;
347
- margin-left: 18px;
348
- box-shadow: 0 2px 8px #38bdf855;
349
- display: inline-block;
350
- }
351
 
352
- .details-grid {
353
- display: grid;
354
- grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
355
- gap: 6px 6px;
 
 
 
 
 
 
 
 
356
  }
357
 
358
- .details-field {
359
- background: linear-gradient(90deg, #f8fafc 0%, #e0e7ef 100%);
360
- border-radius: 0;
361
- box-shadow: 0 2px 8px #2563eb11;
362
- padding: 3px 3px;
363
  display: flex;
364
- flex-direction: column;
365
- gap: 3px;
366
- min-height: 3px;
367
- transition: box-shadow 0.18s, background 0.18s;
 
 
 
 
 
 
 
 
 
368
  }
369
 
370
- .field-label {
371
- color: #2563eb;
372
- font-weight: 700;
373
- font-size: 1.09em;
374
- margin-bottom: 2px;
375
- }
 
376
 
377
- .field-value {
378
- color: #23272b;
379
- font-size: 1.08em;
380
- font-weight: 500;
381
- word-break: break-word;
 
 
 
 
 
 
 
 
 
 
 
 
382
  }
383
 
384
- .details-field .field-value {
385
- max-height: 220px;
386
- overflow-y: auto;
387
- word-break: break-word;
388
- white-space: pre-wrap;
389
  }
390
 
391
- .details-field.description-field {
392
- min-height: 32px;
 
 
 
393
  }
394
 
395
- .details-field.description-field .field-value {
396
- white-space: pre-wrap;
397
- min-height: 32px;
398
- max-height: 400px;
399
- overflow-y: auto;
400
- resize: vertical;
401
- transition: min-height 0.2s, max-height 0.2s;
402
  }
403
 
404
- /* Animated back button */
405
- .animated-back {
406
- background: linear-gradient(90deg, #38bdf8 0%, #bfcfe7 100%);
407
- color: #23272b; /* text color */
408
- font-size: 1em;
409
- font-weight: 900;
410
- border: none;
411
- border-radius: 5px;
412
- margin-bottom: 18px;
413
- padding: 0px 4px;
414
- box-shadow: 0 4px 16px #38bdf855;
415
- cursor: pointer;
416
- transition: background 0.25s, color 0.25s, box-shadow 0.25s, transform 0.18s;
417
- letter-spacing: 1px;
418
- text-align: center;
419
- outline: none;
420
- position: relative;
421
- animation: backBtnFadeIn 0.6s cubic-bezier(.77,0,.175,1);
422
  display: flex;
423
  align-items: center;
424
- justify-content: center;
425
- gap: 0px;
 
 
 
 
 
 
 
426
  }
427
 
428
- .animated-back:hover {
429
- background: linear-gradient(90deg, #0ea5e9 0%, #bfcfe7 100%);
430
- transform: scale(1.08);
431
- box-shadow: 0 8px 32px #2563eb55;
 
432
  }
433
 
434
- .back-icon {
435
- font-size: 1em;
436
- margin-right: 4px;
437
- margin-top: -4px;
438
- transition: transform 0.2s;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
  }
440
 
441
- .animated-back:hover .back-icon {
442
- transform: translateX(-4px) scale(1.2);
 
 
 
 
 
 
 
 
 
 
 
 
 
443
  }
444
 
 
 
 
 
445
 
446
- /* Animations */
447
- @keyframes fadeIn {
448
- 0% {
449
- opacity: 0;
450
- transform: translateY(10px);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
  }
452
 
453
- 100% {
454
- opacity: 1;
455
- transform: translateY(0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
  }
458
 
459
- @keyframes slideDown {
460
- 0% {
461
- opacity: 0;
462
- transform: translateY(-10px);
 
463
  }
464
 
465
- 100% {
466
- opacity: 1;
467
- transform: translateY(0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
469
  }
470
 
471
- @keyframes backBtnFadeIn {
472
- 0% {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
  opacity: 0;
474
- transform: translateX(-20px);
 
 
475
  }
476
 
477
- 100% {
478
  opacity: 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
  transform: translateX(0);
480
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  }
 
1
+ /* case-details-summary-page.component.css */
2
+ /* ===== CSS Reset ===== */
3
+ * {
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
  }
8
 
9
+ html, body {
10
+ height: 100%;
11
+ width: 100%;
12
+ overflow: auto;
13
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
14
+ font-size: 12px;
15
+ scroll-behavior: smooth;
16
+ -webkit-font-smoothing: antialiased;
17
+ -moz-osx-font-smoothing: grayscale;
18
  }
19
 
20
+ /* ===== Animations ===== */
21
+ @keyframes fadeIn {
22
+ from {
23
+ opacity: 0;
24
+ }
25
+
26
+ to {
27
+ opacity: 1;
28
+ }
29
  }
30
 
31
+ @keyframes fadeInUp {
32
+ from {
33
+ opacity: 0;
34
+ transform: translateY(20px);
35
+ }
36
+
37
+ to {
38
+ opacity: 1;
39
+ transform: translateY(0);
40
+ }
41
+ }
42
+
43
+ @keyframes fadeInDown {
44
+ from {
45
+ opacity: 0;
46
+ transform: translateY(-20px);
47
+ }
48
+
49
+ to {
50
+ opacity: 1;
51
+ transform: translateY(0);
52
+ }
53
+ }
54
+
55
+ @keyframes fadeInLeft {
56
+ from {
57
+ opacity: 0;
58
+ transform: translateX(-20px);
59
+ }
60
+
61
+ to {
62
+ opacity: 1;
63
+ transform: translateX(0);
64
+ }
65
+ }
66
+
67
+ @keyframes fadeInRight {
68
+ from {
69
+ opacity: 0;
70
+ transform: translateX(20px);
71
+ }
72
+
73
+ to {
74
+ opacity: 1;
75
+ transform: translateX(0);
76
+ }
77
+ }
78
+
79
+ @keyframes pulse {
80
+ 0%, 100% {
81
+ transform: scale(1);
82
+ opacity: 1;
83
+ }
84
+
85
+ 50% {
86
+ transform: scale(1.05);
87
+ opacity: 0.8;
88
+ }
89
+ }
90
+
91
+ @keyframes pulse-ring {
92
+ 0% {
93
+ transform: scale(0.8);
94
+ opacity: 0.5;
95
+ }
96
+
97
+ 80%, 100% {
98
+ transform: scale(2);
99
+ opacity: 0;
100
+ }
101
+ }
102
+
103
+ @keyframes shimmer {
104
+ 0% {
105
+ background-position: -1000px 0;
106
+ }
107
+
108
+ 100% {
109
+ background-position: 1000px 0;
110
+ }
111
+ }
112
+
113
+ @keyframes float {
114
+ 0%, 100% {
115
+ transform: translateY(0);
116
+ }
117
+
118
+ 50% {
119
+ transform: translateY(-8px);
120
+ }
121
+ }
122
+
123
+ @keyframes gradientShift {
124
+ 0% {
125
+ background-position: 0% 50%;
126
+ }
127
+
128
+ 50% {
129
+ background-position: 100% 50%;
130
+ }
131
+
132
+ 100% {
133
+ background-position: 0% 50%;
134
+ }
135
+ }
136
+
137
+ @keyframes ripple {
138
+ 0% {
139
+ transform: scale(0);
140
+ opacity: 0.5;
141
+ }
142
+
143
+ 100% {
144
+ transform: scale(4);
145
+ opacity: 0;
146
+ }
147
+ }
148
+
149
+ @keyframes slideIn {
150
+ from {
151
+ transform: translateX(-100%);
152
+ opacity: 0;
153
+ }
154
+
155
+ to {
156
+ transform: translateX(0);
157
+ opacity: 1;
158
+ }
159
+ }
160
+
161
+ @keyframes scaleIn {
162
+ from {
163
+ transform: scale(0.95);
164
+ opacity: 0;
165
+ }
166
+
167
+ to {
168
+ transform: scale(1);
169
+ opacity: 1;
170
+ }
171
+ }
172
+
173
+ @keyframes spin {
174
+ to {
175
+ transform: rotate(360deg);
176
+ }
177
  }
178
 
179
+ /* ===== Header Styles ===== */
180
  .site-header {
181
+ background: linear-gradient(135deg, #011329 0%, #0a2540 100%);
182
+ box-shadow: 0 15px 20px -5px rgba(0, 0, 0, 0.1), 0 6px 8px -4px rgba(0, 0, 0, 0.1);
183
+ height: 60px;
184
+ flex-shrink: 0;
185
+ position: sticky;
186
+ top: 0;
187
+ z-index: 1000;
188
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
189
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
190
+ backdrop-filter: blur(8px);
191
+ background: rgba(1, 19, 41, 0.95);
192
  }
193
 
194
+ .site-header::before {
195
+ content: '';
196
+ position: absolute;
197
+ top: 0;
198
+ left: 0;
199
+ right: 0;
200
+ bottom: 0;
201
+ background: linear-gradient(135deg, rgba(30, 58, 138, 0.9) 0%, rgba(1, 19, 41, 0.95) 100%);
202
+ z-index: -1;
203
+ }
204
+
205
+ .site-header.scrolled {
206
+ height: 56px;
207
+ box-shadow: 0 8px 12px -3px rgba(0, 0, 0, 0.1), 0 3px 5px -3px rgba(0, 0, 0, 0.1);
208
+ }
209
+
210
  .header-inner {
211
  display: flex;
212
  align-items: center;
213
  justify-content: space-between;
214
+ height: 100%;
215
+ padding: 0 20px;
216
+ max-width: 1440px;
217
+ margin: 0 auto;
218
+ width: 100%;
219
  }
220
 
221
  .logo-cluster {
222
  display: flex;
223
  align-items: center;
224
+ gap: 12px;
225
+ margin-left: -215px; /* Remove negative margin if it exists */
226
+ }
227
+
228
+ .brand-logo {
229
+ display: flex;
230
+ align-items: center;
231
+ gap: 12px;
232
+ position: relative;
233
  }
234
 
235
  .logo-img-header {
236
+ width: 48px;
237
+ height: 48px;
238
  border-radius: 50%;
239
+ background: white;
240
+ padding: 6px;
241
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
242
+ cursor: pointer;
243
+ transition: transform 0.2s;
244
  }
245
 
246
+ .logo-img-header:hover {
247
+ transform: scale(1.05);
248
+ }
249
+
250
  .py-detect-title-header {
251
+ font-size: 28px;
 
252
  font-weight: 900;
253
+ letter-spacing: 3px;
254
  color: #38bdf8;
255
  display: flex;
256
  align-items: center;
257
  gap: 2px;
 
258
  }
259
 
260
  .py-detect-title-header .py-letter.p,
 
273
  text-shadow: 0 0 6px #38bdf8;
274
  }
275
 
276
+ .py-shape {
277
+ display: inline-block;
278
+ width: 16px;
279
+ height: 4px;
280
+ background: #e3f6ff;
281
+ margin: 0 6px;
282
+ border-radius: 2px;
283
+ box-shadow: 0 0 6px #38bdf8;
284
+ }
 
 
 
285
 
286
+ .header-actions-right {
 
 
 
 
 
287
  display: flex;
288
+ align-items: center;
289
+ gap: 12px;
 
 
 
 
 
290
  }
291
 
292
+ .action-buttons {
293
+ display: flex;
294
+ align-items: center;
295
+ gap: 8px;
296
+ margin-right: -209px;
297
+ }
298
+
299
+ .action-btn {
300
+ display: flex;
301
+ align-items: center;
302
+ gap: 6px;
303
+ padding: 6px 12px;
304
+ border-radius: 6px;
305
+ border: none;
306
+ font-size: 11px;
307
+ font-weight: 600;
308
+ cursor: pointer;
309
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
310
+ position: relative;
311
+ overflow: hidden;
312
+ backdrop-filter: blur(8px);
313
+ }
314
+
315
+ .action-btn::before {
316
+ content: '';
317
+ position: absolute;
318
+ top: 50%;
319
+ left: 50%;
320
+ width: 0;
321
+ height: 0;
322
+ border-radius: 50%;
323
+ background: rgba(255, 255, 255, 0.2);
324
+ transform: translate(-50%, -50%);
325
+ transition: width 0.6s, height 0.6s;
326
  }
327
 
328
+ .action-btn:hover::before {
329
+ width: 200px;
330
+ height: 200px;
331
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
 
333
+ .action-btn.print-btn {
334
+ background: rgba(255, 255, 255, 0.1);
335
+ color: #ffffff;
336
+ border: 1px solid rgba(255, 255, 255, 0.2);
337
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 1px -1px rgba(0, 0, 0, 0.1);
338
+ }
339
 
340
+ .action-btn.print-btn:hover {
341
+ background: rgba(255, 255, 255, 0.2);
342
+ transform: translateY(-1px);
343
+ box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.1), 0 2px 3px -2px rgba(0, 0, 0, 0.1);
344
+ border-color: rgba(255, 255, 255, 0.3);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  }
346
 
347
+ .action-btn.export-btn {
348
+ background: linear-gradient(135deg, #f97316, #ea580c);
349
+ color: #ffffff;
350
+ border: none;
351
+ box-shadow: 0 3px 10px 0 rgba(249, 115, 22, 0.3);
352
+ }
 
 
353
 
354
+ .action-btn.export-btn:hover {
355
+ transform: translateY(-1px);
356
+ box-shadow: 0 6px 12px -3px rgba(0, 0, 0, 0.1), 0 3px 5px -3px rgba(0, 0, 0, 0.1);
357
+ animation: pulse 1s ease-in-out;
358
+ }
359
 
360
+ .btn-label {
361
+ font-size: 11px;
362
+ font-weight: 600;
363
+ letter-spacing: 0.2px;
 
 
 
 
 
 
364
  }
365
 
366
+ .back-btn {
367
+ display: flex;
368
+ align-items: center;
369
+ gap: 6px;
370
+ padding: 6px 14px;
371
+ background: rgba(255, 255, 255, 0.95);
372
+ color: #111827;
373
+ border: 1px solid #e5e7eb;
374
+ border-radius: 6px;
375
+ font-weight: 600;
376
+ font-size: 11px;
377
+ cursor: pointer;
378
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
379
+ position: relative;
380
+ overflow: hidden;
381
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 1px -1px rgba(0, 0, 0, 0.1);
382
  }
383
 
384
+ .back-btn:hover {
385
+ background: #ffffff;
386
+ transform: translateX(-2px);
387
+ box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.1), 0 2px 3px -2px rgba(0, 0, 0, 0.1);
388
+ border-color: #1E3A8A;
389
+ color: #1E3A8A;
390
  }
391
 
392
+ .back-btn i {
393
+ font-size: 11px;
394
+ transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1);
395
+ }
 
 
396
 
397
+ .back-btn:hover i {
398
+ transform: translateX(-2px);
399
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
 
401
+ /* ===== Loading Overlay ===== */
402
+ .loading-overlay {
403
+ position: fixed;
404
+ top: 0;
405
+ left: 0;
406
+ right: 0;
407
+ bottom: 0;
408
+ background: linear-gradient(135deg, #011329 0%, #0a2540 100%);
409
+ display: flex;
410
+ align-items: center;
411
+ justify-content: center;
412
+ z-index: 2000;
413
+ animation: fadeIn 250ms cubic-bezier(0.4, 0, 0.2, 1);
414
+ }
415
 
416
+ .loading-spinner {
417
+ text-align: center;
418
+ color: white;
419
+ position: relative;
 
420
  }
421
 
422
+ .spinner-ring {
423
+ width: 60px;
424
+ height: 60px;
425
+ border: 2px solid rgba(56, 189, 248, 0.3);
426
+ border-top-color: #38bdf8;
427
+ border-radius: 50%;
428
+ animation: spin 1s linear infinite;
429
+ margin: 0 auto 16px;
430
+ box-shadow: 0 0 15px rgba(56, 189, 248, 0.3);
431
  }
432
 
433
+ .loading-spinner i {
434
+ position: absolute;
435
+ top: 50%;
436
+ left: 50%;
437
+ transform: translate(-50%, -50%);
438
+ font-size: 20px;
439
+ color: #38bdf8;
440
+ animation: pulse 2s infinite;
441
  }
442
 
443
+ .loading-spinner p {
444
+ color: #cbd5e1;
445
+ font-weight: 500;
446
+ font-size: 14px;
447
+ margin-top: 16px;
448
+ animation: pulse 2s ease-in-out infinite;
449
  }
450
 
451
+ /* ===== Main Layout ===== */
452
+ .case-details-summary-layout {
453
+ display: flex;
454
+ height: calc(108vh - 137px);
455
+ overflow: hidden;
456
+ background: #f8fafc;
457
+ position: relative;
458
  }
459
 
460
+ /* ===== Sidebar Navigation ===== */
461
+ .sidebar {
462
+ width: 272px;
463
+ background: #ffffff;
464
+ flex-shrink: 0;
465
+ display: flex;
466
+ flex-direction: column;
467
+ border-right: 1px solid #e5e7eb;
468
+ box-shadow: 0 6px 12px -3px rgba(0, 0, 0, 0.1), 0 3px 5px -3px rgba(0, 0, 0, 0.1);
469
+ animation: fadeInLeft 250ms cubic-bezier(0.4, 0, 0.2, 1);
470
  z-index: 100;
471
+ position: relative;
472
+ height: 88.5vh;
473
  }
474
 
475
+ .sidebar::before {
476
+ content: '';
477
+ position: absolute;
478
+ top: 0;
479
+ left: 0;
480
+ right: 0;
481
+ bottom: 0;
482
+ background: linear-gradient(to bottom, #ffffff 0%, #f9fafb 100%);
483
+ z-index: -1;
484
+ }
 
 
485
 
486
+ .sidebar-nav {
487
+ flex: 1;
488
+ padding: 16px 0;
489
+ overflow-y: auto;
490
+ overflow-x: hidden;
491
  }
492
 
493
+ .sidebar-nav::-webkit-scrollbar {
494
+ width: 5px;
 
 
 
 
 
495
  }
496
 
497
+ .sidebar-nav::-webkit-scrollbar-track {
498
+ background: #f9fafb;
499
+ border-radius: 3px;
500
+ }
 
 
 
 
 
 
 
501
 
502
+ .sidebar-nav::-webkit-scrollbar-thumb {
503
+ background: #e5e7eb;
504
+ border-radius: 3px;
505
+ }
506
+
507
+ .sidebar-nav::-webkit-scrollbar-thumb:hover {
508
+ background: #d1d5db;
509
+ }
510
+
511
+ .nav-section {
512
+ margin-bottom: 4px;
513
+ position: relative;
514
  }
515
 
516
+ .nav-section-header {
 
 
 
 
517
  display: flex;
518
+ align-items: center;
519
+ gap: 12px;
520
+ padding: 12px 16px;
521
+ color: #374151;
522
+ font-weight: 600;
523
+ font-size: 12px;
524
+ cursor: pointer;
525
+ border-left: 3px solid transparent;
526
+ background: transparent;
527
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
528
+ position: relative;
529
+ overflow: hidden;
530
+ border-bottom: 1px solid transparent;
531
  }
532
 
533
+ .nav-section-header:hover {
534
+ background: linear-gradient(90deg, rgba(30, 58, 138, 0.05), transparent);
535
+ color: #1E3A8A;
536
+ border-left-color: #1E3A8A;
537
+ transform: translateX(2px);
538
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 1px -1px rgba(0, 0, 0, 0.1);
539
+ }
540
 
541
+ .nav-section-header:hover .nav-icon-wrapper {
542
+ transform: scale(1.08);
543
+ background: linear-gradient(135deg, #1E3A8A, #2563eb);
544
+ box-shadow: 0 3px 10px 0 rgba(30, 58, 138, 0.3);
545
+ }
546
+
547
+ .nav-icon-wrapper {
548
+ width: 32px;
549
+ height: 32px;
550
+ border-radius: 6px;
551
+ background: #f9fafb;
552
+ display: flex;
553
+ align-items: center;
554
+ justify-content: center;
555
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
556
+ border: 1px solid #e5e7eb;
557
+ box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05);
558
  }
559
 
560
+ .nav-section-header i:first-child {
561
+ font-size: 14px;
562
+ color: #1E3A8A;
563
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
 
564
  }
565
 
566
+ .nav-chevron {
567
+ margin-left: auto;
568
+ transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1);
569
+ font-size: 10px;
570
+ color: #9CA3AF;
571
  }
572
 
573
+ .nav-chevron.rotated {
574
+ transform: rotate(90deg);
575
+ color: #1E3A8A;
 
 
 
 
576
  }
577
 
578
+ .nav-subgroups {
579
+ padding: 8px 0;
580
+ background: #f9fafb;
581
+ border-left: 3px solid #e5e7eb;
582
+ animation: fadeIn 250ms cubic-bezier(0.4, 0, 0.2, 1);
583
+ border-bottom: 1px solid #e5e7eb;
584
+ }
585
+
586
+ .nav-subgroup-item {
 
 
 
 
 
 
 
 
 
587
  display: flex;
588
  align-items: center;
589
+ gap: 12px;
590
+ padding: 10px 16px 10px 44px;
591
+ color: #374151;
592
+ font-size: 11px;
593
+ cursor: pointer;
594
+ border-left: 3px solid transparent;
595
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
596
+ position: relative;
597
+ border-bottom: 1px solid transparent;
598
  }
599
 
600
+ .nav-subgroup-item:hover {
601
+ background: rgba(30, 58, 138, 0.05);
602
+ color: #1E3A8A;
603
+ transform: translateX(2px);
604
+ border-bottom-color: #e5e7eb;
605
  }
606
 
607
+ .nav-subgroup-item:hover .subgroup-icon-wrapper {
608
+ transform: scale(1.08);
609
+ background: #1E3A8A;
610
+ box-shadow: 0 3px 10px 0 rgba(30, 58, 138, 0.3);
611
+ }
612
+
613
+ .nav-subgroup-item.active {
614
+ background: rgba(30, 58, 138, 0.08);
615
+ color: #1E3A8A;
616
+ font-weight: 600;
617
+ border-left-color: #1E3A8A;
618
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 1px -1px rgba(0, 0, 0, 0.1);
619
+ }
620
+
621
+ .nav-subgroup-item.active .subgroup-icon-wrapper {
622
+ background: #1E3A8A;
623
+ box-shadow: 0 3px 10px 0 rgba(30, 58, 138, 0.3);
624
+ }
625
+
626
+ .subgroup-icon-wrapper {
627
+ width: 28px;
628
+ height: 28px;
629
+ border-radius: 5px;
630
+ background: #f3f4f6;
631
+ display: flex;
632
+ align-items: center;
633
+ justify-content: center;
634
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
635
+ border: 1px solid #e5e7eb;
636
+ }
637
+
638
+ .nav-subgroup-item i {
639
+ font-size: 12px;
640
+ color: #1E3A8A;
641
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
642
+ }
643
+
644
+ .nav-subgroup-item.active i {
645
+ color: white;
646
+ }
647
+
648
+ .nav-subgroup-item:hover i {
649
+ color: white;
650
+ }
651
+
652
+ .subgroup-indicator {
653
+ margin-left: auto;
654
+ width: 5px;
655
+ height: 5px;
656
+ border-radius: 50%;
657
+ background: #22c55e;
658
+ opacity: 0;
659
+ transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1);
660
+ box-shadow: 0 0 6px #22c55e;
661
+ }
662
+
663
+ .nav-subgroup-item.active .subgroup-indicator {
664
+ opacity: 1;
665
+ animation: pulse 2s infinite;
666
+ }
667
+
668
+ /* Sidebar Footer */
669
+ .sidebar-footer {
670
+ padding: 16px;
671
+ border-top: 1px solid #e5e7eb;
672
+ background: #f9fafb;
673
+ animation: fadeInUp 250ms cubic-bezier(0.4, 0, 0.2, 1);
674
+ box-shadow: inset 0 1px 3px 0 rgba(0, 0, 0, 0.05);
675
+ }
676
+
677
+ .case-summary {
678
+ background: #ffffff;
679
+ border-radius: 10px;
680
+ padding: 16px;
681
+ border: 1px solid #e5e7eb;
682
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 1px -1px rgba(0, 0, 0, 0.1);
683
+ }
684
+
685
+ .case-summary h4 {
686
+ color: #111827;
687
+ font-size: 11px;
688
+ margin-bottom: 16px;
689
+ font-weight: 700;
690
+ text-transform: uppercase;
691
+ letter-spacing: 0.4px;
692
+ display: flex;
693
+ align-items: center;
694
+ gap: 6px;
695
+ padding-bottom: 10px;
696
+ border-bottom: 2px solid #e5e7eb;
697
+ }
698
+
699
+ .case-summary h4 i {
700
+ color: #1E3A8A;
701
+ }
702
+
703
+ .summary-item {
704
+ display: flex;
705
+ justify-content: space-between;
706
+ align-items: center;
707
+ margin-bottom: 12px;
708
+ padding: 10px;
709
+ background: #f9fafb;
710
+ border-radius: 6px;
711
+ border: 1px solid #e5e7eb;
712
+ animation: fadeInUp 250ms cubic-bezier(0.4, 0, 0.2, 1);
713
+ animation-fill-mode: both;
714
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
715
+ }
716
+
717
+ .summary-item:hover {
718
+ transform: translateY(-1px);
719
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 1px -1px rgba(0, 0, 0, 0.1);
720
+ border-color: #1E3A8A;
721
+ }
722
+
723
+ .summary-label {
724
+ color: #6b7280;
725
+ font-size: 11px;
726
+ font-weight: 600;
727
+ }
728
+
729
+ .summary-value {
730
+ font-size: 11px;
731
+ font-weight: 700;
732
+ padding: 4px 10px;
733
+ border-radius: 16px;
734
+ border: 2px solid;
735
+ min-width: 70px;
736
+ text-align: center;
737
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
738
+ box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05);
739
+ }
740
+
741
+ .summary-value:hover {
742
+ transform: scale(1.04);
743
+ }
744
+
745
+ .summary-value.open {
746
+ background: linear-gradient(135deg, #86efac, #22c55e);
747
+ color: #166534;
748
+ border-color: rgba(34, 197, 94, 0.3);
749
+ box-shadow: 0 3px 10px 0 rgba(34, 197, 94, 0.3);
750
+ }
751
+
752
+ .summary-value.under-investigation {
753
+ background: linear-gradient(135deg, #fdba74, #f97316);
754
+ color: #92400e;
755
+ border-color: rgba(249, 115, 22, 0.3);
756
+ box-shadow: 0 3px 10px 0 rgba(249, 115, 22, 0.3);
757
+ }
758
+
759
+ .summary-value.closed {
760
+ background: linear-gradient(135deg, #fca5a5, #ef4444);
761
+ color: #991b1b;
762
+ border-color: rgba(239, 68, 68, 0.3);
763
+ box-shadow: 0 3px 10px 0 rgba(239, 68, 68, 0.3);
764
+ }
765
+
766
+ .summary-value.pending,
767
+ .summary-value.not-assigned {
768
+ background: linear-gradient(135deg, #f3f4f6, #e5e7eb);
769
+ color: #374151;
770
+ border-color: #e5e7eb;
771
+ }
772
+
773
+ .summary-progress {
774
+ margin-top: 16px;
775
+ padding-top: 12px;
776
+ border-top: 2px solid #e5e7eb;
777
+ }
778
+
779
+ .progress-label {
780
+ display: flex;
781
+ justify-content: space-between;
782
+ margin-bottom: 10px;
783
+ font-size: 11px;
784
+ font-weight: 600;
785
+ color: #111827;
786
+ }
787
+
788
+ .progress-bar {
789
+ height: 6px;
790
+ background: #f3f4f6;
791
+ border-radius: 3px;
792
+ overflow: hidden;
793
+ border: 1px solid #e5e7eb;
794
+ box-shadow: inset 0 1px 3px 0 rgba(0, 0, 0, 0.05);
795
+ }
796
+
797
+ .progress-fill {
798
+ height: 100%;
799
+ background: linear-gradient(90deg, #38bdf8, #1E3A8A);
800
+ border-radius: 3px;
801
+ transition: width 1s ease-in-out;
802
+ position: relative;
803
+ overflow: hidden;
804
+ }
805
+
806
+ .progress-fill::after {
807
+ content: '';
808
+ position: absolute;
809
+ top: 0;
810
+ left: 0;
811
+ right: 0;
812
+ bottom: 0;
813
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
814
+ animation: shimmer 2s infinite;
815
+ }
816
+
817
+ /* ===== Main Content Area ===== */
818
+ .main-content {
819
+ flex: 1;
820
+ padding: 16px;
821
+ background: #f8fafc;
822
+ overflow-y: auto;
823
+ overflow-x: hidden;
824
+ display: flex;
825
+ flex-direction: column;
826
+ position: relative;
827
+ }
828
+
829
+ .main-content::before {
830
+ content: '';
831
+ position: absolute;
832
+ top: 0;
833
+ left: 0;
834
+ right: 0;
835
+ bottom: 0;
836
+ background: linear-gradient(135deg, rgba(248, 250, 252, 0.8) 0%, rgba(249, 250, 251, 0.9) 100%);
837
+ z-index: -1;
838
+ }
839
+
840
+ /* ===== Case Header Banner ===== */
841
+ .case-header-banner {
842
+ background: linear-gradient(135deg, #1E3A8A 0%, #1e40af 100%);
843
+ border-radius: 12px;
844
+ padding: 20px;
845
+ margin-bottom: 16px;
846
+ flex-shrink: 0;
847
+ box-shadow: 0 15px 30px -10px rgba(0, 0, 0, 0.2);
848
+ animation: fadeInDown 250ms cubic-bezier(0.4, 0, 0.2, 1);
849
+ position: relative;
850
+ overflow: hidden;
851
+ border: 1px solid rgba(255, 255, 255, 0.2);
852
+ }
853
+
854
+ .case-header-banner::before {
855
+ content: '';
856
+ position: absolute;
857
+ top: 0;
858
+ left: 0;
859
+ right: 0;
860
+ bottom: 0;
861
+ background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
862
+ }
863
+
864
+ .case-header-content {
865
+ position: relative;
866
+ z-index: 1;
867
+ display: flex;
868
+ justify-content: space-between;
869
+ align-items: flex-start;
870
+ gap: 20px;
871
+ }
872
+
873
+ .case-identity {
874
+ flex: 1;
875
+ min-width: 0;
876
+ }
877
+
878
+ .case-ref-top {
879
+ display: flex;
880
+ justify-content: space-between;
881
+ margin-bottom: 8px;
882
+ }
883
+
884
+ .case-ref-label {
885
+ text-transform: uppercase;
886
+ letter-spacing: 1.2px;
887
+ margin-bottom: 2px;
888
+ display: block;
889
+ color: rgba(255, 255, 255, 0.9);
890
+ font-weight: 700;
891
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
892
+ }
893
+
894
+ .case-actions {
895
+ display: flex;
896
+ gap: 6px;
897
+ }
898
+
899
+ .case-action-btn {
900
+ width: 30px;
901
+ height: 30px;
902
+ border-radius: 50%;
903
+ border: 1px solid rgba(255, 255, 255, 0.3);
904
+ background: rgba(255, 255, 255, 0.1);
905
+ color: white;
906
+ cursor: pointer;
907
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
908
+ display: flex;
909
+ align-items: center;
910
+ justify-content: center;
911
+ backdrop-filter: blur(8px);
912
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 1px -1px rgba(0, 0, 0, 0.1);
913
+ }
914
+
915
+ .case-action-btn:hover {
916
+ background: rgba(255, 255, 255, 0.2);
917
+ transform: scale(1.08);
918
+ box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.1), 0 2px 3px -2px rgba(0, 0, 0, 0.1);
919
+ }
920
+
921
+ .case-action-btn.favorited {
922
+ background: rgba(245, 158, 11, 0.3);
923
+ border-color: rgba(245, 158, 11, 0.5);
924
+ box-shadow: 0 0 12px rgba(245, 158, 11, 0.3);
925
+ }
926
+
927
+ .case-ref-value {
928
+ font-size: 28px;
929
+ font-weight: 900;
930
+ margin: 0 0 16px 0;
931
+ color: white;
932
+ text-shadow: 0 1px 6px rgba(0, 0, 0, 0.3);
933
+ background: linear-gradient(135deg, #ffffff 0%, #e3f6ff 100%);
934
+ -webkit-background-clip: text;
935
+ -webkit-text-fill-color: transparent;
936
+ background-clip: text;
937
+ letter-spacing: -0.4px;
938
+ }
939
+
940
+ .case-tags {
941
+ display: flex;
942
+ gap: 8px;
943
+ flex-wrap: wrap;
944
+ }
945
+
946
+ .case-tag {
947
+ padding: 6px 12px;
948
+ background: rgba(255, 255, 255, 0.15);
949
+ border: 1px solid rgba(255, 255, 255, 0.25);
950
+ border-radius: 16px;
951
+ font-size: 11px;
952
+ font-weight: 600;
953
+ color: white;
954
+ display: flex;
955
+ align-items: center;
956
+ gap: 6px;
957
+ backdrop-filter: blur(8px);
958
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
959
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 1px -1px rgba(0, 0, 0, 0.1);
960
+ }
961
+
962
+ .case-tag:hover {
963
+ background: rgba(255, 255, 255, 0.25);
964
+ transform: translateY(-1px);
965
+ box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.1), 0 2px 3px -2px rgba(0, 0, 0, 0.1);
966
+ }
967
+
968
+ .case-meta {
969
+ display: flex;
970
+ align-items: center;
971
+ gap: 12px;
972
+ flex-wrap: wrap;
973
+ }
974
+
975
+ .case-status-badge {
976
+ padding: 8px 16px;
977
+ border-radius: 20px;
978
+ font-size: 11px;
979
+ font-weight: 800;
980
+ text-transform: uppercase;
981
+ letter-spacing: 0.4px;
982
+ border: 2px solid;
983
+ display: flex;
984
+ align-items: center;
985
+ gap: 8px;
986
+ backdrop-filter: blur(8px);
987
+ animation: pulse 2s infinite;
988
+ box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.1), 0 2px 3px -2px rgba(0, 0, 0, 0.1);
989
+ }
990
+
991
+ .case-status-badge.open {
992
+ background: linear-gradient(135deg, rgba(34, 197, 94, 0.25), rgba(34, 197, 94, 0.4));
993
+ color: #dcfce7;
994
+ border-color: rgba(34, 197, 94, 0.5);
995
+ box-shadow: 0 3px 10px 0 rgba(34, 197, 94, 0.3);
996
+ }
997
+
998
+ .case-status-badge.under-investigation {
999
+ background: linear-gradient(135deg, rgba(249, 115, 22, 0.25), rgba(249, 115, 22, 0.4));
1000
+ color: #fef3c7;
1001
+ border-color: rgba(249, 115, 22, 0.5);
1002
+ box-shadow: 0 3px 10px 0 rgba(249, 115, 22, 0.3);
1003
+ }
1004
+
1005
+ .case-status-badge.closed {
1006
+ background: linear-gradient(135deg, rgba(239, 68, 68, 0.25), rgba(239, 68, 68, 0.4));
1007
+ color: #fee2e2;
1008
+ border-color: rgba(239, 68, 68, 0.5);
1009
+ box-shadow: 0 3px 10px 0 rgba(239, 68, 68, 0.3);
1010
+ }
1011
+
1012
+ .case-status-badge.pending,
1013
+ .case-status-badge.not-assigned {
1014
+ background: linear-gradient(135deg, rgba(156, 163, 175, 0.25), rgba(156, 163, 175, 0.4));
1015
+ color: #f3f4f6;
1016
+ border-color: rgba(156, 163, 175, 0.5);
1017
+ }
1018
+
1019
+ .status-dot {
1020
+ width: 8px;
1021
+ height: 8px;
1022
+ border-radius: 50%;
1023
+ display: inline-block;
1024
+ animation: pulse 2s infinite;
1025
+ box-shadow: 0 0 8px currentColor;
1026
+ }
1027
+
1028
+ .case-priority {
1029
+ display: flex;
1030
+ align-items: center;
1031
+ gap: 6px;
1032
+ font-size: 12px;
1033
+ color: white;
1034
+ }
1035
+
1036
+ .priority-label {
1037
+ color: rgba(255, 255, 255, 0.9);
1038
+ font-size: 11px;
1039
+ font-weight: 600;
1040
+ }
1041
+
1042
+ .priority-value {
1043
+ font-weight: 800;
1044
+ padding: 8px 14px;
1045
+ border-radius: 16px;
1046
+ border: 2px solid;
1047
+ color: white;
1048
+ font-size: 11px;
1049
+ display: flex;
1050
+ align-items: center;
1051
+ gap: 6px;
1052
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
1053
+ box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.1), 0 2px 3px -2px rgba(0, 0, 0, 0.1);
1054
+ }
1055
+
1056
+ .priority-value:hover {
1057
+ transform: translateY(-1px);
1058
+ box-shadow: 0 6px 12px -3px rgba(0, 0, 0, 0.1), 0 3px 5px -3px rgba(0, 0, 0, 0.1);
1059
+ }
1060
+
1061
+ .priority-value.high {
1062
+ background: linear-gradient(135deg, rgba(239, 68, 68, 0.3), rgba(220, 38, 38, 0.5));
1063
+ border-color: rgba(239, 68, 68, 0.6);
1064
+ }
1065
+
1066
+ .priority-value.medium {
1067
+ background: linear-gradient(135deg, rgba(249, 115, 22, 0.3), rgba(217, 119, 6, 0.5));
1068
+ border-color: rgba(249, 115, 22, 0.6);
1069
+ }
1070
+
1071
+ .priority-value.low {
1072
+ background: linear-gradient(135deg, rgba(34, 197, 94, 0.3), rgba(5, 150, 105, 0.5));
1073
+ border-color: rgba(34, 197, 94, 0.6);
1074
+ }
1075
+
1076
+ /* Quick Info Items */
1077
+ .case-quick-info {
1078
+ display: grid;
1079
+ grid-template-columns: repeat(3, 1fr);
1080
+ gap: 12px;
1081
+ min-width: 280px;
1082
+ }
1083
+
1084
+ .quick-info-item {
1085
+ display: flex;
1086
+ align-items: center;
1087
+ gap: 12px;
1088
+ padding: 16px;
1089
+ background: rgba(255, 255, 255, 0.95);
1090
+ border-radius: 10px;
1091
+ border: 1px solid rgba(255, 255, 255, 0.2);
1092
+ backdrop-filter: blur(15px);
1093
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
1094
+ position: relative;
1095
+ overflow: hidden;
1096
+ animation: fadeInUp 250ms cubic-bezier(0.4, 0, 0.2, 1);
1097
+ animation-fill-mode: both;
1098
+ box-shadow: 0 6px 20px rgba(31, 38, 135, 0.1);
1099
+ }
1100
+
1101
+ .quick-info-item::before {
1102
+ content: '';
1103
+ position: absolute;
1104
+ top: 0;
1105
+ left: 0;
1106
+ right: 0;
1107
+ bottom: 0;
1108
+ background: linear-gradient(135deg, rgba(56, 189, 248, 0.05), transparent);
1109
+ opacity: 0;
1110
+ transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1);
1111
+ }
1112
+
1113
+ .quick-info-item:hover::before {
1114
+ opacity: 1;
1115
+ }
1116
+
1117
+ .quick-info-item:nth-child(1) {
1118
+ animation-delay: 0.1s;
1119
+ }
1120
+
1121
+ .quick-info-item:nth-child(2) {
1122
+ animation-delay: 0.2s;
1123
+ }
1124
+
1125
+ .quick-info-item:nth-child(3) {
1126
+ animation-delay: 0.3s;
1127
+ }
1128
+
1129
+ .quick-info-item:hover {
1130
+ transform: translateY(-4px);
1131
+ box-shadow: 0 12px 20px -5px rgba(0, 0, 0, 0.1), 0 6px 8px -4px rgba(0, 0, 0, 0.1);
1132
+ border-color: rgba(56, 189, 248, 0.3);
1133
+ }
1134
+
1135
+ .quick-info-item:hover .info-icon-wrapper {
1136
+ transform: scale(1.08) rotate(5deg);
1137
+ box-shadow: 0 3px 10px 0 rgba(30, 58, 138, 0.3);
1138
+ }
1139
+
1140
+ .info-icon-wrapper {
1141
+ width: 44px;
1142
+ height: 44px;
1143
+ border-radius: 10px;
1144
+ background: linear-gradient(135deg, #1E3A8A, #2563eb);
1145
+ display: flex;
1146
+ align-items: center;
1147
+ justify-content: center;
1148
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
1149
+ box-shadow: 0 3px 10px 0 rgba(30, 58, 138, 0.3);
1150
+ border: 2px solid rgba(255, 255, 255, 0.3);
1151
+ }
1152
+
1153
+ .quick-info-item i {
1154
+ font-size: 20px;
1155
+ color: white;
1156
+ }
1157
+
1158
+ .info-content {
1159
+ flex: 1;
1160
+ min-width: 0;
1161
+ }
1162
+
1163
+ .info-label {
1164
+ display: block;
1165
+ font-size: 10px;
1166
+ text-transform: uppercase;
1167
+ letter-spacing: 0.8px;
1168
+ margin-bottom: 6px;
1169
+ color: #6b7280;
1170
+ font-weight: 700;
1171
+ }
1172
+
1173
+ .info-value {
1174
+ display: block;
1175
+ font-size: 16px;
1176
+ font-weight: 800;
1177
+ color: #111827;
1178
+ line-height: 1.3;
1179
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
1180
+ }
1181
+
1182
+ .info-hover-effect {
1183
+ position: absolute;
1184
+ top: 0;
1185
+ left: 0;
1186
+ right: 0;
1187
+ bottom: 0;
1188
+ background: linear-gradient(135deg, transparent, rgba(56, 189, 248, 0.1), transparent);
1189
+ opacity: 0;
1190
+ transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1);
1191
+ }
1192
+
1193
+ .quick-info-item:hover .info-hover-effect {
1194
+ opacity: 1;
1195
+ }
1196
+
1197
+ /* ===== Details Section ===== */
1198
+ .details-section {
1199
+ flex: 1;
1200
+ display: flex;
1201
+ flex-direction: column;
1202
+ background: #ffffff;
1203
+ border-radius: 12px;
1204
+ border: 1px solid #e5e7eb;
1205
+ overflow: hidden;
1206
+ min-height: 0;
1207
+ box-shadow: 0 15px 30px -10px rgba(0, 0, 0, 0.2);
1208
+ animation: scaleIn 250ms cubic-bezier(0.4, 0, 0.2, 1);
1209
+ }
1210
+
1211
+ /* Section Header */
1212
+ .section-header {
1213
+ padding: 16px 20px;
1214
+ background: linear-gradient(135deg, #f9fafb, #ffffff);
1215
+ border-bottom: 2px solid #e5e7eb;
1216
+ display: flex;
1217
+ align-items: center;
1218
+ justify-content: space-between;
1219
+ flex-shrink: 0;
1220
+ position: relative;
1221
+ }
1222
+
1223
+ .section-header::after {
1224
+ content: '';
1225
+ position: absolute;
1226
+ bottom: -2px;
1227
+ left: 0;
1228
+ right: 0;
1229
+ height: 2px;
1230
+ background: linear-gradient(90deg, #1E3A8A, #38bdf8);
1231
+ opacity: 0.5;
1232
+ }
1233
+
1234
+ .section-title {
1235
+ display: flex;
1236
+ align-items: center;
1237
+ gap: 16px;
1238
+ }
1239
+
1240
+ .section-icon-wrapper {
1241
+ width: 44px;
1242
+ height: 44px;
1243
+ border-radius: 10px;
1244
+ background: linear-gradient(135deg, #1E3A8A, #2563eb);
1245
+ display: flex;
1246
+ align-items: center;
1247
+ justify-content: center;
1248
+ box-shadow: 0 3px 10px 0 rgba(30, 58, 138, 0.3);
1249
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
1250
+ border: 2px solid rgba(255, 255, 255, 0.3);
1251
+ }
1252
+
1253
+ .section-title:hover .section-icon-wrapper {
1254
+ transform: scale(1.04) rotate(5deg);
1255
+ box-shadow: 0 6px 12px -3px rgba(0, 0, 0, 0.1), 0 3px 5px -3px rgba(0, 0, 0, 0.1);
1256
+ }
1257
+
1258
+ .section-title i {
1259
+ font-size: 20px;
1260
+ color: white;
1261
+ }
1262
+
1263
+ .section-title h2 {
1264
+ font-size: 18px;
1265
+ font-weight: 800;
1266
+ color: #111827;
1267
+ margin: 0;
1268
+ letter-spacing: 0.4px;
1269
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
1270
+ }
1271
+
1272
+ .section-subtitle {
1273
+ font-size: 12px;
1274
+ color: #1E3A8A;
1275
+ font-weight: 700;
1276
+ margin-top: 4px;
1277
+ display: flex;
1278
+ align-items: center;
1279
+ gap: 6px;
1280
+ }
1281
+
1282
+ .section-subtitle::before {
1283
+ content: '';
1284
+ width: 6px;
1285
+ height: 6px;
1286
+ border-radius: 50%;
1287
+ background: #38bdf8;
1288
+ display: inline-block;
1289
+ box-shadow: 0 0 8px #38bdf8;
1290
+ }
1291
+
1292
+ .section-actions {
1293
+ display: flex;
1294
+ gap: 8px;
1295
+ }
1296
+
1297
+ .section-action-btn {
1298
+ width: 36px;
1299
+ height: 36px;
1300
+ border-radius: 10px;
1301
+ border: 2px solid #e5e7eb;
1302
+ background: #ffffff;
1303
+ color: #6b7280;
1304
+ cursor: pointer;
1305
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
1306
+ display: flex;
1307
+ align-items: center;
1308
+ justify-content: center;
1309
+ font-size: 14px;
1310
+ position: relative;
1311
+ overflow: hidden;
1312
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 1px -1px rgba(0, 0, 0, 0.1);
1313
+ }
1314
+
1315
+ .section-action-btn::before {
1316
+ content: '';
1317
+ position: absolute;
1318
+ top: 50%;
1319
+ left: 50%;
1320
+ width: 0;
1321
+ height: 0;
1322
+ border-radius: 50%;
1323
+ background: rgba(30, 58, 138, 0.1);
1324
+ transform: translate(-50%, -50%);
1325
+ transition: width 0.6s, height 0.6s;
1326
+ }
1327
+
1328
+ .section-action-btn:hover::before {
1329
+ width: 150px;
1330
+ height: 150px;
1331
+ }
1332
+
1333
+ .section-action-btn:hover {
1334
+ background: #1E3A8A;
1335
+ border-color: #1E3A8A;
1336
+ color: white;
1337
+ transform: translateY(-2px);
1338
+ box-shadow: 0 6px 12px -3px rgba(0, 0, 0, 0.1), 0 3px 5px -3px rgba(0, 0, 0, 0.1);
1339
+ }
1340
+
1341
+ .section-action-btn.expand-all:hover {
1342
+ background: #7c3aed;
1343
+ border-color: #7c3aed;
1344
+ box-shadow: 0 3px 15px rgba(124, 58, 237, 0.4);
1345
+ }
1346
+
1347
+ /* ===== Footer ===== */
1348
+ footer {
1349
+ background: linear-gradient(135deg, rgba(30, 58, 138, 0.9) 0%, rgba(1, 19, 41, 0.95) 100%);
1350
+ color: #fff;
1351
+ text-align: center;
1352
+ padding: 12px 0;
1353
+ position: fixed;
1354
+ bottom: 0;
1355
+ left: 0;
1356
+ width: 100%;
1357
+ font-size: 12px;
1358
+ font-weight: 500;
1359
+ z-index: 1000;
1360
+ }
1361
+
1362
+ /* Details Content */
1363
+ .details-content-scrollable {
1364
+ overflow-y: scroll;
1365
+ padding: 20px;
1366
+ background: #ffffff;
1367
+ height: 63vh;
1368
+ position: relative;
1369
+ }
1370
+
1371
+ .details-content-scrollable::-webkit-scrollbar {
1372
+ width: 8px;
1373
+ }
1374
+
1375
+ .details-content-scrollable::-webkit-scrollbar-track {
1376
+ background: #f9fafb;
1377
+ border-radius: 4px;
1378
+ border: 1px solid #e5e7eb;
1379
+ }
1380
+
1381
+ .details-content-scrollable::-webkit-scrollbar-thumb {
1382
+ background: #d1d5db;
1383
+ border-radius: 4px;
1384
+ border: 2px solid #f9fafb;
1385
+ }
1386
+
1387
+ .details-content-scrollable::-webkit-scrollbar-thumb:hover {
1388
+ background: #9ca3af;
1389
+ }
1390
+
1391
+ /* Form Grid */
1392
+ .form-grid {
1393
+ display: grid;
1394
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
1395
+ gap: 16px;
1396
+ }
1397
+
1398
+ .form-group {
1399
+ display: flex;
1400
+ flex-direction: column;
1401
+ animation: fadeIn 250ms cubic-bezier(0.4, 0, 0.2, 1);
1402
+ }
1403
+
1404
+ .form-group.description-field {
1405
+ grid-column: 1 / -1;
1406
+ }
1407
+
1408
+ .form-group.important-field {
1409
+ border-left: 4px solid #1E3A8A;
1410
+ padding-left: 12px;
1411
+ background: linear-gradient(90deg, rgba(30, 58, 138, 0.03), transparent);
1412
+ border-radius: 0 10px 10px 0;
1413
+ }
1414
+
1415
+ .form-group.expanded .field-container {
1416
+ box-shadow: 0 12px 20px -5px rgba(0, 0, 0, 0.1), 0 6px 8px -4px rgba(0, 0, 0, 0.1);
1417
+ border-color: #1E3A8A;
1418
+ transform: translateY(-4px);
1419
+ background: linear-gradient(135deg, #ffffff, #f9fafb);
1420
+ }
1421
+
1422
+ .form-group.expanded .field-expand-icon {
1423
+ transform: rotate(180deg);
1424
+ }
1425
+
1426
+ /* Field Container */
1427
+ .field-container {
1428
+ background: #ffffff;
1429
+ border: 2px solid #e5e7eb;
1430
+ border-radius: 10px;
1431
+ overflow: hidden;
1432
+ height: 100%;
1433
+ display: flex;
1434
+ flex-direction: column;
1435
+ min-height: 0;
1436
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
1437
+ cursor: pointer;
1438
+ position: relative;
1439
+ box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.1), 0 2px 3px -2px rgba(0, 0, 0, 0.1);
1440
+ align-items: center;
1441
+ }
1442
+
1443
+ .field-container:hover {
1444
+ border-color: #1E3A8A;
1445
+ box-shadow: 0 12px 20px -5px rgba(0, 0, 0, 0.1), 0 6px 8px -4px rgba(0, 0, 0, 0.1);
1446
+ transform: translateY(-3px);
1447
+ }
1448
+
1449
+ .field-container:hover .field-icon-wrapper {
1450
+ transform: scale(1.08);
1451
+ background: #1E3A8A;
1452
+ box-shadow: 0 3px 10px 0 rgba(30, 58, 138, 0.3);
1453
+ }
1454
+
1455
+ .field-container:hover .field-icon-wrapper i {
1456
+ color: white;
1457
+ }
1458
+
1459
+ .field-header {
1460
+ display: flex;
1461
+ justify-content: space-between;
1462
+ align-items: center;
1463
+ padding: 16px;
1464
+ background: linear-gradient(135deg, #f9fafb, #ffffff);
1465
+ border-bottom: 2px solid #e5e7eb;
1466
+ flex-shrink: 0;
1467
+ gap: 0.2vw;
1468
+ }
1469
+
1470
+ .field-label {
1471
+ display: flex;
1472
+ align-items: center;
1473
+ gap: 12px;
1474
+ min-width: 0;
1475
+ flex: 1;
1476
+ }
1477
+
1478
+ .field-icon-wrapper {
1479
+ width: 36px;
1480
+ height: 36px;
1481
+ border-radius: 6px;
1482
+ background: #f3f4f6;
1483
+ display: flex;
1484
+ align-items: center;
1485
+ justify-content: center;
1486
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
1487
+ border: 2px solid #e5e7eb;
1488
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 1px -1px rgba(0, 0, 0, 0.1);
1489
+ }
1490
+
1491
+ .field-label i {
1492
+ font-size: 14px;
1493
+ color: #1E3A8A;
1494
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
1495
+ }
1496
+
1497
+ .field-label-text {
1498
+ color: #374151;
1499
+ font-size: 12px;
1500
+ font-weight: 800;
1501
+ text-transform: uppercase;
1502
+ letter-spacing: 0.4px;
1503
+ overflow: hidden;
1504
+ text-overflow: ellipsis;
1505
+ flex: 1;
1506
+ }
1507
+
1508
+ .field-meta {
1509
+ display: flex;
1510
+ align-items: center;
1511
+ gap: 12px;
1512
+ flex-shrink: 0;
1513
+ }
1514
+
1515
+ .field-type {
1516
+ font-size: 10px;
1517
+ color: #6b7280;
1518
+ background: #f3f4f6;
1519
+ padding: 4px 10px;
1520
+ border-radius: 12px;
1521
+ text-transform: uppercase;
1522
+ letter-spacing: 0.4px;
1523
+ font-weight: 800;
1524
+ white-space: nowrap;
1525
+ border: 1px solid #e5e7eb;
1526
+ box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05);
1527
+ }
1528
+
1529
+ .field-expand-icon {
1530
+ font-size: 12px;
1531
+ color: #9CA3AF;
1532
+ transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1);
1533
+ background: #ffffff;
1534
+ width: 24px;
1535
+ height: 24px;
1536
+ border-radius: 50%;
1537
+ display: flex;
1538
+ align-items: center;
1539
+ justify-content: center;
1540
+ border: 1px solid #e5e7eb;
1541
+ box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05);
1542
+ }
1543
+
1544
+ .field-value-container {
1545
+ padding: 16px;
1546
+ background: #ffffff;
1547
+ flex: 1;
1548
+ min-height: 0;
1549
+ display: flex;
1550
+ align-items: center;
1551
+ justify-content: space-between;
1552
+ gap: 16px;
1553
+ }
1554
+
1555
+ .field-value {
1556
+ color: #111827;
1557
+ font-size: 14px;
1558
+ line-height: 1.6;
1559
+ word-break: break-word;
1560
+ font-weight: 600;
1561
+ width: 100%;
1562
+ flex: 1;
1563
+ }
1564
+
1565
+ .field-value.empty-field {
1566
+ background: linear-gradient(135deg, #f9fafb, #f8fafc);
1567
+ border-left: 4px solid #e5e7eb;
1568
+ padding: 16px;
1569
+ border-radius: 6px;
1570
+ font-style: italic;
1571
+ animation: pulse 3s infinite;
1572
+ border: 1px solid #e5e7eb;
1573
+ box-shadow: inset 0 1px 3px 0 rgba(0, 0, 0, 0.05);
1574
+ }
1575
+
1576
+ /* Not Provided State */
1577
+ .not-provided {
1578
+ color: #9CA3AF;
1579
+ font-style: italic;
1580
+ display: flex;
1581
+ align-items: center;
1582
+ gap: 8px;
1583
+ font-size: 13px;
1584
+ font-weight: 700;
1585
+ padding: 10px;
1586
+ background: rgba(156, 163, 175, 0.1);
1587
+ border-radius: 6px;
1588
+ border: 1px dashed #e5e7eb;
1589
+ }
1590
+
1591
+ .not-provided i {
1592
+ font-size: 16px;
1593
+ color: #9CA3AF;
1594
+ animation: pulse 2s infinite;
1595
+ }
1596
+
1597
+ .field-actions {
1598
+ display: flex;
1599
+ gap: 8px;
1600
+ flex-shrink: 0;
1601
+ }
1602
+
1603
+ .field-action-btn {
1604
+ width: 30px;
1605
+ height: 30px;
1606
+ border-radius: 6px;
1607
+ border: 2px solid #e5e7eb;
1608
+ background: #ffffff;
1609
+ color: #6b7280;
1610
+ cursor: pointer;
1611
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
1612
+ display: flex;
1613
+ align-items: center;
1614
+ justify-content: center;
1615
+ font-size: 12px;
1616
+ position: relative;
1617
+ overflow: hidden;
1618
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 1px -1px rgba(0, 0, 0, 0.1);
1619
+ }
1620
+
1621
+ .field-action-btn:hover {
1622
+ background: #1E3A8A;
1623
+ border-color: #1E3A8A;
1624
+ color: white;
1625
+ transform: scale(1.08);
1626
+ box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.1), 0 2px 3px -2px rgba(0, 0, 0, 0.1);
1627
+ }
1628
+
1629
+ .field-footer {
1630
+ padding: 12px 16px;
1631
+ background: linear-gradient(135deg, #f9fafb, #ffffff);
1632
+ border-top: 2px solid #e5e7eb;
1633
+ display: flex;
1634
+ justify-content: space-between;
1635
+ font-size: 11px;
1636
+ color: #6b7280;
1637
+ font-weight: 500;
1638
+ }
1639
+
1640
+ .field-hint {
1641
+ font-style: italic;
1642
+ background: rgba(30, 58, 138, 0.05);
1643
+ padding: 3px 8px;
1644
+ border-radius: 5px;
1645
+ border: 1px solid rgba(30, 58, 138, 0.1);
1646
+ }
1647
+
1648
+ .field-updated {
1649
+ font-weight: 600;
1650
+ color: #374151;
1651
+ background: rgba(156, 163, 175, 0.1);
1652
+ padding: 3px 8px;
1653
+ border-radius: 5px;
1654
+ border: 1px solid #e5e7eb;
1655
+ }
1656
+
1657
+ /* Empty Fields Summary */
1658
+ .empty-fields-summary {
1659
+ margin-top: 20px;
1660
+ padding: 20px;
1661
+ background: linear-gradient(135deg, #fef3c7, #fef9c3);
1662
+ border: 2px solid #fde68a;
1663
+ border-radius: 10px;
1664
+ animation: fadeIn 250ms cubic-bezier(0.4, 0, 0.2, 1);
1665
+ box-shadow: 0 6px 12px -3px rgba(0, 0, 0, 0.1), 0 3px 5px -3px rgba(0, 0, 0, 0.1);
1666
+ }
1667
+
1668
+ .summary-header {
1669
+ display: flex;
1670
+ align-items: center;
1671
+ gap: 12px;
1672
+ margin-bottom: 16px;
1673
+ }
1674
+
1675
+ .summary-header i {
1676
+ font-size: 24px;
1677
+ color: #d97706;
1678
+ animation: pulse 2s infinite;
1679
+ }
1680
+
1681
+ .summary-header h3 {
1682
+ font-size: 16px;
1683
+ color: #92400e;
1684
+ font-weight: 800;
1685
+ margin: 0;
1686
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
1687
+ }
1688
+
1689
+ .empty-fields-summary p {
1690
+ color: #92400e;
1691
+ margin-bottom: 20px;
1692
+ line-height: 1.6;
1693
+ font-size: 13px;
1694
+ font-weight: 500;
1695
  }
1696
 
1697
+ .fill-empty-btn {
1698
+ background: linear-gradient(135deg, #f97316, #ea580c);
1699
+ color: white;
1700
+ border: none;
1701
+ padding: 12px 20px;
1702
+ border-radius: 10px;
1703
+ font-weight: 700;
1704
+ font-size: 13px;
1705
+ cursor: pointer;
1706
+ display: flex;
1707
+ align-items: center;
1708
+ gap: 10px;
1709
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
1710
+ box-shadow: 0 3px 10px 0 rgba(249, 115, 22, 0.3);
1711
+ border: 2px solid rgba(249, 115, 22, 0.3);
1712
  }
1713
 
1714
+ .fill-empty-btn:hover {
1715
+ transform: translateY(-2px);
1716
+ box-shadow: 0 12px 20px -5px rgba(0, 0, 0, 0.1), 0 6px 8px -4px rgba(0, 0, 0, 0.1);
1717
+ }
1718
 
1719
+ /* Empty State */
1720
+ .empty-state {
1721
+ flex: 1;
1722
+ display: flex;
1723
+ flex-direction: column;
1724
+ justify-content: center;
1725
+ align-items: center;
1726
+ padding: 60px 32px;
1727
+ text-align: center;
1728
+ background: #ffffff;
1729
+ border-radius: 12px;
1730
+ border: 2px dashed #e5e7eb;
1731
+ animation: fadeIn 250ms cubic-bezier(0.4, 0, 0.2, 1);
1732
+ box-shadow: 0 6px 12px -3px rgba(0, 0, 0, 0.1), 0 3px 5px -3px rgba(0, 0, 0, 0.1);
1733
+ position: relative;
1734
+ overflow: hidden;
1735
+ }
1736
+
1737
+ .empty-state::before {
1738
+ content: '';
1739
+ position: absolute;
1740
+ top: 0;
1741
+ left: 0;
1742
+ right: 0;
1743
+ bottom: 0;
1744
+ background: linear-gradient(135deg, rgba(30, 58, 138, 0.02), rgba(56, 189, 248, 0.02));
1745
+ z-index: -1;
1746
  }
1747
 
1748
+ .empty-state-icon {
1749
+ font-size: 60px;
1750
+ color: #1E3A8A;
1751
+ margin-bottom: 32px;
1752
+ position: relative;
1753
+ animation: float 6s ease-in-out infinite;
1754
+ }
1755
+
1756
+ .pulse-ring {
1757
+ position: absolute;
1758
+ top: 50%;
1759
+ left: 50%;
1760
+ transform: translate(-50%, -50%);
1761
+ width: 80px;
1762
+ height: 80px;
1763
+ border: 2px solid #38bdf8;
1764
+ border-radius: 50%;
1765
+ animation: pulse-ring 3s infinite;
1766
+ }
1767
+
1768
+ .empty-state h3 {
1769
+ font-size: 24px;
1770
+ color: #111827;
1771
+ margin-bottom: 16px;
1772
+ font-weight: 900;
1773
+ background: linear-gradient(135deg, #1E3A8A, #2563eb);
1774
+ -webkit-background-clip: text;
1775
+ -webkit-text-fill-color: transparent;
1776
+ background-clip: text;
1777
+ letter-spacing: -0.4px;
1778
+ }
1779
+
1780
+ .empty-state p {
1781
+ color: #6b7280;
1782
+ max-width: 400px;
1783
+ line-height: 1.6;
1784
+ margin-bottom: 32px;
1785
+ font-size: 16px;
1786
+ font-weight: 500;
1787
+ }
1788
+
1789
+ .empty-state-actions {
1790
+ display: flex;
1791
+ gap: 16px;
1792
+ margin-bottom: 32px;
1793
+ }
1794
+
1795
+ .primary-btn {
1796
+ padding: 14px 28px;
1797
+ background: linear-gradient(135deg, #1E3A8A, #2563eb);
1798
+ color: white;
1799
+ border: none;
1800
+ border-radius: 10px;
1801
+ font-weight: 800;
1802
+ font-size: 14px;
1803
+ cursor: pointer;
1804
+ display: flex;
1805
+ align-items: center;
1806
+ gap: 12px;
1807
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
1808
+ position: relative;
1809
+ overflow: hidden;
1810
+ box-shadow: 0 3px 10px 0 rgba(30, 58, 138, 0.3);
1811
+ border: 2px solid rgba(255, 255, 255, 0.3);
1812
+ }
1813
+
1814
+ .primary-btn:hover {
1815
+ transform: translateY(-4px);
1816
+ box-shadow: 0 15px 30px -10px rgba(0, 0, 0, 0.2);
1817
+ letter-spacing: 0.5px;
1818
  }
1819
+
1820
+ .secondary-btn {
1821
+ padding: 14px 28px;
1822
+ background: white;
1823
+ color: #1E3A8A;
1824
+ border: 2px solid #1E3A8A;
1825
+ border-radius: 10px;
1826
+ font-weight: 800;
1827
+ font-size: 14px;
1828
+ cursor: pointer;
1829
+ display: flex;
1830
+ align-items: center;
1831
+ gap: 12px;
1832
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
1833
+ box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.1), 0 2px 3px -2px rgba(0, 0, 0, 0.1);
1834
  }
1835
 
1836
+ .secondary-btn:hover {
1837
+ background: #1E3A8A;
1838
+ color: white;
1839
+ transform: translateY(-4px);
1840
+ box-shadow: 0 6px 12px -3px rgba(0, 0, 0, 0.1), 0 3px 5px -3px rgba(0, 0, 0, 0.1);
1841
  }
1842
 
1843
+ .empty-state-tips {
1844
+ margin-top: 32px;
1845
+ padding-top: 16px;
1846
+ border-top: 2px solid #e5e7eb;
1847
+ width: 100%;
1848
+ max-width: 400px;
1849
+ }
1850
+
1851
+ .tip {
1852
+ display: flex;
1853
+ align-items: center;
1854
+ gap: 12px;
1855
+ color: #6b7280;
1856
+ font-size: 13px;
1857
+ font-weight: 500;
1858
+ padding: 10px;
1859
+ background: rgba(30, 58, 138, 0.05);
1860
+ border-radius: 6px;
1861
+ border: 1px solid rgba(30, 58, 138, 0.1);
1862
+ }
1863
+
1864
+ .tip i {
1865
+ color: #f97316;
1866
+ font-size: 18px;
1867
+ animation: pulse 2s infinite;
1868
  }
1869
+
1870
+ /* Quick Actions Bar */
1871
+ .quick-actions-bar {
1872
+ position: fixed;
1873
+ bottom: 70px; /* Increased from 59px for better visibility above footer */
1874
+ right: 20px;
1875
+ display: flex;
1876
+ flex-direction: column;
1877
+ gap: 12px;
1878
+ z-index: 1101;
1879
+ animation: fadeInUp 250ms cubic-bezier(0.4, 0, 0.2, 1);
1880
+ background: rgba(255, 255, 255, 0.9);
1881
+ backdrop-filter: blur(10px);
1882
+ padding: 10px;
1883
+ border-radius: 16px;
1884
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.15);
1885
+ border: 1px solid rgba(30, 58, 138, 0.1);
1886
  }
1887
 
1888
+ .quick-action {
1889
+ width: 48px;
1890
+ height: 48px;
1891
+ border-radius: 50%;
1892
+ background: linear-gradient(135deg, #1E3A8A, #2563eb);
1893
+ color: white;
1894
+ border: none;
1895
+ cursor: pointer;
1896
+ display: flex;
1897
+ flex-direction: column;
1898
+ align-items: center;
1899
+ justify-content: center;
1900
+ gap: 2px;
1901
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
1902
+ box-shadow: 0 3px 10px 0 rgba(30, 58, 138, 0.3);
1903
+ border: 2px solid rgba(255, 255, 255, 0.3);
1904
+ position: relative;
1905
+ overflow: hidden;
1906
+ }
1907
+
1908
+ .quick-action:hover::after {
1909
+ opacity: 1;
1910
+ }
1911
+
1912
+ .quick-action::before {
1913
+ content: '';
1914
+ position: absolute;
1915
+ right: 52px;
1916
+ top: 50%;
1917
+ transform: translateY(-50%);
1918
+ border: 6px solid transparent;
1919
+ border-left-color: #1f2937;
1920
  opacity: 0;
1921
+ pointer-events: none;
1922
+ transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1);
1923
+ z-index: 1102;
1924
  }
1925
 
1926
+ .quick-action:hover::before {
1927
  opacity: 1;
1928
+ }
1929
+
1930
+ .quick-action:hover {
1931
+ transform: translateY(-4px) scale(1.08);
1932
+ box-shadow: 0 15px 30px -10px rgba(0, 0, 0, 0.2);
1933
+ }
1934
+
1935
+ /* Different colors for each action button */
1936
+ .quick-action:nth-child(2):hover {
1937
+ background: linear-gradient(135deg, #059669, #10b981);
1938
+ }
1939
+
1940
+ .quick-action:nth-child(3):hover {
1941
+ background: linear-gradient(135deg, #7c3aed, #8b5cf6);
1942
+ }
1943
+
1944
+ .quick-action:nth-child(4):hover {
1945
+ background: linear-gradient(135deg, #f97316, #fb923c);
1946
+ }
1947
+
1948
+ .quick-action i {
1949
+ font-size: 18px;
1950
+ }
1951
+
1952
+ .quick-action span {
1953
+ font-size: 9px;
1954
+ font-weight: 800;
1955
+ text-transform: uppercase;
1956
+ letter-spacing: 0.4px;
1957
+ }
1958
+
1959
+ /* Tooltip for quick actions */
1960
+ .quick-action::after {
1961
+ content: attr(data-tooltip);
1962
+ position: absolute;
1963
+ right: 60px;
1964
+ top: 50%;
1965
+ transform: translateY(-50%);
1966
+ background: #1f2937;
1967
+ color: white;
1968
+ padding: 8px 12px;
1969
+ border-radius: 6px;
1970
+ font-size: 11px;
1971
+ font-weight: 600;
1972
+ white-space: nowrap;
1973
+ opacity: 0;
1974
+ pointer-events: none;
1975
+ transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1);
1976
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
1977
+ z-index: 1102;
1978
+ }
1979
+
1980
+ /* Notification Toast */
1981
+ .notification-toast {
1982
+ position: fixed;
1983
+ top: 80px;
1984
+ right: 24px;
1985
+ background: #ffffff;
1986
+ border-radius: 12px;
1987
+ box-shadow: 0 15px 30px -10px rgba(0, 0, 0, 0.2);
1988
+ padding: 20px;
1989
+ max-width: 320px;
1990
+ z-index: 2000;
1991
+ transform: translateX(120%);
1992
+ transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1);
1993
+ animation: slideInRight 250ms cubic-bezier(0.4, 0, 0.2, 1);
1994
+ border: 2px solid #e5e7eb;
1995
+ backdrop-filter: blur(8px);
1996
+ }
1997
+
1998
+ .notification-toast.show {
1999
  transform: translateX(0);
2000
  }
2001
+
2002
+ .toast-content {
2003
+ display: flex;
2004
+ align-items: flex-start;
2005
+ gap: 16px;
2006
+ }
2007
+
2008
+ .toast-content i {
2009
+ font-size: 24px;
2010
+ margin-top: 2px;
2011
+ color: #1E3A8A;
2012
+ }
2013
+
2014
+ .toast-title {
2015
+ font-weight: 800;
2016
+ color: #111827;
2017
+ margin-bottom: 4px;
2018
+ font-size: 14px;
2019
+ }
2020
+
2021
+ .toast-message {
2022
+ color: #6b7280;
2023
+ font-size: 12px;
2024
+ line-height: 1.5;
2025
+ font-weight: 500;
2026
+ }
2027
+
2028
+ .toast-close {
2029
+ position: absolute;
2030
+ top: 16px;
2031
+ right: 16px;
2032
+ background: #ffffff;
2033
+ border: 2px solid #e5e7eb;
2034
+ color: #9CA3AF;
2035
+ cursor: pointer;
2036
+ font-size: 12px;
2037
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
2038
+ width: 28px;
2039
+ height: 28px;
2040
+ border-radius: 50%;
2041
+ display: flex;
2042
+ align-items: center;
2043
+ justify-content: center;
2044
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 1px -1px rgba(0, 0, 0, 0.1);
2045
+ }
2046
+
2047
+ .toast-close:hover {
2048
+ color: #ef4444;
2049
+ border-color: #ef4444;
2050
+ transform: scale(1.08);
2051
+ }
2052
+
2053
+ /* ===== Print Styles ===== */
2054
+ @media print {
2055
+ .site-header,
2056
+ .sidebar,
2057
+ .quick-actions-bar,
2058
+ .notification-toast,
2059
+ .action-buttons,
2060
+ .section-actions,
2061
+ .field-actions,
2062
+ .field-expand-icon,
2063
+ .empty-fields-summary,
2064
+ footer {
2065
+ display: none !important;
2066
+ }
2067
+
2068
+ body {
2069
+ background: white !important;
2070
+ color: black !important;
2071
+ font-size: 12px !important;
2072
+ }
2073
+
2074
+ .case-details-summary-layout {
2075
+ height: auto !important;
2076
+ overflow: visible !important;
2077
+ background: white !important;
2078
+ display: block !important;
2079
+ }
2080
+
2081
+ .main-content {
2082
+ padding: 0 !important;
2083
+ background: white !important;
2084
+ overflow: visible !important;
2085
+ width: 100% !important;
2086
+ }
2087
+
2088
+ .case-header-banner {
2089
+ box-shadow: none !important;
2090
+ background: #f0f0f0 !important;
2091
+ border: 1px solid #ccc !important;
2092
+ margin-bottom: 20px !important;
2093
+ }
2094
+
2095
+ .case-ref-value {
2096
+ -webkit-text-fill-color: black !important;
2097
+ color: black !important;
2098
+ }
2099
+
2100
+ .details-section {
2101
+ box-shadow: none !important;
2102
+ border: 1px solid #ccc !important;
2103
+ page-break-inside: avoid;
2104
+ margin-bottom: 20px !important;
2105
+ }
2106
+
2107
+ .form-grid {
2108
+ display: block !important;
2109
+ }
2110
+
2111
+ .form-group {
2112
+ page-break-inside: avoid;
2113
+ margin-bottom: 15px !important;
2114
+ }
2115
+
2116
+ .field-container {
2117
+ border: 1px solid #ccc !important;
2118
+ box-shadow: none !important;
2119
+ margin-bottom: 10px !important;
2120
+ break-inside: avoid;
2121
+ }
2122
+
2123
+ .field-header {
2124
+ background: #f5f5f5 !important;
2125
+ padding: 10px !important;
2126
+ }
2127
+
2128
+ .field-value-container {
2129
+ display: block !important;
2130
+ padding: 15px !important;
2131
+ }
2132
+
2133
+ .field-value {
2134
+ font-size: 12px !important;
2135
+ }
2136
+
2137
+ .field-footer {
2138
+ display: none !important;
2139
+ }
2140
+
2141
+ .details-content-scrollable {
2142
+ height: auto !important;
2143
+ overflow: visible !important;
2144
+ max-height: none !important;
2145
+ padding: 15px !important;
2146
+ }
2147
+
2148
+ /* Ensure all content is visible */
2149
+ .nav-subgroups,
2150
+ .expanded .field-value-container {
2151
+ display: block !important;
2152
+ height: auto !important;
2153
+ opacity: 1 !important;
2154
+ }
2155
+
2156
+ .field-expand-icon {
2157
+ display: none !important;
2158
+ }
2159
+
2160
+ .print-mode .sidebar {
2161
+ display: none !important;
2162
+ }
2163
+ }
2164
+
2165
+ /* ===== Responsive Design ===== */
2166
+ @media (max-width: 1400px) {
2167
+ .form-grid {
2168
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
2169
+ }
2170
+
2171
+ .case-quick-info {
2172
+ grid-template-columns: repeat(2, 1fr);
2173
+ }
2174
+
2175
+ .case-ref-value {
2176
+ font-size: 24px;
2177
+ }
2178
+ }
2179
+
2180
+ @media (max-width: 1200px) {
2181
+ .sidebar {
2182
+ width: 200px;
2183
+ }
2184
+
2185
+ .header-inner,
2186
+ .main-content {
2187
+ padding: 0 16px;
2188
+ }
2189
+
2190
+ .section-header {
2191
+ padding: 16px;
2192
+ }
2193
+
2194
+ .details-content-scrollable {
2195
+ padding: 16px;
2196
+ }
2197
+ }
2198
+
2199
+ @media (max-width: 992px) {
2200
+ .case-details-summary-layout {
2201
+ flex-direction: column;
2202
+ height: auto;
2203
+ min-height: 100vh;
2204
+ overflow: auto;
2205
+ }
2206
+
2207
+ .sidebar {
2208
+ width: 100%;
2209
+ height: auto;
2210
+ max-height: 320px;
2211
+ border-right: none;
2212
+ border-bottom: 2px solid #e5e7eb;
2213
+ box-shadow: 0 6px 12px -3px rgba(0, 0, 0, 0.1), 0 3px 5px -3px rgba(0, 0, 0, 0.1);
2214
+ }
2215
+
2216
+ .case-header-content {
2217
+ flex-direction: column;
2218
+ gap: 20px;
2219
+ }
2220
+
2221
+ .case-quick-info {
2222
+ grid-template-columns: 1fr;
2223
+ width: 100%;
2224
+ }
2225
+
2226
+ .quick-actions-bar {
2227
+ position: static;
2228
+ flex-direction: row;
2229
+ justify-content: center;
2230
+ margin-top: 24px;
2231
+ gap: 8px;
2232
+ }
2233
+
2234
+ .quick-action {
2235
+ width: 44px;
2236
+ height: 44px;
2237
+ }
2238
+
2239
+ .form-grid {
2240
+ grid-template-columns: 1fr;
2241
+ }
2242
+ }
2243
+
2244
+ @media (max-width: 768px) {
2245
+ .header-inner {
2246
+ padding: 0 16px;
2247
+ flex-direction: column;
2248
+ gap: 12px;
2249
+ height: auto;
2250
+ padding: 12px 16px;
2251
+ }
2252
+
2253
+ .site-header {
2254
+ height: auto;
2255
+ }
2256
+
2257
+ .site-header.scrolled {
2258
+ height: auto;
2259
+ }
2260
+
2261
+ .action-buttons {
2262
+ flex-wrap: wrap;
2263
+ justify-content: center;
2264
+ gap: 8px;
2265
+ }
2266
+
2267
+ .case-ref-value {
2268
+ font-size: 22px;
2269
+ }
2270
+
2271
+ .case-status-badge,
2272
+ .priority-value {
2273
+ padding: 8px 12px;
2274
+ font-size: 10px;
2275
+ }
2276
+
2277
+ .section-header {
2278
+ flex-direction: column;
2279
+ gap: 16px;
2280
+ align-items: flex-start;
2281
+ }
2282
+
2283
+ .section-actions {
2284
+ align-self: flex-end;
2285
+ }
2286
+
2287
+ .empty-state {
2288
+ padding: 40px 16px;
2289
+ }
2290
+
2291
+ .empty-state h3 {
2292
+ font-size: 20px;
2293
+ }
2294
+
2295
+ .empty-state p {
2296
+ font-size: 14px;
2297
+ }
2298
+
2299
+ .empty-state-actions {
2300
+ flex-direction: column;
2301
+ width: 100%;
2302
+ gap: 12px;
2303
+ }
2304
+
2305
+ .primary-btn,
2306
+ .secondary-btn {
2307
+ width: 100%;
2308
+ justify-content: center;
2309
+ padding: 12px 20px;
2310
+ }
2311
+ }
2312
+
2313
+ @media (max-width: 480px) {
2314
+ .py-detect-title-header {
2315
+ font-size: 18px;
2316
+ letter-spacing: 1.5px;
2317
+ }
2318
+
2319
+ .logo-img-header {
2320
+ width: 32px;
2321
+ height: 32px;
2322
+ }
2323
+
2324
+ .action-btn {
2325
+ padding: 5px 10px;
2326
+ font-size: 10px;
2327
+ }
2328
+
2329
+ .back-btn {
2330
+ padding: 5px 12px;
2331
+ font-size: 10px;
2332
+ }
2333
+
2334
+ .case-ref-value {
2335
+ font-size: 20px;
2336
+ }
2337
+
2338
+ .case-tags {
2339
+ flex-direction: column;
2340
+ align-items: flex-start;
2341
+ }
2342
+
2343
+ .case-meta {
2344
+ flex-direction: column;
2345
+ align-items: flex-start;
2346
+ gap: 8px;
2347
+ }
2348
+
2349
+ .main-content {
2350
+ padding: 12px;
2351
+ }
2352
+
2353
+ .section-header,
2354
+ .details-content-scrollable {
2355
+ padding: 12px;
2356
+ }
2357
+
2358
+ .field-header {
2359
+ flex-direction: column;
2360
+ align-items: flex-start;
2361
+ gap: 8px;
2362
+ }
2363
+
2364
+ .field-meta {
2365
+ align-self: flex-end;
2366
+ }
2367
+
2368
+ .case-quick-info {
2369
+ gap: 8px;
2370
+ }
2371
+
2372
+ .quick-info-item {
2373
+ padding: 12px;
2374
+ }
2375
+
2376
+ .notification-toast {
2377
+ right: 12px;
2378
+ left: 12px;
2379
+ max-width: none;
2380
+ }
2381
+ }
2382
+
2383
+ /* Remark fields should span full width */
2384
+ .form-group.remark-field {
2385
+ grid-column: 1 / -1;
2386
  }
src/app/case-details-summary-page/case-details-summary-page.component.html CHANGED
@@ -1,87 +1,341 @@
1
- <!-- Modern UI header with logo and PyDetect title -->
2
- <div class="site-header">
3
- <div class="header-inner">
4
- <div class="logo-cluster">
5
- <span style="cursor:default;display:flex;align-items:center;">
6
- <img src="/assets/pykara-logo.png" alt="PyDetect Logo" class="logo-img-header" />
7
- </span>
8
- <div class="py-detect-title-header">
9
- <span class="py-letter p">P</span>
10
- <span class="py-letter y">Y</span>
11
- <span class="py-shape"></span>
12
- <span class="py-letter d">D</span>
13
- <span class="py-letter e">E</span>
14
- <span class="py-letter t">T</span>
15
- <span class="py-letter e2">E</span>
16
- <span class="py-letter c">C</span>
17
- <span class="py-letter t2">T</span>
18
- </div>
19
- </div>
20
- <div class="header-actions-right">
21
- <button class="back-btn animated-back" (click)="navigateBack()">
22
- <span class="back-icon">←</span> Back
23
- </button>
24
- </div>
25
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  </div>
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- <div class="case-details-summary-layout">
30
- <aside class="sidebar">
31
- <ul>
32
- <li>
33
- <button (click)="setMainHeader('crime')" [class.active]="expandedMainHeader==='crime'">Crime Details</button>
34
- <ul *ngIf="expandedMainHeader==='crime'">
35
- <ng-container *ngFor="let subgroupName of crimeSubgroupOrder">
36
- <li *ngIf="sections['crime']?.subgroups[subgroupName]" (click)="setSubheader('crime', subgroupName)" [class.active]="activeSubheader==='crime-' + subgroupName">
37
- {{ subgroupName }}
38
- </li>
39
- </ng-container>
40
- </ul>
41
- </li>
42
- <li>
43
- <button (click)="setMainHeader('suspect')" [class.active]="expandedMainHeader==='suspect'">Suspect Details</button>
44
- <ul *ngIf="expandedMainHeader==='suspect'">
45
- <ng-container *ngFor="let subgroupName of suspectSubgroupOrder">
46
- <li *ngIf="sections['suspect']?.subgroups[subgroupName]" (click)="setSubheader('suspect', subgroupName)" [class.active]="activeSubheader==='suspect-' + subgroupName">
47
- {{ subgroupName }}
48
- </li>
49
- </ng-container>
50
- </ul>
51
- </li>
52
- <li>
53
- <button (click)="setMainHeader('notes')" [class.active]="expandedMainHeader==='notes'">Documents/Evidence</button>
54
- <ul *ngIf="expandedMainHeader==='notes'">
55
- <ng-container *ngFor="let subgroupName of notesSubgroupOrder">
56
- <li *ngIf="sections['notes']?.subgroups[subgroupName]" (click)="setSubheader('notes', subgroupName)" [class.active]="activeSubheader==='notes-' + subgroupName">
57
- {{ subgroupName }}
58
- </li>
59
- </ng-container>
60
- </ul>
61
- </li>
62
- </ul>
63
- </aside>
64
- <main class="main-content">
65
- <div *ngIf="activeMainHeader && activeSubheader">
66
- <div class="details-card">
67
- <div class="details-header">
68
- <h2>{{ sections[activeMainHeader]?.title }} <span class="subheader-pill">{{ activeSubheaderName }}</span></h2>
69
- </div>
70
- <div *ngIf="sections[activeMainHeader]?.subgroups[activeSubheaderName] as fields">
71
- <div class="details-grid">
72
- <div class="details-field" [ngClass]="{'description-field': field.toLowerCase().includes('description') || field.toLowerCase().includes('notes') || field.toLowerCase().includes('findings') }" *ngFor="let field of fields">
73
- <div class="field-label">{{ field }}</div>
74
- <div class="field-value">{{ getFilledDetail(activeMainHeader, activeSubheaderName, field) }}</div>
75
- </div>
76
- </div>
77
- </div>
78
- </div>
79
- </div>
80
- </main>
81
  </div>
82
 
83
- <!-- Footer from provided design -->
84
  <footer>
85
- <p>©2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
86
  </footer>
87
- <!-- End of record-card -->
 
1
+ <!-- case-details-summary-page.component.html -->
2
+ <!-- Professional Header -->
3
+ <div class="site-header" [class.scrolled]="isScrolled">
4
+ <div class="header-inner">
5
+ <div class="logo-cluster">
6
+ <span (click)="navigateHome()" style="cursor:pointer;display:flex;align-items:center;">
7
+ <img src="/assets/pykara-logo.png" alt="PyDetect Logo" class="logo-img-header" />
8
+ </span>
9
+ <div class="py-detect-title-header">
10
+ <span class="py-letter p">P</span>
11
+ <span class="py-letter y">Y</span>
12
+ <span class="py-shape"></span>
13
+ <span class="py-letter d">D</span>
14
+ <span class="py-letter e">E</span>
15
+ <span class="py-letter t">T</span>
16
+ <span class="py-letter e2">E</span>
17
+ <span class="py-letter c">C</span>
18
+ <span class="py-letter t2">T</span>
19
+ </div>
20
+ </div>
21
+
22
+ <div class="header-actions-right">
23
+ <div class="action-buttons">
24
+ <button class="action-btn print-btn" (click)="printSummary()" title="Print Summary">
25
+ <i class="fas fa-print"></i>
26
+ <span class="btn-label">Print</span>
27
+ </button>
28
+ <button class="action-btn export-btn" (click)="exportAsPDF()" title="Export as PDF">
29
+ <i class="fas fa-file-pdf"></i>
30
+ <span class="btn-label">PDF</span>
31
+ </button>
32
+ <button class="back-btn" (click)="navigateBack()">
33
+ <i class="fas fa-arrow-left"></i>
34
+ <span>Back</span>
35
+ </button>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+
41
+ <!-- Loading State -->
42
+ <div *ngIf="loading" class="loading-overlay" @fadeInOut>
43
+ <div class="loading-spinner">
44
+ <div class="spinner-ring"></div>
45
+ <i class="fas fa-search"></i>
46
+ <p>Loading case details...</p>
47
+ </div>
48
  </div>
49
 
50
+ <!-- Main Content -->
51
+ <div class="case-details-summary-layout" [class.print-mode]="printMode">
52
+
53
+ <!-- Left Navigation Sidebar -->
54
+ <aside class="sidebar" *ngIf="!printMode" @slideInLeft>
55
+ <nav class="sidebar-nav">
56
+ <div class="nav-section" *ngFor="let section of sidebarSections; let i = index"
57
+ [class.expanded]="expandedMainHeader === section.key">
58
+ <div class="nav-section-header" (click)="setMainHeader(section.key)">
59
+ <div class="nav-icon-wrapper">
60
+ <i [class]="section.icon"></i>
61
+ </div>
62
+ <span>{{section.title}}</span>
63
+ <i class="fas fa-chevron-right nav-chevron"
64
+ [class.rotated]="expandedMainHeader === section.key"></i>
65
+ </div>
66
+ <div class="nav-subgroups" *ngIf="expandedMainHeader === section.key" @expandAnimation>
67
+ <div *ngFor="let subgroup of getSubgroupsForSection(section.key); let j = index"
68
+ class="nav-subgroup-item"
69
+ [class.active]="activeSubheader === section.key + '-' + subgroup.name"
70
+ (click)="setSubheader(section.key, subgroup.name)"
71
+ @staggerItem>
72
+ <div class="subgroup-icon-wrapper">
73
+ <i [class]="subgroup.icon"></i>
74
+ </div>
75
+ <span>{{ subgroup.name }}</span>
76
+ <div class="subgroup-indicator"></div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </nav>
81
+
82
+ <div class="sidebar-footer" @fadeInUp>
83
+ <div class="case-summary" *ngIf="selectedCase">
84
+ <h4><i class="fas fa-chart-pie"></i> Case Summary</h4>
85
+ <div class="summary-item" @fadeInUp *ngIf="selectedCase.status">
86
+ <span class="summary-label">Status:</span>
87
+ <span class="summary-value" [class]="getCaseStatusClass()">
88
+ {{ selectedCase.status || 'Not Assigned' }}
89
+ </span>
90
+ </div>
91
+ <div class="summary-item" @fadeInUp *ngIf="selectedCase.casePriority">
92
+ <span class="summary-label">Priority:</span>
93
+ <span class="summary-value priority-{{ selectedCase.casePriority.toLowerCase() }}">
94
+ {{ selectedCase.casePriority || 'Not Set' }}
95
+ </span>
96
+ </div>
97
+ <div class="summary-item" @fadeInUp>
98
+ <span class="summary-label">Last Updated:</span>
99
+ <span class="summary-value">
100
+ {{ getFilledDetail('crime', 'Identification & Timing', 'Date & Time (Entry)') || 'N/A' }}
101
+ </span>
102
+ </div>
103
+ <div class="summary-progress" *ngIf="getProgressPercentage() > 0">
104
+ <div class="progress-label">
105
+ <span>Case Progress</span>
106
+ <span>{{ getProgressPercentage() }}%</span>
107
+ </div>
108
+ <div class="progress-bar">
109
+ <div class="progress-fill" [style.width.%]="getProgressPercentage()"></div>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </aside>
115
+
116
+ <!-- Main Content Area -->
117
+ <main class="main-content">
118
+ <!-- Case Header Banner -->
119
+ <div class="case-header-banner" @slideInDown>
120
+ <div class="case-header-content">
121
+ <div class="case-identity">
122
+ <div class="case-ref">
123
+ <div class="case-ref-top">
124
+ <h1 class="case-ref-label">CASE REFERENCE</h1>
125
+ <h1 class="case-ref-value">{{ selectedCase?.caseId || 'Not Assigned' }}</h1>
126
+
127
+ <div class="case-actions">
128
+ <button class="case-action-btn" (click)="toggleFavorite()" [class.favorited]="isFavorited">
129
+ <i [ngClass]="isFavorited ? 'fas fa-star' : 'far fa-star'"></i>
130
+ </button>
131
+ <button class="case-action-btn" (click)="addReminder()">
132
+ <i class="fas fa-bell"></i>
133
+ </button>
134
+ </div>
135
+ </div>
136
+ <div class="case-tags">
137
+ <span class="case-tag" *ngIf="selectedCase?.crime">
138
+ <i class="fas fa-gavel"></i>
139
+ {{ selectedCase?.crime }}
140
+ </span>
141
+ <span class="case-tag">
142
+ <i class="fas fa-calendar"></i>
143
+ {{ getFilledDetail('crime', 'Identification & Timing', 'Date & Time (Entry)') | date:'mediumDate' }}
144
+ </span>
145
+
146
+ <div class="case-meta">
147
+ <div class="case-status-badge" [class]="getCaseStatusClass()" @pulseAnimation>
148
+ <span class="status-dot"></span>
149
+ {{ (selectedCase?.status || 'Not Assigned') | uppercase }}
150
+ </div>
151
+ <div class="case-priority" *ngIf="selectedCase?.casePriority">
152
+ <span class="priority-label">Priority:</span>
153
+ <span class="priority-value priority-{{ selectedCase?.casePriority?.toLowerCase() }}">
154
+ <i class="fas fa-flag"></i>
155
+ {{ selectedCase?.casePriority | uppercase }}
156
+ </span>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ </div>
161
+ </div>
162
+
163
+ <div class="case-quick-info">
164
+ <div class="quick-info-item" *ngFor="let info of quickInfoItems" @slideInUp>
165
+ <div class="info-icon-wrapper">
166
+ <i [class]="info.icon"></i>
167
+ </div>
168
+ <div class="info-content">
169
+ <span class="info-label">{{ info.label }}</span>
170
+ <span class="info-value">{{ info.value || 'Not Specified' }}</span>
171
+ </div>
172
+ <div class="info-hover-effect"></div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </div>
177
+
178
+ <!-- Details Container -->
179
+ <div class="details-container">
180
+ <!-- Details Section -->
181
+ <div *ngIf="activeMainHeader && activeSubheader" class="details-section" @fadeIn>
182
+ <!-- Section Header -->
183
+ <div class="section-header">
184
+ <div class="section-title">
185
+ <div class="section-icon-wrapper">
186
+ <i [class]="sections[activeMainHeader]?.icon"></i>
187
+ </div>
188
+ <div>
189
+ <h2>{{ sections[activeMainHeader]?.title | uppercase }}</h2>
190
+ <p class="section-subtitle">{{ activeSubheaderName | uppercase }}</p>
191
+ </div>
192
+ </div>
193
+ <div class="section-actions">
194
+ <button class="section-action-btn" (click)="copySection()" title="Copy Section">
195
+ <i class="fas fa-copy"></i>
196
+ </button>
197
+ <button class="section-action-btn" (click)="printSummary()" title="Print">
198
+ <i class="fas fa-print"></i>
199
+ </button>
200
+ <button class="section-action-btn" (click)="exportAsPDF()" title="Export PDF">
201
+ <i class="fas fa-file-pdf"></i>
202
+ </button>
203
+ <button class="section-action-btn expand-all" (click)="toggleExpandAll()" title="Expand All">
204
+ <i class="fas" [class.fa-expand]="!allExpanded" [class.fa-compress]="allExpanded"></i>
205
+ </button>
206
+ </div>
207
+ </div>
208
+
209
+ <!-- Details Content -->
210
+ <div class="details-content-scrollable">
211
+ <div *ngIf="sections[activeMainHeader]?.subgroups[activeSubheaderName] as fields">
212
+ <div class="form-grid">
213
+ <div class="form-group"
214
+ *ngFor="let field of fields; let i = index"
215
+ [class.remark-field]="field === 'Remark'"
216
+ [ngClass]="{
217
+ 'description-field': isDescriptionField(field),
218
+ 'important-field': isImportantField(field),
219
+ 'expanded': allExpanded || expandedFields.has(field)
220
+ }"
221
+ @staggerItem>
222
+ <div class="field-container" (click)="toggleFieldExpand(field)">
223
+ <div class="field-header">
224
+ <div class="field-label">
225
+ <div class="field-icon-wrapper">
226
+ <i [class]="getFieldIcon(field)"></i>
227
+ </div>
228
+ <span class="field-label-text">{{ field | uppercase }}</span>
229
+ </div>
230
+ <div class="field-meta">
231
+ <span class="field-type">
232
+ {{ getFieldType(field) | uppercase }}
233
+ </span>
234
+ <i class="fas fa-chevron-down field-expand-icon"
235
+ [class.rotated]="allExpanded || expandedFields.has(field)"></i>
236
+ </div>
237
+ </div>
238
+ <div class="field-value-container">
239
+ <div class="field-value"
240
+ [class.empty-field]="getFilledDetail(activeMainHeader, activeSubheaderName, field) === '— Not Provided'">
241
+ <ng-container *ngIf="getFilledDetail(activeMainHeader, activeSubheaderName, field) !== '— Not Provided'; else notProvided">
242
+ <span class="value-text">{{ getFilledDetail(activeMainHeader, activeSubheaderName, field) }}</span>
243
+ </ng-container>
244
+ <ng-template #notProvided>
245
+ <span class="not-provided">
246
+ <i class="fas fa-minus-circle"></i>
247
+ NOT PROVIDED
248
+ </span>
249
+ </ng-template>
250
+ </div>
251
+ </div>
252
+ <div class="field-footer">
253
+ <span class="field-updated">Last updated: {{ getFieldUpdateTime(field) }}</span>
254
+ </div>
255
+ </div>
256
+ </div>
257
+ </div>
258
+ </div>
259
+
260
+ <!-- Empty Fields Summary -->
261
+ <div class="empty-fields-summary" *ngIf="hasEmptyFields()" @fadeIn>
262
+ <div class="summary-header">
263
+ <i class="fas fa-exclamation-triangle"></i>
264
+ <h3>Missing Information</h3>
265
+ </div>
266
+ <p>{{ getEmptyFieldsCount() }} fields in this section require attention.</p>
267
+ <button class="action-btn fill-empty-btn" (click)="highlightEmptyFields()">
268
+ <i class="fas fa-highlighter"></i>
269
+ Highlight Missing Fields
270
+ </button>
271
+ </div>
272
+ </div>
273
+ </div>
274
+
275
+ <!-- Empty State -->
276
+ <div *ngIf="!activeMainHeader || !activeSubheader" class="empty-state" @fadeIn>
277
+ <div class="empty-state-icon">
278
+ <i class="fas fa-file-search"></i>
279
+ <div class="pulse-ring"></div>
280
+ </div>
281
+ <h3>SELECT A SECTION TO VIEW DETAILS</h3>
282
+ <p>Choose a category from the sidebar to display the corresponding case information.</p>
283
+ <div class="empty-state-actions">
284
+ <button class="primary-btn" (click)="setMainHeader('crime')">
285
+ <i class="fas fa-gavel"></i>
286
+ START WITH CRIME DETAILS
287
+ </button>
288
+ <button class="secondary-btn" (click)="expandAllSections()">
289
+ <i class="fas fa-expand"></i>
290
+ EXPAND ALL SECTIONS
291
+ </button>
292
+ </div>
293
+ <div class="empty-state-tips">
294
+ <div class="tip">
295
+ <i class="fas fa-lightbulb"></i>
296
+ <span>Tip: Use keyboard shortcuts for faster navigation</span>
297
+ </div>
298
+ </div>
299
+ </div>
300
+
301
+ <!-- Quick Actions Bar -->
302
+ <div class="quick-actions-bar" *ngIf="activeMainHeader && activeSubheader" @slideInUp>
303
+ <div class="quick-action" (click)="scrollToTop()" data-tooltip="Scroll to Top">
304
+ <i class="fas fa-arrow-up"></i>
305
+ <span>Top</span>
306
+ </div>
307
+ <div class="quick-action" (click)="exportSection()" data-tooltip="Export Section">
308
+ <i class="fas fa-download"></i>
309
+ <span>Export</span>
310
+ </div>
311
+ <div class="quick-action" (click)="shareSection()" data-tooltip="Share Section">
312
+ <i class="fas fa-share-alt"></i>
313
+ <span>Share</span>
314
+ </div>
315
+ <div class="quick-action" (click)="addNote()" data-tooltip="Add Note">
316
+ <i class="fas fa-plus"></i>
317
+ <span>Add Note</span>
318
+ </div>
319
+ </div>
320
+ </div>
321
+ </main>
322
 
323
+ <!-- Notification Toast -->
324
+ <div class="notification-toast" [class.show]="showNotification" @slideInRight>
325
+ <div class="toast-content">
326
+ <i class="fas" [class]="notificationIcon"></i>
327
+ <div>
328
+ <p class="toast-title">{{ notificationTitle }}</p>
329
+ <p class="toast-message">{{ notificationMessage }}</p>
330
+ </div>
331
+ </div>
332
+ <button class="toast-close" (click)="hideNotification()">
333
+ <i class="fas fa-times"></i>
334
+ </button>
335
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  </div>
337
 
338
+ <!-- Footer from record page -->
339
  <footer>
340
+ <p>©2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
341
  </footer>
 
src/app/case-details-summary-page/case-details-summary-page.component.ts CHANGED
@@ -1,446 +1,1016 @@
1
- import { InfopageComponent } from '../infopage/infopage.component';
2
- import { Component, OnInit } from '@angular/core';
3
  import { ActivatedRoute, Router } from '@angular/router';
4
  import { CaseStoreService, PoliceCase } from '../shared/case-store.service';
 
 
 
 
 
 
 
 
 
 
5
 
6
  @Component({
7
- selector: 'app-case-details-summary-page',
8
- templateUrl: './case-details-summary-page.component.html',
9
- styleUrls: ['./case-details-summary-page.component.css']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  })
11
- export class CaseDetailsSummaryPageComponent implements OnInit {
12
- caseId: string | null = null;
13
- activeSection: string = 'crime';
14
- activeSubgroup: string = '';
15
- // Added ordered list for crime subgroups to ensure sidebar shows in desired order
16
- crimeSubgroupOrder: string[] = [
17
- 'Identification & Timing',
18
- 'Location & People',
19
- 'Offence & Context',
20
- 'Evidence & Scene',
21
- 'Operational Notes',
22
- 'Status & Linkage',
23
- 'Remark'
24
- ];
25
- // Added ordered list for suspect subgroups
26
- suspectSubgroupOrder: string[] = [
27
- 'Identity',
28
- 'Physical Description',
29
- 'Background',
30
- 'Known Associates',
31
- 'Prior Records',
32
- 'Remark'
33
- ];
34
-
35
- // Added ordered list for notes/evidence subgroups
36
- notesSubgroupOrder: string[] = [
37
- 'Investigation Notes',
38
- 'Evidence Files',
39
- 'Links and Recommendation',
40
- 'Remark'
41
- ];
42
- sections: any = {
43
- crime: {
44
- title: 'Crime Details',
45
- subgroups: {
46
- 'Identification & Timing': ['Case ID', 'FIR / Ref #', 'Crime Type', 'Case Category', 'Date & Time (Entry)', 'Occurred From', 'Occurred To', 'Time Reported', 'Time Discovered', 'Country', 'State', 'District', 'Number of Victims', 'Brief Description'],
47
- 'Location & People': ['Location', 'Jurisdiction / PS', 'Scene Type', 'Reported By', 'Reported Contact', 'Witness Count', 'Victim Name', 'Victim Contact', 'Victim Summary', 'Suspected Offender Known?', 'Suspect Link'],
48
- 'Offence & Context': ['Legal Sections / Charges', 'Offence Category', 'Offence Description', 'Suspected Motive', 'Confirmed Motive', 'Weapon Involved', 'Property Loss / Damage'],
49
- 'Evidence & Scene': ['Evidence Collected', 'Physical Evidence', 'Evidence Storage Reference', 'Photos / Video?', 'CCTV Present?', 'CCTV Sources / IDs', 'Forensic Tests Required', 'Chain of Custody?', 'Scene Condition', 'Digital Evidence'],
50
- 'Operational Notes': ['Investigating Officer', 'Duty Person', 'Supervising Officer', 'Patrol Notes', 'Arrest Made', 'Arrest Location', 'Initial Actions Taken', 'riskLevel', 'Confidentiality'],
51
- 'Status & Linkage': ['Biometric / Forensic IDs', 'DNA Ref ID', 'Fingerprint ID', 'Case Status', 'Linked Cases', 'arrestCount', 'Case Priority', 'Follow-up Date', 'Court Case ID', 'Next Hearing Date', 'Final Summary'],
52
- 'Remark': ['Remark']
53
- }
54
- },
55
- suspect: {
56
- title: 'Suspect Details',
57
- subgroups: {
58
- 'Identity': ['Suspect ID', 'Suspect Name', 'Alias / Nickname', 'Age', 'Gender', 'Nationality', 'Nationality ID / Passport Number', 'Languages', 'Address', 'Known Aliases', 'Government ID'],
59
- 'Physical Description': ['Height (cm)', 'Weight (kg)', 'Tattoo Details', 'Hair Color', 'Scar Details', 'Distinguishing Marks', 'Build', 'Eye Color', 'Photo Upload'],
60
- 'Background': ['Employment', 'Education', 'Occupation', 'Company', 'Workplace Address', 'Marital Status', 'Known Habits', 'Known Financial Details'],
61
- 'Known Associates': ['Associate Names', 'Gang Affiliation', 'Family Connections', 'Social Media Handles'],
62
- 'Prior Records': ['Criminal History', 'Prior Arrests', 'Probation/Parole Status'],
63
- 'Remark': ['Remark']
64
- }
65
- },
66
- notes: {
67
- title: 'Evidence and Documents',
68
- subgroups: {
69
- 'Investigation Notes': ['Initial Findings', 'Detailed Notes', 'Status', 'Version History / Updates'],
70
- 'Evidence Files': ['Evidence Photos', 'Evidence Videos', 'Evidence Documents'],
71
- 'Links and Recommendation': ['Links to Evidence', 'Final Recommendations'],
72
- 'Remark': ['Remark']
73
- }
74
- }
75
- };
76
- mainTitles: string[] = ['Crime Details', 'Suspect Details', 'Evidence and Documents'];
77
- mainKeys: string[] = ['crime', 'suspect', 'notes'];
78
- subgroups: string[] = [];
79
- activeTab: string = 'Crime Details';
80
- tabs: string[] = ['Crime Details', 'Suspect Details', 'Evidence and Documents']; // Added tabs property
81
- slideOpen: boolean = true; // Added slideOpen property
82
- activeMainHeader: string = 'crime';
83
- activeSubheader: string = '';
84
- activeSubheaderName: string = '';
85
- expandedMainHeader: string | null = 'crime'; // Track which main header is expanded
86
- selectedCase: PoliceCase | null = null;
87
- fromPage: string = 'record'; // default
88
- returnId: string | null = null;
89
-
90
- // date sets for formatting
91
- private dateTimeFields = new Set<string>(['Date & Time (Entry)', 'Occurred From', 'Occurred To', 'Time Reported', 'Time Discovered']);
92
- private dateFields = new Set<string>(['Follow-up Date', 'Next Hearing Date']);
93
-
94
- constructor(private route: ActivatedRoute, private router: Router, private caseStore: CaseStoreService) {
95
- // Read route params
96
- this.route.paramMap.subscribe(params => {
97
- this.caseId = params.get('id');
98
- });
99
-
100
- // Read query params (preferable for reliable behavior)
101
- this.route.queryParams.subscribe(params => {
102
- if (params['from']) {
103
- this.fromPage = params['from'];
104
- }
105
- if (params['returnId']) {
106
- this.returnId = params['returnId'];
107
- }
108
- });
109
-
110
- // Also read navigation state (router state or history.state) as a fallback
111
- const nav = this.router.getCurrentNavigation();
112
- if (nav && nav.extras && (nav.extras.state as any)) {
113
- const st = nav.extras.state as any;
114
- if (st.from) this.fromPage = st.from;
115
- if (st.returnId) this.returnId = st.returnId;
116
- if (st.case) this.selectedCase = st.case as PoliceCase;
117
- if (st.caseId) this.returnId = this.returnId || st.caseId;
118
- }
119
-
120
- // Fallback to history.state for cases where getCurrentNavigation() is unavailable (e.g., after reload)
121
- const hist = (window && (window as any).history && (window as any).history.state) || {};
122
- if (hist.from) this.fromPage = hist.from;
123
- if (hist.returnId) this.returnId = this.returnId || hist.returnId;
124
- if (hist.caseId) this.returnId = this.returnId || hist.caseId;
125
- if (hist.case && !this.selectedCase) this.selectedCase = hist.case as PoliceCase;
126
-
127
- // Debug log to help trace where navigation came from
128
- console.log('[CaseDetailsSummary] init sources:', {
129
- urlParamId: this.caseId,
130
- queryFrom: (this.route.snapshot.queryParamMap.get('from')),
131
- queryReturnId: (this.route.snapshot.queryParamMap.get('returnId')),
132
- navState: nav?.extras?.state,
133
- historyState: hist,
134
- resolvedFrom: this.fromPage,
135
- resolvedReturnId: this.returnId
136
- });
137
- }
138
-
139
- ngOnInit() {
140
- this.setSection('crime');
141
- // Ensure the main header and first subgroup are selected so main content shows by default
142
- const crimeSubgroups = Object.keys(this.sections['crime']?.subgroups || {});
143
- const first = crimeSubgroups[0] || '';
144
- this.activeMainHeader = 'crime';
145
- this.expandedMainHeader = 'crime';
146
- this.activeSubheader = 'crime-' + first;
147
- this.activeSubheaderName = first;
148
-
149
- // Load the selected case if not already provided in navigation state
150
- if (!this.selectedCase) {
151
- // Prefer caseId from route param or returnId
152
- const id = this.caseId || this.returnId || null;
153
- if (id) {
154
- const found = this.caseStore.getPoliceCases().find(c => c.caseId === id || String(c.caseId).toLowerCase() === id.toLowerCase());
155
- if (found) this.selectedCase = found;
156
- }
157
- }
158
- }
159
-
160
- setSection(section: string) {
161
- this.activeSection = section;
162
- this.subgroups = Object.keys(this.sections[section].subgroups);
163
- this.activeSubgroup = this.subgroups[0];
164
- this.activeTab = this.sections[section].title;
165
- }
166
-
167
- setSubgroup(subgroup: string) {
168
- this.activeSubgroup = subgroup;
169
- }
170
-
171
- setTab(tab: string) {
172
- // Find section key by tab title
173
- const sectionKey = this.mainKeys[this.mainTitles.indexOf(tab)];
174
- if (sectionKey) {
175
- this.activeTab = tab;
176
- this.setSection(sectionKey);
177
- }
178
- }
179
-
180
- setMainHeader(header: string) {
181
- if (this.expandedMainHeader === header) {
182
- // Collapse if already expanded
183
- this.expandedMainHeader = null;
184
- } else {
185
- // Expand and collapse others
186
- this.expandedMainHeader = header;
187
- this.activeMainHeader = header;
188
- // Set first subgroup as default
189
- const subgroups = Object.keys(this.sections[header]?.subgroups || {});
190
- this.activeSubheader = header + '-' + (subgroups[0] || '');
191
- this.activeSubheaderName = subgroups[0] || '';
192
- }
193
- }
194
-
195
- setSubheader(mainHeader: string, subgroup: string) {
196
- this.activeSubheader = mainHeader + '-' + subgroup;
197
- this.activeSubheaderName = subgroup;
198
- }
199
-
200
- getFilledDetail(mainHeader: string, subgroup: string, field: string): string {
201
- // If we have a selectedCase, map the field to the case object first
202
- if (this.selectedCase) {
203
- const v = this.getFieldValueFromCase(this.selectedCase, field);
204
- if (v !== null && v !== undefined && v !== '') {
205
- // format dates if needed
206
- if (this.dateFields && this.dateFields.has(field)) {
207
- const d = new Date(v);
208
- if (!isNaN(d.getTime())) return d.toISOString().slice(0,10);
209
- }
210
- if (this.dateTimeFields && this.dateTimeFields.has(field)) {
211
- const d = new Date(v);
212
- if (!isNaN(d.getTime())) return d.toLocaleString();
213
- }
214
- return String(v);
215
- }
216
- }
217
-
218
- // Load saved form data from localStorage as fallback (legacy behavior)
219
- const savedData = localStorage.getItem('pydetect-form-data');
220
- if (savedData) {
221
- const data = JSON.parse(savedData);
222
- if (data.formData && data.formData[field] !== undefined && data.formData[field] !== null) {
223
- return data.formData[field];
224
- }
225
- }
226
- return '';
227
- }
228
-
229
- // Map field labels to properties on PoliceCase (copied/adapted from recordpage)
230
- private getFieldValueFromCase(sc: any, field: string): any {
231
- const fieldMap: { [key: string]: string | string[] } = {
232
- 'Case ID': 'caseId',
233
- 'FIR / Ref #': 'firRef',
234
- 'Crime Type': 'crime',
235
- 'Case Category': 'caseCategory',
236
- 'Date & Time (Entry)': 'dateTime',
237
- 'Occurred From': 'occurredFrom',
238
- 'Occurred To': 'occurredTo',
239
- 'Time Reported': 'timeReported',
240
- 'Time Discovered': 'timeDiscovered',
241
- 'Country': 'country',
242
- 'State': 'state',
243
- 'District': 'district',
244
- 'Number of Victims': 'numberOfVictims',
245
- 'Brief Description': 'briefDescription',
246
- 'Location': ['police', 'address'],
247
- 'Jurisdiction / PS': 'jurisdiction',
248
- 'Scene Type': 'sceneType',
249
- 'Reported By': 'reportedBy',
250
- 'Reported Contact': 'reportedContact',
251
- 'Witness Count': 'witnessCount',
252
- 'Victim Name': 'victimName',
253
- 'Victim Contact': 'victimContact',
254
- 'Victim Summary': 'victimSummary',
255
- 'Suspected Offender Known?': 'suspectedOffenderKnown',
256
- 'Suspect Link': 'suspectLink',
257
- 'Legal Sections / Charges': 'legalSections',
258
- 'Offence Category': 'offenceCategory',
259
- 'Offence Description': 'offenceDescription',
260
- 'Suspected Motive': 'suspectedMotive',
261
- 'Confirmed Motive': 'confirmedMotive',
262
- 'Weapon Involved': 'weaponInvolved',
263
- 'Property Loss / Damage': 'propertyLoss',
264
- 'Evidence Collected': 'evidenceCollected',
265
- 'Forensic Tests Required': 'forensicTestsRequired',
266
- 'Scene Condition': 'sceneCondition',
267
- 'Photos / Video?': 'photosVideo',
268
- 'CCTV Present?': 'cctvPresent',
269
- 'CCTV Sources / IDs': 'cctvSources',
270
- 'Physical Evidence (list)': 'physicalEvidence',
271
- 'Chain of Custody?': 'chainOfCustody',
272
- 'Digital Evidence': 'digitalEvidence',
273
- 'Evidence Storage Reference': 'evidenceStorageReference',
274
- 'Investigating Officer': ['police', 'name'],
275
- 'Duty Person': ['police', 'dutyPerson'],
276
- 'Supervising Officer': ['police', 'supervisingOfficer'],
277
- 'Patrol Notes': ['police', 'patrolNotes'],
278
- 'Arrest Made': 'arrestMade',
279
- 'Arrest Location': 'arrestLocation',
280
- 'Initial Actions Taken': 'initialActionsTaken',
281
- 'riskLevel': 'riskLevel',
282
- 'Confidentiality': 'confidentiality',
283
- 'Biometric / Forensic IDs': 'biometricIds',
284
- 'DNA Ref ID': 'dnaRefId',
285
- 'Fingerprint ID': 'fingerprintId',
286
- 'Case Status': 'status',
287
- 'Linked Cases': 'linkedCases',
288
- 'arrestCount': 'arrestCount',
289
- 'Case Priority': 'casePriority',
290
- 'Follow-up Date': 'followUpDate',
291
- 'Court Case ID': 'courtCaseId',
292
- 'Next Hearing Date': 'nextHearingDate',
293
- 'Final Summary': 'finalSummary',
294
- 'Remark': 'remark',
295
- // Suspect Details
296
- 'Suspect ID': ['accused', 'suspectId'],
297
- 'Suspect Name': ['accused', 'name'],
298
- 'Alias / Nickname': ['accused', 'alias'],
299
- 'Age': ['accused', 'age'],
300
- 'Gender': ['accused', 'gender'],
301
- 'Nationality': ['accused', 'nationality'],
302
- 'Nationality ID / Passport Number': ['accused', 'passportNumber'],
303
- 'Languages': ['accused', 'languages'],
304
- 'Address': ['accused', 'address'],
305
- 'Known Aliases': ['accused', 'knownAliases'],
306
- 'Government ID': ['accused', 'governmentId'],
307
- 'Height (cm)': ['accused', 'height'],
308
- 'Weight (kg)': ['accused', 'weight'],
309
- 'Build': ['accused', 'build'],
310
- 'Hair Color': ['accused', 'hairColor'],
311
- 'Eye Color': ['accused', 'eyeColor'],
312
- 'Distinguishing Marks': ['accused', 'distinguishingMarks'],
313
- 'Tattoo Details': ['accused', 'tattooDetails'],
314
- 'Scar Details': ['accused', 'scarDetails'],
315
- 'Photo Upload': ['accused', 'photoUpload'],
316
- 'Employment': ['accused', 'employment'],
317
- 'Education': ['accused', 'education'],
318
- 'Occupation': ['accused', 'occupation'],
319
- 'Company': ['accused', 'company'],
320
- 'Workplace Address': ['accused', 'workplaceAddress'],
321
- 'Marital Status': ['accused', 'maritalStatus'],
322
- 'Known Habits': ['accused', 'knownHabits'],
323
- 'Known Financial Details': ['accused', 'knownFinancialDetails'],
324
- 'Associate Names': ['accused', 'associateNames'],
325
- 'Gang Affiliation': ['accused', 'gangAffiliation'],
326
- 'Family Connections': ['accused', 'familyConnections'],
327
- 'Social Media Handles': ['accused', 'socialMediaHandles'],
328
- 'Criminal History': ['accused', 'criminalHistory'],
329
- 'Prior Arrests': ['accused', 'priorArrests'],
330
- 'Probation/Parole Status': ['accused', 'probationStatus'],
331
- // Notes/Evidence
332
- 'Initial Findings': ['police', 'information'],
333
- 'Detailed Notes': ['notes', 'detailedNotes'],
334
- 'Status': 'status',
335
- 'Version History / Updates': ['notes', 'versionHistory'],
336
- 'Evidence Photos': ['legal', 'evidencePhotos'],
337
- 'Evidence Videos': ['legal', 'evidenceVideos'],
338
- 'Evidence Documents': ['legal', 'evidenceDocuments'],
339
- 'Links to Evidence': ['legal', 'linksToEvidence'],
340
- 'Final Recommendations': ['legal', 'finalRecommendations'],
341
- 'Witness Statements': ['legal', 'witnessStatements'],
342
- 'Confessions': ['legal', 'confessions'],
343
- // Audit Fields
344
- 'Created By': 'createdBy',
345
- 'Creation Date': 'creationDate',
346
- 'Last Updated': 'lastUpdated',
347
- 'Verified By': 'verifiedBy'
348
- };
349
-
350
- const path = fieldMap[field] || field;
351
- let value: any = undefined;
352
- if (Array.isArray(path)) {
353
- let v: any = sc as any;
354
- for (const p of path) {
355
- if (v && v[p] !== undefined) v = v[p];
356
- else { v = undefined; break; }
357
- }
358
- value = v;
359
- } else {
360
- value = (sc as any) && (sc as any)[path] !== undefined ? (sc as any)[path] : undefined;
361
- }
362
-
363
- // If not found on mapped path, try raw formData saved with the case
364
- if (value === null || value === undefined || value === '') {
365
- try {
366
- const fd = this.getFormDataArray(sc as any);
367
- const norm = (s: any) => {
368
- if (s === null || s === undefined) return '';
369
- let t = String(s).toLowerCase();
370
- t = t.replace(/&/g, '');
371
- t = t.replace(/and/g, '');
372
- t = t.replace(/entry/g, '');
373
- t = t.replace(/\s+/g, '');
374
- return t.replace(/[^a-z0-9]/g, '');
375
- };
376
-
377
- if (fd && fd.length) {
378
- let kv = fd.find(k => k && String(k.key).toLowerCase() === String(field).toLowerCase());
379
- if (kv) value = kv.value;
380
- if (value === null || value === undefined || value === '') {
381
- const fieldNorm = norm(field);
382
- const pathName = Array.isArray(path) ? path[path.length -1] : String(path);
383
- const pathNorm = norm(pathName);
384
- kv = fd.find(k => k && (norm(k.key) === fieldNorm || norm(k.key) === pathNorm || norm(k.key).includes(fieldNorm) || fieldNorm.includes(norm(k.key))));
385
- if (kv) value = kv.value;
386
- }
387
- }
388
-
389
- if ((value === null || value === undefined || value === '') && (sc as any) && (sc as any).formData && typeof (sc as any).formData === 'object') {
390
- if ((sc as any).formData[field] !== undefined) value = (sc as any).formData[field];
391
- else {
392
- const fieldNorm = (s: any) => s === null || s === undefined ? '' : String(s).toLowerCase().replace(/[^a-z0-9]/g, '');
393
- const target = fieldNorm(field);
394
- for (const k of Object.keys((sc as any).formData)) {
395
- if (fieldNorm(k) === target || k.toLowerCase() === field.toLowerCase() || k.toLowerCase().includes(field.toLowerCase())) {
396
- value = (sc as any).formData[k];
397
- break;
398
- }
399
- }
400
- }
401
- }
402
- } catch (e) {
403
- // ignore
404
- }
405
- }
406
-
407
- return value;
408
- }
409
-
410
- // Helpers reused from other components
411
- private isFormDataArray(fd: any): boolean {
412
- return Array.isArray(fd) && fd.length >0 && fd.every((item: any) => item && Object.prototype.hasOwnProperty.call(item, 'key'));
413
- }
414
-
415
- private getFormDataArray(caseObj: any): Array<{ key: string; value: any }> {
416
- if (!caseObj || !(caseObj as any).formData) return [];
417
- const fd = (caseObj as any).formData;
418
- if (this.isFormDataArray(fd)) return fd as Array<{ key: string; value: any }>;
419
- if (typeof fd === 'object') return Object.keys(fd).map(k => ({ key: k, value: fd[k] }));
420
- return [{ key: 'value', value: fd }];
421
- }
422
-
423
- // New helper: safely format display value for formData
424
- formatFormValue(value: any): string {
425
- if (value === null || value === undefined || value === '') return '';
426
- if (typeof value === 'object') {
427
- try { return JSON.stringify(value, null,2); } catch { return String(value); }
428
- }
429
- return String(value);
430
- }
431
-
432
- navigateBack() {
433
- // Prefer explicit fromPage values set via queryParam or navigation state
434
- if (this.fromPage === 'case-details') {
435
- // If returnId is available, pass it back in navigation state for the target to use
436
- if (this.returnId) {
437
- this.router.navigate(['/case-details'], { state: { caseId: this.returnId } });
438
- } else {
439
- this.router.navigate(['/case-details']);
440
- }
441
- } else {
442
- // Default/record
443
- this.router.navigate(['/record']);
444
- }
445
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
  }
 
1
+ import { Component, OnInit, OnDestroy, HostListener, ElementRef, ViewChild } from '@angular/core';
 
2
  import { ActivatedRoute, Router } from '@angular/router';
3
  import { CaseStoreService, PoliceCase } from '../shared/case-store.service';
4
+ import { Subscription } from 'rxjs';
5
+ import {
6
+ trigger,
7
+ transition,
8
+ style,
9
+ animate,
10
+ query,
11
+ stagger,
12
+ keyframes
13
+ } from '@angular/animations';
14
 
15
  @Component({
16
+ selector: 'app-case-details-summary-page',
17
+ templateUrl: './case-details-summary-page.component.html',
18
+ styleUrls: ['./case-details-summary-page.component.css'],
19
+ animations: [
20
+ trigger('fadeInOut', [
21
+ transition(':enter', [
22
+ style({ opacity: 0 }),
23
+ animate('300ms ease-in', style({ opacity: 1 }))
24
+ ]),
25
+ transition(':leave', [
26
+ animate('300ms ease-out', style({ opacity: 0 }))
27
+ ])
28
+ ]),
29
+ trigger('slideInUp', [
30
+ transition(':enter', [
31
+ style({ opacity: 0, transform: 'translateY(20px)' }),
32
+ animate('400ms cubic-bezier(0.4, 0, 0.2, 1)',
33
+ style({ opacity: 1, transform: 'translateY(0)' }))
34
+ ])
35
+ ]),
36
+ trigger('slideInDown', [
37
+ transition(':enter', [
38
+ style({ opacity: 0, transform: 'translateY(-20px)' }),
39
+ animate('400ms cubic-bezier(0.4, 0, 0.2, 1)',
40
+ style({ opacity: 1, transform: 'translateY(0)' }))
41
+ ])
42
+ ]),
43
+ trigger('slideInLeft', [
44
+ transition(':enter', [
45
+ style({ opacity: 0, transform: 'translateX(-20px)' }),
46
+ animate('400ms cubic-bezier(0.4, 0, 0.2, 1)',
47
+ style({ opacity: 1, transform: 'translateX(0)' }))
48
+ ])
49
+ ]),
50
+ trigger('slideInRight', [
51
+ transition(':enter', [
52
+ style({ opacity: 0, transform: 'translateX(20px)' }),
53
+ animate('400ms cubic-bezier(0.4, 0, 0.2, 1)',
54
+ style({ opacity: 1, transform: 'translateX(0)' }))
55
+ ])
56
+ ]),
57
+ trigger('fadeIn', [
58
+ transition(':enter', [
59
+ style({ opacity: 0 }),
60
+ animate('500ms ease-in', style({ opacity: 1 }))
61
+ ])
62
+ ]),
63
+ trigger('pulseAnimation', [
64
+ transition('* => *', [
65
+ animate('2s ease-in-out',
66
+ keyframes([
67
+ style({ transform: 'scale(1)', offset: 0 }),
68
+ style({ transform: 'scale(1.02)', offset: 0.5 }),
69
+ style({ transform: 'scale(1)', offset: 1 })
70
+ ])
71
+ )
72
+ ])
73
+ ]),
74
+ trigger('expandAnimation', [
75
+ transition(':enter', [
76
+ style({ height: 0, opacity: 0 }),
77
+ animate('300ms ease-out',
78
+ style({ height: '*', opacity: 1 }))
79
+ ]),
80
+ transition(':leave', [
81
+ animate('300ms ease-in',
82
+ style({ height: 0, opacity: 0 }))
83
+ ])
84
+ ]),
85
+ trigger('staggerItem', [
86
+ transition(':enter', [
87
+ query(':enter', [
88
+ style({ opacity: 0, transform: 'translateY(10px)' }),
89
+ stagger('50ms', [
90
+ animate('300ms ease-out',
91
+ style({ opacity: 1, transform: 'translateY(0)' }))
92
+ ])
93
+ ], { optional: true })
94
+ ])
95
+ ])
96
+ ]
97
  })
98
+ export class CaseDetailsSummaryPageComponent implements OnInit, OnDestroy {
99
+ @ViewChild('mainContent') mainContent!: ElementRef;
100
+
101
+ caseId: string | null = null;
102
+ selectedCase: PoliceCase | null = null;
103
+
104
+ // UI State
105
+ activeMainHeader: string = 'crime';
106
+ activeSubheader: string = '';
107
+ activeSubheaderName: string = '';
108
+ expandedMainHeader: string | null = 'crime';
109
+ expandedFields = new Set<string>();
110
+ allExpanded = false;
111
+ isScrolled = false;
112
+ isFavorited = true;
113
+ loading = true;
114
+ printMode = false;
115
+ // Notification properties
116
+ showNotification: boolean = false;
117
+ notificationTitle: string = '';
118
+ notificationMessage: string = '';
119
+ notificationIcon: string = 'fas fa-info-circle';
120
+
121
+ // Data
122
+ sidebarSections: Array<{ key: string, title: string, icon: string }> = [
123
+ { key: 'crime', title: 'Crime Details', icon: 'fas fa-gavel' },
124
+ { key: 'suspect', title: 'Suspect Details', icon: 'fas fa-user-secret' },
125
+ { key: 'notes', title: 'Evidence & Documents', icon: 'fas fa-file-alt' }
126
+ ];
127
+
128
+ quickInfoItems: Array<{ label: string, icon: string, value: string }> = [
129
+ { label: 'Date Filed', icon: 'fas fa-calendar-alt', value: '' },
130
+ { label: 'Location', icon: 'fas fa-map-marker-alt', value: '' },
131
+ { label: 'Investigating Officer', icon: 'fas fa-user-shield', value: '' }
132
+ ];
133
+
134
+ sections: any = {
135
+ crime: {
136
+ title: 'Crime Details',
137
+ icon: 'fas fa-gavel',
138
+ subgroups: {
139
+ 'Identification & Timing': ['Case ID', 'FIR / Ref #', 'Crime Type', 'Case Category', 'Date & Time (Entry)', 'Occurred From', 'Occurred To', 'Time Reported', 'Time Discovered', 'Country', 'State', 'District', 'Number of Victims', 'Brief Description'],
140
+ 'Location & People': ['Location', 'Jurisdiction / PS', 'Scene Type', 'Reported By', 'Reported Contact', 'Witness Count', 'Victim Name', 'Victim Contact', 'Suspected Offender Known?', 'Suspect Link', 'Victim Summary'],
141
+ 'Offence & Context': ['Legal Sections / Charges', 'Offence Category', 'Suspected Motive', 'Confirmed Motive', 'Weapon Involved', 'Property Loss / Damage', 'Offence Description'],
142
+ 'Evidence & Scene': ['Evidence Collected', 'Physical Evidence', 'Evidence Storage Reference', 'Photos / Video?', 'CCTV Present?', 'CCTV Sources / IDs', 'Forensic Tests Required', 'Chain of Custody?', 'Scene Condition', 'Digital Evidence'],
143
+ 'Operational Notes': ['Investigating Officer', 'Duty Person', 'Supervising Officer', 'Arrest Made', 'Arrest Location', 'Initial Actions Taken', 'riskLevel', 'Confidentiality', 'Patrol Notes'],
144
+ 'Status & Linkage': ['Biometric / Forensic IDs', 'DNA Ref ID', 'Fingerprint ID', 'Case Status', 'Linked Cases', 'arrestCount', 'Case Priority', 'Follow-up Date', 'Court Case ID', 'Next Hearing Date', 'Final Summary'],
145
+ 'Remark': ['Remark']
146
+ }
147
+ },
148
+ suspect: {
149
+ title: 'Suspect Details',
150
+ icon: 'fas fa-user-secret',
151
+ subgroups: {
152
+ 'Identity': ['Suspect ID', 'Suspect Name', 'Alias / Nickname', 'Age', 'Gender', 'Nationality', 'Nationality ID / Passport Number', 'Languages', 'Address', 'Known Aliases', 'Government ID'],
153
+ 'Physical Description': ['Height (cm)', 'Weight (kg)', 'Tattoo Details', 'Hair Color', 'Scar Details', 'Distinguishing Marks', 'Build', 'Eye Color', 'Photo Upload'],
154
+ 'Background': ['Employment', 'Education', 'Occupation', 'Company', 'Workplace Address', 'Marital Status', 'Known Habits', 'Known Financial Details'],
155
+ 'Known Associates': ['Associate Names', 'Gang Affiliation', 'Family Connections', 'Social Media Handles'],
156
+ 'Prior Records': ['Criminal History', 'Prior Arrests', 'Probation/Parole Status'],
157
+ 'Remark': ['Remark']
158
+ }
159
+ },
160
+ notes: {
161
+ title: 'Evidence and Documents',
162
+ icon: 'fas fa-file-alt',
163
+ subgroups: {
164
+ 'Investigation Notes': ['Status', 'Version History / Updates', 'Initial Findings', 'Detailed Notes'],
165
+ 'Evidence Files': ['Evidence Photos', 'Evidence Videos', 'Evidence Documents'],
166
+ 'Links and Recommendation': ['Links to Evidence', 'Final Recommendations'],
167
+ 'Remark': ['Remark']
168
+ }
169
+ }
170
+ };
171
+
172
+ crimeSubgroupOrder: Array<{ name: string, icon: string }> = [
173
+ { name: 'Identification & Timing', icon: 'fas fa-fingerprint' },
174
+ { name: 'Location & People', icon: 'fas fa-map-marker-alt' },
175
+ { name: 'Offence & Context', icon: 'fas fa-gavel' },
176
+ { name: 'Evidence & Scene', icon: 'fas fa-search' },
177
+ { name: 'Operational Notes', icon: 'fas fa-clipboard-list' },
178
+ { name: 'Status & Linkage', icon: 'fas fa-link' },
179
+ { name: 'Remark', icon: 'fas fa-comment' }
180
+ ];
181
+
182
+ suspectSubgroupOrder: Array<{ name: string, icon: string }> = [
183
+ { name: 'Identity', icon: 'fas fa-id-card' },
184
+ { name: 'Physical Description', icon: 'fas fa-user' },
185
+ { name: 'Background', icon: 'fas fa-history' },
186
+ { name: 'Known Associates', icon: 'fas fa-users' },
187
+ { name: 'Prior Records', icon: 'fas fa-file-alt' },
188
+ { name: 'Remark', icon: 'fas fa-comment' }
189
+ ];
190
+
191
+ notesSubgroupOrder: Array<{ name: string, icon: string }> = [
192
+ { name: 'Investigation Notes', icon: 'fas fa-clipboard-check' },
193
+ { name: 'Evidence Files', icon: 'fas fa-folder-open' },
194
+ { name: 'Links and Recommendation', icon: 'fas fa-paperclip' },
195
+ { name: 'Remark', icon: 'fas fa-comment' }
196
+ ];
197
+
198
+ // Date fields for formatting
199
+ private dateTimeFields = new Set<string>(['Date & Time (Entry)', 'Occurred From', 'Occurred To', 'Time Reported', 'Time Discovered']);
200
+ private dateFields = new Set<string>(['Follow-up Date', 'Next Hearing Date']);
201
+ private routeSubscriptions: Subscription[] = [];
202
+ private fromPage = 'record';
203
+ private returnId: string | null = null;
204
+ private currentDate = new Date();
205
+
206
+ constructor(
207
+ private route: ActivatedRoute,
208
+ private router: Router,
209
+ private caseStore: CaseStoreService
210
+ ) { }
211
+
212
+ ngOnInit() {
213
+ this.initializeFromNavigation();
214
+ this.loadSelectedCase();
215
+ this.initializeDefaults();
216
+ this.setupQuickInfo();
217
+ }
218
+
219
+ ngOnDestroy() {
220
+ this.routeSubscriptions.forEach(sub => sub.unsubscribe());
221
+ }
222
+
223
+ @HostListener('window:scroll', ['$event'])
224
+ onScroll(event: Event) {
225
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
226
+ this.isScrolled = scrollTop > 20;
227
+ }
228
+
229
+ private initializeFromNavigation(): void {
230
+ this.loading = true;
231
+
232
+ const paramSub = this.route.paramMap.subscribe(params => {
233
+ this.caseId = params.get('id');
234
+ });
235
+
236
+ const querySub = this.route.queryParams.subscribe(params => {
237
+ if (params['from']) {
238
+ this.fromPage = params['from'];
239
+ }
240
+ if (params['returnId']) {
241
+ this.returnId = params['returnId'];
242
+ }
243
+ if (params['print'] === 'true') {
244
+ this.printMode = true;
245
+ setTimeout(() => window.print(), 500);
246
+ }
247
+ });
248
+
249
+ this.routeSubscriptions.push(paramSub, querySub);
250
+
251
+ const nav = this.router.getCurrentNavigation();
252
+ if (nav?.extras?.state) {
253
+ const state = nav.extras.state as any;
254
+ if (state.from) this.fromPage = state.from;
255
+ if (state.returnId) this.returnId = state.returnId;
256
+ if (state.case) this.selectedCase = state.case as PoliceCase;
257
+ if (state.caseId) this.returnId = this.returnId || state.caseId;
258
+ if (state.favorited) this.isFavorited = state.favorited;
259
+ }
260
+ }
261
+
262
+ private initializeDefaults(): void {
263
+ this.activeMainHeader = 'crime';
264
+ this.expandedMainHeader = 'crime';
265
+
266
+ const crimeSubgroups = Object.keys(this.sections['crime']?.subgroups || {});
267
+ const first = crimeSubgroups[0] || '';
268
+ this.activeSubheader = 'crime-' + first;
269
+ this.activeSubheaderName = first;
270
+
271
+ this.loading = false;
272
+ }
273
+
274
+ private loadSelectedCase(): void {
275
+ if (!this.selectedCase) {
276
+ const id = this.caseId || this.returnId;
277
+ if (id) {
278
+ const cases = this.caseStore.getPoliceCases();
279
+ const found = cases.find(c =>
280
+ c.caseId === id || String(c.caseId).toLowerCase() === id.toLowerCase()
281
+ );
282
+ if (found) {
283
+ this.selectedCase = found;
284
+ }
285
+ }
286
+ }
287
+ }
288
+
289
+ private setupQuickInfo(): void {
290
+ if (this.selectedCase) {
291
+ const dateTime = this.getFilledDetail('crime', 'Identification & Timing', 'Date & Time (Entry)');
292
+ this.quickInfoItems[0].value = dateTime !== '— Not Provided' ? dateTime : 'Not Specified';
293
+
294
+ const location = this.getLocation();
295
+ this.quickInfoItems[1].value = location !== 'Not Specified' ? location : 'Not Specified';
296
+
297
+ const officer = this.getFilledDetail('crime', 'Operational Notes', 'Investigating Officer');
298
+ this.quickInfoItems[2].value = officer !== '— Not Provided' ? officer : 'Not Specified';
299
+ } else {
300
+ this.quickInfoItems.forEach(item => item.value = 'Not Specified');
301
+ }
302
+ }
303
+
304
+ getSubgroupsForSection(sectionKey: string): Array<{ name: string, icon: string }> {
305
+ switch (sectionKey) {
306
+ case 'crime': return this.crimeSubgroupOrder;
307
+ case 'suspect': return this.suspectSubgroupOrder;
308
+ case 'notes': return this.notesSubgroupOrder;
309
+ default: return [];
310
+ }
311
+ }
312
+
313
+ setMainHeader(header: string): void {
314
+ if (this.expandedMainHeader === header) {
315
+ this.expandedMainHeader = null;
316
+ } else {
317
+ this.expandedMainHeader = header;
318
+ this.activeMainHeader = header;
319
+
320
+ const subgroups = this.getSubgroupsForSection(header);
321
+ if (subgroups.length > 0) {
322
+ const firstSubgroup = subgroups[0].name;
323
+ this.activeSubheader = `${header}-${firstSubgroup}`;
324
+ this.activeSubheaderName = firstSubgroup;
325
+ }
326
+ }
327
+ }
328
+
329
+ setSubheader(mainHeader: string, subgroup: string): void {
330
+ this.activeSubheader = `${mainHeader}-${subgroup}`;
331
+ this.activeSubheaderName = subgroup;
332
+ }
333
+
334
+ toggleFieldExpand(field: string): void {
335
+ if (this.expandedFields.has(field)) {
336
+ this.expandedFields.delete(field);
337
+ } else {
338
+ this.expandedFields.add(field);
339
+ }
340
+ }
341
+
342
+ toggleExpandAll(): void {
343
+ this.allExpanded = !this.allExpanded;
344
+ if (this.allExpanded) {
345
+ this.showNotificationMessage('All fields are now expanded', 'fas fa-expand');
346
+ } else {
347
+ this.showNotificationMessage('All fields are now collapsed', 'fas fa-compress');
348
+ }
349
+ }
350
+
351
+ expandAllSections(): void {
352
+ this.expandedMainHeader = 'crime';
353
+ this.setMainHeader('suspect');
354
+ this.setMainHeader('notes');
355
+ this.expandedMainHeader = 'crime';
356
+ this.allExpanded = true;
357
+ this.showNotificationMessage('All sections are now expanded', 'fas fa-expand-arrows-alt');
358
+ }
359
+
360
+ getFilledDetail(mainHeader: string, subgroup: string, field: string): string {
361
+ if (this.selectedCase) {
362
+ const value = this.getFieldValueFromCase(this.selectedCase, field);
363
+
364
+ if (value !== null && value !== undefined && value !== '' && value !== 'Not Provided') {
365
+ return this.formatValue(value, field);
366
+ }
367
+ }
368
+
369
+ return ' Not Provided';
370
+ }
371
+
372
+ private formatValue(value: any, field: string): string {
373
+ if (Array.isArray(value)) {
374
+ return value.join(', ');
375
+ }
376
+
377
+ if (this.dateFields.has(field)) {
378
+ const date = new Date(value);
379
+ if (!isNaN(date.getTime())) {
380
+ return date.toLocaleDateString('en-US', {
381
+ year: 'numeric',
382
+ month: 'short',
383
+ day: 'numeric'
384
+ });
385
+ }
386
+ }
387
+
388
+ if (this.dateTimeFields.has(field)) {
389
+ const date = new Date(value);
390
+ if (!isNaN(date.getTime())) {
391
+ return date.toLocaleString('en-US', {
392
+ year: 'numeric',
393
+ month: 'short',
394
+ day: 'numeric',
395
+ hour: '2-digit',
396
+ minute: '2-digit',
397
+ hour12: true
398
+ });
399
+ }
400
+ }
401
+
402
+ if (typeof value === 'boolean') {
403
+ return value ? 'Yes' : 'No';
404
+ }
405
+
406
+ return String(value).trim();
407
+ }
408
+
409
+ getFieldType(field: string): string {
410
+ if (this.dateFields.has(field) || this.dateTimeFields.has(field)) {
411
+ return 'Date/Time';
412
+ }
413
+ if (field.toLowerCase().includes('description') ||
414
+ field.toLowerCase().includes('notes') ||
415
+ field.toLowerCase().includes('remarks')) {
416
+ return 'Text';
417
+ }
418
+ if (field.toLowerCase().includes('count') ||
419
+ field.toLowerCase().includes('number') ||
420
+ field.toLowerCase().includes('age') ||
421
+ field.toLowerCase().includes('height') ||
422
+ field.toLowerCase().includes('weight')) {
423
+ return 'Number';
424
+ }
425
+ return 'Text';
426
+ }
427
+
428
+ isDescriptionField(field: string): boolean {
429
+ const descKeywords = ['description', 'notes', 'findings', 'summary', 'remarks'];
430
+ return descKeywords.some(keyword => field.toLowerCase().includes(keyword));
431
+ }
432
+
433
+ isImportantField(field: string): boolean {
434
+ const importantKeywords = ['status', 'priority', 'motive', 'weapon', 'emergency', 'critical'];
435
+ return importantKeywords.some(keyword => field.toLowerCase().includes(keyword));
436
+ }
437
+
438
+ getFieldIcon(field: string): string {
439
+ if (field.toLowerCase().includes('date') || field.toLowerCase().includes('time')) {
440
+ return 'fas fa-calendar-alt';
441
+ } else if (field.toLowerCase().includes('location') || field.toLowerCase().includes('address')) {
442
+ return 'fas fa-map-marker-alt';
443
+ } else if (field.toLowerCase().includes('name') || field.toLowerCase().includes('officer')) {
444
+ return 'fas fa-user';
445
+ } else if (field.toLowerCase().includes('status')) {
446
+ return 'fas fa-info-circle';
447
+ } else if (field.toLowerCase().includes('priority')) {
448
+ return 'fas fa-flag';
449
+ } else if (field.toLowerCase().includes('description') || field.toLowerCase().includes('notes')) {
450
+ return 'fas fa-file-alt';
451
+ } else {
452
+ return 'fas fa-tag';
453
+ }
454
+ }
455
+
456
+ getFieldHint(field: string): string {
457
+ if (field.toLowerCase().includes('date')) {
458
+ return 'Date format: MM/DD/YYYY';
459
+ } else if (field.toLowerCase().includes('time')) {
460
+ return 'Time format: HH:MM AM/PM';
461
+ } else if (field.toLowerCase().includes('email')) {
462
+ return 'Enter valid email address';
463
+ } else if (field.toLowerCase().includes('phone')) {
464
+ return 'Enter 10-digit phone number';
465
+ } else {
466
+ return 'Enter relevant information';
467
+ }
468
+ }
469
+
470
+ getFieldUpdateTime(field: string): string {
471
+ try {
472
+ const lu = this.selectedCase?.lastUpdated;
473
+ if (lu) {
474
+ const date = new Date(lu);
475
+ if (!isNaN(date.getTime())) {
476
+ const diffMs = Date.now() - date.getTime();
477
+ const diffMin = Math.floor(diffMs / 60000);
478
+ if (diffMin <= 0) return 'Just now';
479
+ if (diffMin < 60) return `${diffMin} minute${diffMin === 1 ? '' : 's'} ago`;
480
+ const diffHr = Math.floor(diffMin / 60);
481
+ if (diffHr < 24) return `${diffHr} hour${diffHr === 1 ? '' : 's'} ago`;
482
+ return date.toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
483
+ }
484
+ }
485
+ } catch (e) {
486
+ // ignore and fallback to mock
487
+ }
488
+
489
+ // Fallback: random-ish mock update time
490
+ const hoursAgo = Math.floor(Math.random() * 24);
491
+ if (hoursAgo === 0) return 'Just now';
492
+ if (hoursAgo === 1) return '1 hour ago';
493
+ return `${hoursAgo} hours ago`;
494
+ }
495
+
496
+ private getFieldValueFromCase(caseObj: any, field: string): any {
497
+ const fieldMap: { [key: string]: string | string[] } = {
498
+ // Crime Details
499
+ 'Case ID': 'caseId',
500
+ 'FIR / Ref #': 'firRef',
501
+ 'Crime Type': 'crime',
502
+ 'Case Category': 'caseCategory',
503
+ 'Date & Time (Entry)': 'dateTime',
504
+ 'Occurred From': 'occurredFrom',
505
+ 'Occurred To': 'occurredTo',
506
+ 'Time Reported': 'timeReported',
507
+ 'Time Discovered': 'timeDiscovered',
508
+ 'Country': 'country',
509
+ 'State': 'state',
510
+ 'District': 'district',
511
+ 'Number of Victims': 'numberOfVictims',
512
+ 'Brief Description': 'briefDescription',
513
+ 'Location': ['police', 'address'],
514
+ 'Jurisdiction / PS': 'jurisdiction',
515
+ 'Scene Type': 'sceneType',
516
+ 'Reported By': 'reportedBy',
517
+ 'Reported Contact': 'reportedContact',
518
+ 'Witness Count': 'witnessCount',
519
+ 'Victim Name': 'victimName',
520
+ 'Victim Contact': 'victimContact',
521
+ 'Victim Summary': 'victimSummary',
522
+ 'Suspected Offender Known?': 'suspectedOffenderKnown',
523
+ 'Suspect Link': 'suspectLink',
524
+ 'Legal Sections / Charges': 'legalSections',
525
+ 'Offence Category': 'offenceCategory',
526
+ 'Offence Description': 'offenceDescription',
527
+ 'Suspected Motive': 'suspectedMotive',
528
+ 'Confirmed Motive': 'confirmedMotive',
529
+ 'Weapon Involved': 'weaponInvolved',
530
+ 'Property Loss / Damage': 'propertyLoss',
531
+ 'Evidence Collected': 'evidenceCollected',
532
+ 'Forensic Tests Required': 'forensicTestsRequired',
533
+ 'Scene Condition': 'sceneCondition',
534
+ 'Photos / Video?': 'photosVideo',
535
+ 'CCTV Present?': 'cctvPresent',
536
+ 'CCTV Sources / IDs': 'cctvSources',
537
+ 'Physical Evidence (list)': 'physicalEvidence',
538
+ 'Chain of Custody?': 'chainOfCustody',
539
+ 'Digital Evidence': 'digitalEvidence',
540
+ 'Evidence Storage Reference': 'evidenceStorageReference',
541
+ 'Investigating Officer': ['police', 'name'],
542
+ 'Duty Person': ['police', 'dutyPerson'],
543
+ 'Supervising Officer': ['police', 'supervisingOfficer'],
544
+ 'Patrol Notes': ['police', 'patrolNotes'],
545
+ 'Arrest Made': 'arrestMade',
546
+ 'Arrest Location': 'arrestLocation',
547
+ 'Initial Actions Taken': 'initialActionsTaken',
548
+ 'riskLevel': 'riskLevel',
549
+ 'Confidentiality': 'confidentiality',
550
+ 'Biometric / Forensic IDs': 'biometricIds',
551
+ 'DNA Ref ID': 'dnaRefId',
552
+ 'Fingerprint ID': 'fingerprintId',
553
+ 'Case Status': 'status',
554
+ 'Linked Cases': 'linkedCases',
555
+ 'arrestCount': 'arrestCount',
556
+ 'Case Priority': 'casePriority',
557
+ 'Follow-up Date': 'followUpDate',
558
+ 'Court Case ID': 'courtCaseId',
559
+ 'Next Hearing Date': 'nextHearingDate',
560
+ 'Final Summary': 'finalSummary',
561
+ 'Remark': 'remark',
562
+
563
+ // Suspect Details
564
+ 'Suspect ID': ['accused', 'suspectId'],
565
+ 'Suspect Name': ['accused', 'name'],
566
+ 'Alias / Nickname': ['accused', 'alias'],
567
+ 'Age': ['accused', 'age'],
568
+ 'Gender': ['accused', 'gender'],
569
+ 'Nationality': ['accused', 'nationality'],
570
+ 'Nationality ID / Passport Number': ['accused', 'passportNumber'],
571
+ 'Languages': ['accused', 'languages'],
572
+ 'Address': ['accused', 'address'],
573
+ 'Known Aliases': ['accused', 'knownAliases'],
574
+ 'Government ID': ['accused', 'governmentId'],
575
+ 'Height (cm)': ['accused', 'height'],
576
+ 'Weight (kg)': ['accused', 'weight'],
577
+ 'Build': ['accused', 'build'],
578
+ 'Hair Color': ['accused', 'hairColor'],
579
+ 'Eye Color': ['accused', 'eyeColor'],
580
+ 'Distinguishing Marks': ['accused', 'distinguishingMarks'],
581
+ 'Tattoo Details': ['accused', 'tattooDetails'],
582
+ 'Scar Details': ['accused', 'scarDetails'],
583
+ 'Photo Upload': ['accused', 'photoUpload'],
584
+ 'Employment': ['accused', 'employment'],
585
+ 'Education': ['accused', 'education'],
586
+ 'Occupation': ['accused', 'occupation'],
587
+ 'Company': ['accused', 'company'],
588
+ 'Workplace Address': ['accused', 'workplaceAddress'],
589
+ 'Marital Status': ['accused', 'maritalStatus'],
590
+ 'Known Habits': ['accused', 'knownHabits'],
591
+ 'Known Financial Details': ['accused', 'knownFinancialDetails'],
592
+ 'Associate Names': ['accused', 'associateNames'],
593
+ 'Gang Affiliation': ['accused', 'gangAffiliation'],
594
+ 'Family Connections': ['accused', 'familyConnections'],
595
+ 'Social Media Handles': ['accused', 'socialMediaHandles'],
596
+ 'Criminal History': ['accused', 'criminalHistory'],
597
+ 'Prior Arrests': ['accused', 'priorArrests'],
598
+ 'Probation/Parole Status': ['accused', 'probationStatus'],
599
+
600
+ // Notes/Evidence
601
+ 'Initial Findings': ['police', 'information'],
602
+ 'Detailed Notes': ['notes', 'detailedNotes'],
603
+ 'Status': 'status',
604
+ 'Version History / Updates': ['notes', 'versionHistory'],
605
+ 'Evidence Photos': ['legal', 'evidencePhotos'],
606
+ 'Evidence Videos': ['legal', 'evidenceVideos'],
607
+ 'Evidence Documents': ['legal', 'evidenceDocuments'],
608
+ 'Links to Evidence': ['legal', 'linksToEvidence'],
609
+ 'Final Recommendations': ['legal', 'finalRecommendations']
610
+ };
611
+
612
+ const path = fieldMap[field] || field;
613
+ let value: any = undefined;
614
+
615
+ if (Array.isArray(path)) {
616
+ let tempValue: any = caseObj;
617
+ for (const p of path) {
618
+ if (tempValue && tempValue[p] !== undefined) {
619
+ tempValue = tempValue[p];
620
+ } else {
621
+ tempValue = undefined;
622
+ break;
623
+ }
624
+ }
625
+ value = tempValue;
626
+ } else {
627
+ value = caseObj && caseObj[path] !== undefined ? caseObj[path] : undefined;
628
+ }
629
+
630
+ if (value === null || value === undefined || value === '') {
631
+ value = this.findValueInFormData(caseObj, field, path);
632
+ }
633
+
634
+ return value;
635
+ }
636
+
637
+ getLocation(): string {
638
+ if (!this.selectedCase) return 'Not Specified';
639
+
640
+ const district = this.getFieldValueFromCase(this.selectedCase, 'District');
641
+ const state = this.getFieldValueFromCase(this.selectedCase, 'State');
642
+ const country = this.getFieldValueFromCase(this.selectedCase, 'Country');
643
+
644
+ const parts = [];
645
+ if (district && district !== '— Not Provided') parts.push(district);
646
+ if (state && state !== '— Not Provided') parts.push(state);
647
+ if (country && country !== '— Not Provided') parts.push(country);
648
+
649
+ return parts.length > 0 ? parts.join(', ') : 'Not Specified';
650
+ }
651
+
652
+ private findValueInFormData(caseObj: any, field: string, path: string | string[]): any {
653
+ try {
654
+ if (caseObj?.formData) {
655
+ if (caseObj.formData[field] !== undefined) {
656
+ return caseObj.formData[field];
657
+ }
658
+ }
659
+ } catch (e) {
660
+ console.warn('Error accessing formData:', e);
661
+ }
662
+
663
+ return undefined;
664
+ }
665
+
666
+ getCaseStatusClass(): string {
667
+ const status = this.selectedCase?.status?.toLowerCase() || '';
668
+ if (status.includes('open')) return 'open';
669
+ if (status.includes('investigation')) return 'under-investigation';
670
+ if (status.includes('closed')) return 'closed';
671
+ if (status.includes('pending')) return 'pending';
672
+ return 'not-assigned';
673
+ }
674
+
675
+ getProgressPercentage(): number {
676
+ if (!this.selectedCase) return 0;
677
+
678
+ const fields = [
679
+ 'Case ID', 'Crime Type', 'Date & Time (Entry)', 'Location',
680
+ 'Investigating Officer', 'Case Status', 'Case Priority'
681
+ ];
682
+
683
+ let filled = 0;
684
+ fields.forEach(field => {
685
+ const value = this.getFieldValueFromCase(this.selectedCase, field);
686
+ if (value && value !== 'Not Provided') filled++;
687
+ });
688
+
689
+ return Math.round((filled / fields.length) * 100);
690
+ }
691
+
692
+ hasEmptyFields(): boolean {
693
+ if (!this.activeMainHeader || !this.activeSubheaderName) return false;
694
+ return this.getEmptyFieldsCount() > 0;
695
+ }
696
+
697
+ getEmptyFieldsCount(): number {
698
+ if (!this.activeMainHeader || !this.activeSubheaderName || !this.selectedCase) return 0;
699
+
700
+ const fields = this.sections[this.activeMainHeader]?.subgroups[this.activeSubheaderName] || [];
701
+ let emptyCount = 0;
702
+
703
+ fields.forEach((field: string) => {
704
+ const value = this.getFilledDetail(this.activeMainHeader, this.activeSubheaderName, field);
705
+ if (value === '— Not Provided') emptyCount++;
706
+ });
707
+
708
+ return emptyCount;
709
+ }
710
+
711
+ // UI Actions
712
+ toggleFavorite() {
713
+ this.isFavorited = !this.isFavorited;
714
+ const message = this.isFavorited ? 'Added to Favorites' : 'Removed from Favorites';
715
+ const icon = this.isFavorited ? 'fas fa-star' : 'far fa-star';
716
+ this.showNotificationMessage(message, icon);
717
+ }
718
+
719
+ addReminder(): void {
720
+ this.showNotificationMessage('Reminder added for this case', 'fas fa-bell');
721
+ }
722
+
723
+ copySection(): void {
724
+ if (!this.activeMainHeader || !this.activeSubheaderName) {
725
+ this.showNotificationMessage('Please select a section to copy', 'fas fa-exclamation-circle');
726
+ return;
727
+ }
728
+
729
+ try {
730
+ const sectionData = this.getSectionDataForExport();
731
+ const textToCopy = this.generateCopyText(sectionData);
732
+
733
+ navigator.clipboard.writeText(textToCopy).then(() => {
734
+ this.showNotificationMessage('Section copied to clipboard', 'fas fa-copy');
735
+ }).catch(err => {
736
+ console.error('Copy failed:', err);
737
+ this.fallbackCopyMethod(textToCopy);
738
+ });
739
+ } catch (error) {
740
+ console.error('Copy failed:', error);
741
+ this.showNotificationMessage('Copy failed. Please try again.', 'fas fa-times-circle');
742
+ }
743
+ }
744
+
745
+ copyFieldValue(field: string, event: Event): void {
746
+ event.stopPropagation();
747
+ const value = this.getFilledDetail(this.activeMainHeader, this.activeSubheaderName, field);
748
+
749
+ // Remove the "— " prefix if present
750
+ const cleanValue = value.startsWith('— ') ? value.substring(2) : value;
751
+
752
+ navigator.clipboard.writeText(cleanValue).then(() => {
753
+ this.showNotificationMessage('Field value copied to clipboard', 'fas fa-copy');
754
+ }).catch(err => {
755
+ console.error('Copy failed:', err);
756
+ const textArea = document.createElement('textarea');
757
+ textArea.value = cleanValue;
758
+ document.body.appendChild(textArea);
759
+ textArea.select();
760
+ document.execCommand('copy');
761
+ document.body.removeChild(textArea);
762
+ this.showNotificationMessage('Field value copied to clipboard', 'fas fa-copy');
763
+ });
764
+ }
765
+
766
+ editField(field: string, event: Event): void {
767
+ event.stopPropagation();
768
+ this.showNotificationMessage(`Editing field: ${field}`, 'fas fa-edit');
769
+ }
770
+
771
+ highlightEmptyFields(): void {
772
+ this.showNotificationMessage('Empty fields are now highlighted', 'fas fa-highlighter');
773
+ }
774
+
775
+ scrollToTop(): void {
776
+ window.scrollTo({ top: 0, behavior: 'smooth' });
777
+ this.showNotificationMessage('Scrolled to top', 'fas fa-arrow-up');
778
+ }
779
+
780
+ exportSection(): void {
781
+ if (!this.activeMainHeader || !this.activeSubheaderName) {
782
+ this.showNotificationMessage('Please select a section to export', 'fas fa-exclamation-circle');
783
+ return;
784
+ }
785
+ try {
786
+ const sectionData = this.getSectionDataForExport();
787
+
788
+ // Create a JSON string
789
+ const dataStr = JSON.stringify(sectionData, null, 2);
790
+ const dataBlob = new Blob([dataStr], { type: 'application/json' });
791
+
792
+ // Create download link
793
+ const downloadUrl = URL.createObjectURL(dataBlob);
794
+ const link = document.createElement('a');
795
+ link.href = downloadUrl;
796
+ link.download = `Case_${this.selectedCase?.caseId || 'Unknown'}_${this.activeMainHeader}_${this.activeSubheaderName}.json`;
797
+ document.body.appendChild(link);
798
+ link.click();
799
+ document.body.removeChild(link);
800
+ URL.revokeObjectURL(downloadUrl);
801
+
802
+ this.showNotificationMessage('Section exported successfully', 'fas fa-download');
803
+ } catch (error) {
804
+ console.error('Export failed:', error);
805
+ this.showNotificationMessage('Export failed. Please try again.', 'fas fa-times-circle');
806
+ }
807
+ }
808
+
809
+ shareSection(): void {
810
+ if (!this.activeMainHeader || !this.activeSubheaderName) {
811
+ this.showNotificationMessage('Please select a section to share', 'fas fa-exclamation-circle');
812
+ return;
813
+ }
814
+
815
+ try {
816
+ const sectionData = this.getSectionDataForExport();
817
+ const shareText = this.generateShareText(sectionData);
818
+
819
+ if (navigator.share) {
820
+ navigator.share({
821
+ title: `Case ${this.selectedCase?.caseId || 'Unknown'} - ${this.activeMainHeader}`,
822
+ text: shareText,
823
+ url: window.location.href
824
+ }).then(() => {
825
+ this.showNotificationMessage('Shared successfully', 'fas fa-share-alt');
826
+ }).catch(err => {
827
+ console.error('Share failed:', err);
828
+ this.fallbackShareMethod(shareText);
829
+ });
830
+ } else {
831
+ this.fallbackShareMethod(shareText);
832
+ }
833
+ } catch (error) {
834
+ console.error('Share failed:', error);
835
+ this.showNotificationMessage('Share failed. Please try again.', 'fas fa-times-circle');
836
+ }
837
+ }
838
+
839
+ // Helper method to get section data for export
840
+ private getSectionDataForExport(): any {
841
+ const fields = this.sections[this.activeMainHeader!]?.subgroups[this.activeSubheaderName!];
842
+ const sectionData: any = {
843
+ caseId: this.selectedCase?.caseId,
844
+ section: this.activeMainHeader,
845
+ subgroup: this.activeSubheaderName,
846
+ fields: {},
847
+ exportDate: new Date().toISOString()
848
+ };
849
+
850
+ if (fields) {
851
+ fields.forEach((field: string) => {
852
+ sectionData.fields[field] = this.getFilledDetail(
853
+ this.activeMainHeader!,
854
+ this.activeSubheaderName!,
855
+ field
856
+ );
857
+ });
858
+ }
859
+
860
+ return sectionData;
861
+ }
862
+
863
+ // Helper method to generate copy text
864
+ private generateCopyText(sectionData: any): string {
865
+ const caseId = this.selectedCase?.caseId || 'Unknown Case';
866
+ const section = this.activeMainHeader?.toUpperCase() || '';
867
+ const subgroup = this.activeSubheaderName || '';
868
+
869
+ let text = `CASE: ${caseId}\n`;
870
+ text += `SECTION: ${section} - ${subgroup}\n`;
871
+ text += '══════════════════════════════════════════════════\n\n';
872
+
873
+ // Add field values
874
+ Object.keys(sectionData.fields).forEach(field => {
875
+ const value = sectionData.fields[field];
876
+ text += `${field}: ${value}\n`;
877
+ });
878
+
879
+ text += '\n══════════════════════════════════════════════════\n';
880
+ text += `Copied on: ${new Date().toLocaleString()}\n`;
881
+ text += `Source: PyDetect Case Management System`;
882
+
883
+ return text;
884
+ }
885
+
886
+ // Helper method to generate share text
887
+ private generateShareText(sectionData: any): string {
888
+ const caseId = this.selectedCase?.caseId || 'Unknown Case';
889
+ const section = this.activeMainHeader?.toUpperCase() || '';
890
+ const subgroup = this.activeSubheaderName || '';
891
+
892
+ let text = `Case ${caseId} - ${section}: ${subgroup}\n\n`;
893
+
894
+ // Add field values
895
+ Object.keys(sectionData.fields).forEach(field => {
896
+ const value = sectionData.fields[field];
897
+ if (value !== '— Not Provided') {
898
+ text += `${field}: ${value}\n`;
899
+ }
900
+ });
901
+
902
+ text += `\nExported: ${new Date().toLocaleString()}`;
903
+ return text;
904
+ }
905
+
906
+ // Fallback copy method
907
+ private fallbackCopyMethod(text: string): void {
908
+ const textArea = document.createElement('textarea');
909
+ textArea.value = text;
910
+ textArea.style.position = 'fixed';
911
+ textArea.style.left = '-999999px';
912
+ textArea.style.top = '-999999px';
913
+ document.body.appendChild(textArea);
914
+ textArea.focus();
915
+ textArea.select();
916
+
917
+ try {
918
+ const successful = document.execCommand('copy');
919
+ if (successful) {
920
+ this.showNotificationMessage('Section copied to clipboard', 'fas fa-copy');
921
+ } else {
922
+ throw new Error('Copy command failed');
923
+ }
924
+ } catch (err) {
925
+ console.error('Fallback copy failed:', err);
926
+ this.showNotificationMessage('Copy failed. Please try manually.', 'fas fa-times-circle');
927
+ // Last resort: show text in alert
928
+ alert('Please copy the following text:\n\n' + text);
929
+ } finally {
930
+ document.body.removeChild(textArea);
931
+ }
932
+ }
933
+
934
+ // Fallback share method (copy to clipboard)
935
+ private fallbackShareMethod(text: string): void {
936
+ navigator.clipboard.writeText(text).then(() => {
937
+ this.showNotificationMessage('Copied to clipboard!', 'fas fa-copy');
938
+ }).catch(err => {
939
+ console.error('Clipboard copy failed:', err);
940
+ // Last resort - show text in alert
941
+ alert('Share text:\n\n' + text);
942
+ this.showNotificationMessage('Text displayed for sharing', 'fas fa-info-circle');
943
+ });
944
+ }
945
+
946
+ addNote(): void {
947
+ const note = prompt('Add a note for this section:');
948
+ if (note && note.trim()) {
949
+ this.showNotificationMessage('Note added successfully', 'fas fa-check-circle');
950
+ }
951
+ }
952
+
953
+ exportAsPDF(): void {
954
+ try {
955
+ this.showNotificationMessage('Preparing PDF export...', 'fas fa-file-pdf');
956
+
957
+ // Create a printable version of the page
958
+ this.printMode = true;
959
+
960
+ setTimeout(() => {
961
+ window.print();
962
+
963
+ setTimeout(() => {
964
+ this.printMode = false;
965
+ this.showNotificationMessage('PDF ready for printing/saving', 'fas fa-file-pdf');
966
+ }, 500);
967
+ }, 1000);
968
+
969
+ } catch (error) {
970
+ console.error('PDF export failed:', error);
971
+ this.showNotificationMessage('PDF export failed. Please try printing instead.', 'fas fa-times-circle');
972
+ }
973
+ }
974
+
975
+ printSummary(): void {
976
+ this.printMode = true;
977
+ setTimeout(() => {
978
+ window.print();
979
+ this.printMode = false;
980
+ this.showNotificationMessage('Print dialog opened', 'fas fa-print');
981
+ }, 100);
982
+ }
983
+
984
+ private showNotificationMessage(message: string, icon: string = 'fas fa-info-circle'): void {
985
+ this.notificationTitle = 'Quick Action';
986
+ this.notificationMessage = message;
987
+ this.notificationIcon = icon;
988
+ this.showNotification = true;
989
+
990
+ // Auto-hide notification after 3 seconds
991
+ setTimeout(() => {
992
+ this.showNotification = false;
993
+ }, 3000);
994
+ }
995
+
996
+ hideNotification(): void {
997
+ this.showNotification = false;
998
+ }
999
+
1000
+ navigateBack(): void {
1001
+ switch (this.fromPage) {
1002
+ case 'case-details':
1003
+ const state = this.returnId ? { caseId: this.returnId } : undefined;
1004
+ this.router.navigate(['/case-details'], { state });
1005
+ break;
1006
+ case 'record':
1007
+ default:
1008
+ this.router.navigate(['/record']);
1009
+ break;
1010
+ }
1011
+ }
1012
+
1013
+ navigateHome(): void {
1014
+ this.router.navigate(['/']);
1015
+ }
1016
  }
src/app/homepage/homepage.component.css CHANGED
@@ -1,7 +1,8 @@
1
  body, html {
2
  margin: 0;
3
  padding: 0;
4
- font-family: 'Arial', 'Segoe UI', 'Roboto', sans-serif;
 
5
  /* Background is managed globally via body.homepage-bg in styles.css */
6
  }
7
 
@@ -13,7 +14,7 @@ body, html {
13
  position: relative;
14
  align-items: center; /* Center content vertically */
15
  justify-content: center; /* Center content horizontally */
16
- background: url(/assets/py-detect-illustration.png) no-repeat center center fixed;
17
  background-size: cover;
18
  }
19
 
@@ -303,8 +304,8 @@ body {
303
 
304
  label {
305
  color: #bae6fd;
306
- font-size: 26px;
307
- font-weight: 600;
308
  margin-bottom: 4px;
309
  letter-spacing: 1px;
310
  text-shadow: 0 0 6px #38bdf888;
@@ -316,7 +317,7 @@ label {
316
  border: 3px solid #38bdf8;
317
  background: rgba(30,41,59,0.98);
318
  color: #e3f6ff;
319
- font-size: 28px;
320
  width: 100%;
321
  box-shadow: 0 2px 12px #38bdf844;
322
  transition: border-color 0.3s;
@@ -338,14 +339,14 @@ label {
338
  background: linear-gradient(90deg, #38bdf8 0%, #23272b 100%);
339
  color: #e3f6ff;
340
  padding: 22px 0;
341
- font-size: 32px;
342
  border: none;
343
  border-radius: 14px;
344
  cursor: pointer;
345
  transition: background 0.3s ease, box-shadow 0.3s ease;
346
  width: 100%;
347
  margin-top: 0;
348
- font-weight: bold;
349
  letter-spacing: 1px;
350
  box-shadow: 0 2px 16px #38bdf888;
351
  text-shadow: 0 0 6px #23272b;
@@ -631,8 +632,8 @@ label {
631
  align-items: center;
632
  padding: 2vw;
633
  position: fixed;
634
- top: 37vh;
635
- left: calc(100% - 68vw);
636
  width: 32vw;
637
  text-wrap: auto;
638
  font-weight: 800;
@@ -641,7 +642,10 @@ label {
641
  }
642
 
643
  /* Subtle gradient overlay behind hero text for readability */
644
- .hero { position: fixed; /* already set, ensure stacking */ }
 
 
 
645
  .hero::before {
646
  content: '';
647
  position: absolute;
@@ -719,7 +723,7 @@ label {
719
 
720
  h2.section-title {
721
  font-size: 28px;
722
- color: #0d9de3;
723
  margin-bottom: 10px;
724
  }
725
  /* Footer */
@@ -1108,18 +1112,19 @@ footer .social-icons {
1108
 
1109
 
1110
  .header-nav {
1111
- display: flex;
1112
- align-items: center;
1113
- gap:48px;
1114
- color: #e3f6ff;
1115
- font-weight:700;
1116
- margin-right: 100px;
1117
- }
1118
- .header-nav .nav-link {
1119
- color: #e3f6ff;
1120
- text-decoration: none;
1121
- font-size:0.95rem;
1122
  }
 
 
 
 
 
 
1123
  .header-nav .nav-link:hover {
1124
  color: #38bdf8;
1125
  }
@@ -1127,16 +1132,23 @@ footer .social-icons {
1127
  color: #5fa8d4;
1128
  opacity:0.9;
1129
  }
1130
- .header-nav .nav-icon { font-size:0.95rem; margin-right:6px; vertical-align: -1px; opacity:0.9; }
 
 
 
 
 
1131
  .header-nav .dropdown-item .nav-icon { margin-right:8px; }
1132
 
1133
  /* Premium soft hover underline and color fade for header nav */
1134
- .header-nav .nav-link,
1135
- .header-nav .dropdown-toggle,
1136
- .header-nav .dropdown-item {
1137
- position: relative;
1138
- transition: color .25s ease;
1139
- }
 
 
1140
  .header-nav .nav-link::after,
1141
  .header-nav .dropdown-toggle::after,
1142
  .header-nav .dropdown-item::after {
@@ -1251,12 +1263,12 @@ footer .social-icons {
1251
  /* Title stack for logo + tagline */
1252
  .title-stack { display:flex; flex-direction: column; align-items:flex-start; gap:2px; }
1253
  .logo-tagline {
1254
- font-size:0.92rem;
1255
- font-weight:400;
1256
- color: #b7d6eb;
1257
- letter-spacing:0.3px;
1258
- margin-top: -6px; /* tuck closer under title */
1259
- opacity:0.9;
1260
  }
1261
 
1262
  /* Remove separate fixed language selector */
@@ -1270,7 +1282,7 @@ footer .social-icons {
1270
  color: #e3f6ff;
1271
  border:1px solid #3aa6d9;
1272
  border-radius:8px;
1273
- padding:08px;
1274
  font-size:0.9rem;
1275
  box-shadow:0 2px 10px rgba(2,6,23,0.35);
1276
  cursor: pointer;
@@ -1307,7 +1319,7 @@ footer .social-icons {
1307
  .auth-topright .auth-btn[data-tooltip]:focus-visible::after { opacity:1; transform: translateX(50%) translateY(0); }
1308
  .hero .hero-cta {
1309
  margin-top: 12px;
1310
- background: linear-gradient(90deg, #38bdf8 0%, #23272b 100%);
1311
  color: #18181b;
1312
  border: none;
1313
  border-radius: 999px;
@@ -1339,7 +1351,6 @@ footer .social-icons {
1339
  color: #e3f6ff;
1340
  position: relative; /* for pseudo-element if present */
1341
  }
1342
- /* If decorative background was present, keep but ensure card sits above */
1343
  .how-it-works::before {
1344
  content: '';
1345
  position: absolute;
@@ -1446,6 +1457,54 @@ footer .social-icons {
1446
  }
1447
 
1448
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1449
 
1450
 
1451
 
 
1
  body, html {
2
  margin: 0;
3
  padding: 0;
4
+ font-family: 'Inter', 'Segoe UI', Arial, sans-serif;
5
+ font-size: 16px;
6
  /* Background is managed globally via body.homepage-bg in styles.css */
7
  }
8
 
 
14
  position: relative;
15
  align-items: center; /* Center content vertically */
16
  justify-content: center; /* Center content horizontally */
17
+ /* background: url(/assets/py-detect-illustration.png) no-repeat center center fixed;*/
18
  background-size: cover;
19
  }
20
 
 
304
 
305
  label {
306
  color: #bae6fd;
307
+ font-size: 14px;
308
+ font-weight: 500;
309
  margin-bottom: 4px;
310
  letter-spacing: 1px;
311
  text-shadow: 0 0 6px #38bdf888;
 
317
  border: 3px solid #38bdf8;
318
  background: rgba(30,41,59,0.98);
319
  color: #e3f6ff;
320
+ font-size: 15px;
321
  width: 100%;
322
  box-shadow: 0 2px 12px #38bdf844;
323
  transition: border-color 0.3s;
 
339
  background: linear-gradient(90deg, #38bdf8 0%, #23272b 100%);
340
  color: #e3f6ff;
341
  padding: 22px 0;
342
+ font-size: 15px;
343
  border: none;
344
  border-radius: 14px;
345
  cursor: pointer;
346
  transition: background 0.3s ease, box-shadow 0.3s ease;
347
  width: 100%;
348
  margin-top: 0;
349
+ font-weight: 600;
350
  letter-spacing: 1px;
351
  box-shadow: 0 2px 16px #38bdf888;
352
  text-shadow: 0 0 6px #23272b;
 
632
  align-items: center;
633
  padding: 2vw;
634
  position: fixed;
635
+ top: 35vh;
636
+ left: calc(100% - 96vw);
637
  width: 32vw;
638
  text-wrap: auto;
639
  font-weight: 800;
 
642
  }
643
 
644
  /* Subtle gradient overlay behind hero text for readability */
645
+ /*.hero { position: fixed;
646
+
647
+ }*/
648
+
649
  .hero::before {
650
  content: '';
651
  position: absolute;
 
723
 
724
  h2.section-title {
725
  font-size: 28px;
726
+ color: #fff;
727
  margin-bottom: 10px;
728
  }
729
  /* Footer */
 
1112
 
1113
 
1114
  .header-nav {
1115
+ display: flex;
1116
+ align-items: center;
1117
+ gap: 41px;
1118
+ /* color: #692727 !important;*/
1119
+ font-weight: 700;
1120
+ margin-right: 100px;
 
 
 
 
 
1121
  }
1122
+ .header-nav .nav-link {
1123
+ color: #ffffff;
1124
+ text-decoration: none;
1125
+ font-size: 14px;
1126
+ font-weight: 600;
1127
+ }
1128
  .header-nav .nav-link:hover {
1129
  color: #38bdf8;
1130
  }
 
1132
  color: #5fa8d4;
1133
  opacity:0.9;
1134
  }
1135
+ .header-nav .nav-icon {
1136
+ font-size: 1vw;
1137
+ margin-right: 6px;
1138
+ /* vertical-align: -1px; */
1139
+ opacity: 0.9;
1140
+ }
1141
  .header-nav .dropdown-item .nav-icon { margin-right:8px; }
1142
 
1143
  /* Premium soft hover underline and color fade for header nav */
1144
+ .header-nav .nav-link,
1145
+ .header-nav .dropdown-toggle,
1146
+ .header-nav .dropdown-item {
1147
+ position: relative;
1148
+ transition: color .25s ease;
1149
+ display: flex;
1150
+ align-items: baseline;
1151
+ }
1152
  .header-nav .nav-link::after,
1153
  .header-nav .dropdown-toggle::after,
1154
  .header-nav .dropdown-item::after {
 
1263
  /* Title stack for logo + tagline */
1264
  .title-stack { display:flex; flex-direction: column; align-items:flex-start; gap:2px; }
1265
  .logo-tagline {
1266
+ font-size: 0.92rem;
1267
+ font-weight: 400;
1268
+ color: #ffffff;
1269
+ letter-spacing: 0.3px;
1270
+ margin-top: -6px; /* tuck closer under title */
1271
+ opacity: 0.9;
1272
  }
1273
 
1274
  /* Remove separate fixed language selector */
 
1282
  color: #e3f6ff;
1283
  border:1px solid #3aa6d9;
1284
  border-radius:8px;
1285
+ padding:0 8px;
1286
  font-size:0.9rem;
1287
  box-shadow:0 2px 10px rgba(2,6,23,0.35);
1288
  cursor: pointer;
 
1319
  .auth-topright .auth-btn[data-tooltip]:focus-visible::after { opacity:1; transform: translateX(50%) translateY(0); }
1320
  .hero .hero-cta {
1321
  margin-top: 12px;
1322
+ background: linear-gradient( 90deg, #38BDF8 0%, #0EA5E9 100% );
1323
  color: #18181b;
1324
  border: none;
1325
  border-radius: 999px;
 
1351
  color: #e3f6ff;
1352
  position: relative; /* for pseudo-element if present */
1353
  }
 
1354
  .how-it-works::before {
1355
  content: '';
1356
  position: absolute;
 
1457
  }
1458
 
1459
 
1460
+ /* Language box with globe icon and label */
1461
+ .lang-box {
1462
+ display: inline-flex;
1463
+ align-items: center;
1464
+ gap:8px;
1465
+ padding:6px 10px;
1466
+ border:1px solid #3aa6d9;
1467
+ border-radius:10px;
1468
+ background: rgba(13,26,43,0.65);
1469
+ margin:0 12px 8px 12px;
1470
+ box-shadow:0 2px 10px rgba(2,6,23,0.35);
1471
+ }
1472
+
1473
+ .lang-icon {
1474
+ color: #38bdf8;
1475
+ text-shadow:0 0 6px #38bdf866;
1476
+ font-size:0.95rem;
1477
+ }
1478
+
1479
+ .lang-label {
1480
+ color: #e3f6ff;
1481
+ font-size:0.9rem;
1482
+ opacity:0.95;
1483
+ }
1484
+
1485
+ /* Reuse existing select but remove large right margin so it fits the box */
1486
+ .lang-select.header-lang {
1487
+ margin-right:0;
1488
+ margin-top:0;
1489
+ height:28px;
1490
+ padding:4px 8px;
1491
+ }
1492
+
1493
+
1494
+ /* Language dropdown alignment to avoid overlapping auth button */
1495
+ .header-nav .lang-dropdown { position: relative; }
1496
+ .header-nav .lang-dropdown .dropdown-menu {
1497
+ right:0; /* align the menu to the toggle's right edge */
1498
+ left: auto;
1499
+ transform-origin: top right;
1500
+ }
1501
+ /* When the language dropdown is near the right edge, open to the left */
1502
+ @media (min-width:761px) {
1503
+ .header-nav .lang-dropdown .dropdown-menu {
1504
+ transform: translateY(0) translateX(-20px);
1505
+ }
1506
+ }
1507
+
1508
 
1509
 
1510
 
src/app/homepage/homepage.component.html CHANGED
@@ -24,43 +24,53 @@
24
  </div>
25
  <!-- Right-side minimal navigation -->
26
  <nav class="header-nav" aria-label="Primary">
27
- <a class="nav-link" [class.active]="selectedNav==='home'" href="#" (click)="navigateHome(); $event.preventDefault()">
28
- <i class="fas fa-home nav-icon" aria-hidden="true"></i> Home
29
- </a>
30
- <span class="nav-sep">|</span>
31
- <!-- About dropdown (second) -->
32
- <div class="dropdown">
33
- <a class="nav-link dropdown-toggle" [class.active]="selectedNav==='mission' || selectedNav==='how-to-use' || selectedNav==='about'" href="#" (click)="markSelected('about'); $event.preventDefault()">
34
- <i class="fas fa-info-circle nav-icon" aria-hidden="true"></i> About
35
- </a>
36
- <div class="dropdown-menu">
37
- <a class="dropdown-item" [class.active]="selectedNav==='mission'" href="#mission" (click)="scrollToMission($event)">
38
- <i class="fas fa-bullseye nav-icon" aria-hidden="true"></i> Our mission
39
- </a>
40
- <a class="dropdown-item" [class.active]="selectedNav==='how-to-use'" href="#how-to-use" (click)="scrollToHowToUse($event)">
41
- <i class="fas fa-lightbulb nav-icon" aria-hidden="true"></i> How to use
42
- </a>
43
- <a class="dropdown-item" [class.active]="selectedNav==='how-it-works'" href="#how-it-works" (click)="scrollToHowItWorks($event)">
44
- <i class="fas fa-cogs nav-icon" aria-hidden="true"></i> How it works
45
- </a>
46
- </div>
47
- </div>
48
- <span class="nav-sep">|</span>
49
- <!-- Features (third) -->
50
- <a class="nav-link" [class.active]="selectedNav==='features'" href="#features" (click)="scrollToFeatures($event)">
51
- <i class="fas fa-star nav-icon" aria-hidden="true"></i> Features
52
- </a>
53
- <span class="nav-sep">|</span>
54
- <!-- Support (fourth) -->
55
- <a class="nav-link" [class.active]="selectedNav==='support'" href="mailto:info@pykara.ai?subject=Py-Detect%20Support%20Request" (click)="markSelected('support')">
56
- <i class="fas fa-life-ring nav-icon" aria-hidden="true"></i> Support
57
- </a>
58
- <!-- Language selector inline, after Support -->
59
- <select class="lang-select header-lang" aria-label="Select language" [value]="selectedLanguage" (change)="onLanguageChange($any($event.target).value)">
60
- <option value="en">EN</option>
61
- <option value="en-IN">IN</option>
62
- </select>
63
- </nav>
 
 
 
 
 
 
 
 
 
 
64
  <!-- Top-right auth button -->
65
  <div class="auth-topright">
66
  <button class="auth-btn" (click)="openSignIn()" aria-label="Open Sign In" title="Account">
 
24
  </div>
25
  <!-- Right-side minimal navigation -->
26
  <nav class="header-nav" aria-label="Primary">
27
+
28
+ <a class="nav-link" [class.active]="selectedNav==='home'" href="#" (click)="navigateHome(); $event.preventDefault()">
29
+ <i class="fas fa-home nav-icon" aria-hidden="true"></i> Home
30
+ </a>
31
+ <span class="nav-sep">|</span>
32
+ <!-- About dropdown (second) -->
33
+ <div class="dropdown">
34
+ <a class="nav-link dropdown-toggle" [class.active]="selectedNav==='mission' || selectedNav==='how-to-use' || selectedNav==='about'" href="#" (click)="markSelected('about'); $event.preventDefault()">
35
+ <i class="fas fa-info-circle nav-icon" aria-hidden="true"></i> About ▾
36
+ </a>
37
+ <div class="dropdown-menu">
38
+ <a class="dropdown-item" [class.active]="selectedNav==='mission'" href="#mission" (click)="scrollToMission($event)">
39
+ <i class="fas fa-bullseye nav-icon" aria-hidden="true"></i> Our mission
40
+ </a>
41
+ <a class="dropdown-item" [class.active]="selectedNav==='how-to-use'" href="#how-to-use" (click)="scrollToHowToUse($event)">
42
+ <i class="fas fa-lightbulb nav-icon" aria-hidden="true"></i> How to use
43
+ </a>
44
+ <a class="dropdown-item" [class.active]="selectedNav==='how-it-works'" href="#how-it-works" (click)="scrollToHowItWorks($event)">
45
+ <i class="fas fa-cogs nav-icon" aria-hidden="true"></i> How it works
46
+ </a>
47
+ </div>
48
+ </div>
49
+ <span class="nav-sep">|</span>
50
+ <!-- Features (third) -->
51
+ <a class="nav-link" [class.active]="selectedNav==='features'" href="#features" (click)="scrollToFeatures($event)">
52
+ <i class="fas fa-star nav-icon" aria-hidden="true"></i> Features
53
+ </a>
54
+ <span class="nav-sep">|</span>
55
+ <!-- Support (fourth) -->
56
+ <a class="nav-link" [class.active]="selectedNav==='support'" href="mailto:info@pykara.ai?subject=Py-Detect%20Support%20Request" (click)="markSelected('support')">
57
+ <i class="fas fa-life-ring nav-icon" aria-hidden="true"></i> Support
58
+ </a>
59
+ <span class="nav-sep">|</span>
60
+ <!-- Language dropdown placed like About -->
61
+ <div class="dropdown lang-dropdown">
62
+ <a class="nav-link dropdown-toggle" href="#" (click)="$event.preventDefault()">
63
+ <i class="fas fa-globe-americas nav-icon" aria-hidden="true"></i> Language ▾
64
+ </a>
65
+ <div class="dropdown-menu" role="menu" aria-label="Choose language">
66
+ <a class="dropdown-item" href="#" (click)="onLanguageChange('en-GB'); $event.preventDefault()">English (UK)</a>
67
+ <a class="dropdown-item" href="#" (click)="onLanguageChange('en-US'); $event.preventDefault()">English (US)</a>
68
+ <a class="dropdown-item" href="#" (click)="onLanguageChange('sv'); $event.preventDefault()">Swedish</a>
69
+ <a class="dropdown-item" href="#" (click)="onLanguageChange('es'); $event.preventDefault()">Spanish</a>
70
+ <a class="dropdown-item" href="#" (click)="onLanguageChange('hi'); $event.preventDefault()">Hindi</a>
71
+ </div>
72
+ </div>
73
+ </nav>
74
  <!-- Top-right auth button -->
75
  <div class="auth-topright">
76
  <button class="auth-btn" (click)="openSignIn()" aria-label="Open Sign In" title="Account">
src/app/homepage/homepage.component.ts CHANGED
@@ -20,7 +20,7 @@ export class HomepageComponent implements OnInit, OnDestroy {
20
 
21
  // Track selected nav for persistent highlight
22
  selectedNav: string = '';
23
- selectedLanguage = 'en';
24
 
25
  // (Optional legacy flags kept intact if used elsewhere)
26
  selectedInfo: string | null = null;
 
20
 
21
  // Track selected nav for persistent highlight
22
  selectedNav: string = '';
23
+ selectedLanguage = 'en-US';
24
 
25
  // (Optional legacy flags kept intact if used elsewhere)
26
  selectedInfo: string | null = null;
src/app/infopage/infopage.component.css CHANGED
The diff for this file is too large to render. See raw diff
 
src/app/infopage/infopage.component.html CHANGED
@@ -29,12 +29,36 @@
29
  <i class="fas fa-save"></i>
30
  <span>{{ autoSaveStatus }}</span>
31
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  <!-- View Records Button -->
33
- <div class="view-records-btn" style="margin-left: 16px; vertical-align: middle; color: #fff; font-size: 1.5em; width: 2em; height: 2em; display: flex; align-items: center; justify-content: center; cursor: pointer; position: relative; margin-bottom: 15px;"
34
- type="button"
35
- (click)="goToRecords()"
36
- (mouseenter)="showViewRecordsTooltip = true"
37
- (mouseleave)="showViewRecordsTooltip = false">
38
  <i class="fas fa-folder-open"></i>
39
  <span *ngIf="showViewRecordsTooltip" style="position:absolute;top:100%;left:0%;transform:translateX(-50%);background:#222;color:#fff;padding:4px 12px;border-radius:6px;font-size:0.6em;white-space:nowrap;z-index:10;box-shadow:0 2px 8px rgba(0,0,0,0.12);">View Records</span>
40
  </div>
@@ -46,22 +70,22 @@
46
  <div class="section-navigation" [@fadeIn]>
47
  <div class="ai-neural-bg"></div>
48
  <div class="section-pills">
49
- <button class="section-pill main-section-pill ai-enhanced"
50
  *ngFor="let section of sectionKeys; let i = index"
51
  [class.active]="currentSection === section"
52
  [class.completed]="isSectionCompleted(section)"
53
- [attr.data-step]="i + 1"
54
  (click)="showSection(section)"
55
  [attr.tabindex]="0">
56
- <div class="pill-neural-core"></div>
57
  <i [class]="sectionIcons[section]"></i>
58
  <span>{{ sections[section].title }}</span>
 
 
59
  <div class="ai-completion-orb" *ngIf="isSectionCompleted(section)">
60
  <div class="orb-pulse"></div>
61
  <i class="fas fa-check"></i>
62
  </div>
63
- <div class="pill-scanner"></div>
64
  </button>
 
65
  </div>
66
  <div class="section-ai-grid"></div>
67
  </div>
@@ -94,7 +118,7 @@
94
  <div class="investigation-container">
95
 
96
  <!-- Universal Single Investigation Card -->
97
- <div class="form-card primary-card" [@cardSlide] #formCard1>
98
  <div class="card-header compact-card-header">
99
  <div class="card-header-main">
100
  <h2 class="compact-title">
@@ -122,26 +146,30 @@
122
  <i class="fas fa-times"></i>
123
  </button>
124
  </div>
 
 
125
  <div class="popup-fields-list">
126
  <div *ngFor="let field of getAvailableFields(); trackBy: trackByField" class="popup-field-row">
127
  <label class="popup-field-label">
128
  <input type="checkbox"
129
  [checked]="isFieldSelected(field)"
130
- (click)="$event.stopPropagation()"
131
  (change)="toggleFieldSelection(field, $event)" />
132
  <span class="popup-field-text">{{ field }}</span>
133
  </label>
134
  </div>
135
  </div>
136
  <div class="popup-actions">
137
- <button class="popup-action-btn clear-btn" (click)="resetFieldSelection($event)" type="button">
138
- <i class="fas fa-times-circle"></i> Clear All
 
139
  </button>
140
- <button class="popup-action-btn selectall-btn" (click)="selectAllFields($event)" type="button">
141
- <i class="fas fa-check-double"></i> Select All
 
142
  </button>
143
- <button class="popup-action-btn save-btn" (click)="closeFieldSelector()" type="button">
144
- <i class="fas fa-save"></i> Save
 
145
  </button>
146
  </div>
147
  </div>
@@ -154,9 +182,9 @@
154
  <strong>{{ sections[currentSection].title }} - {{ currentSubgroup }}:</strong> {{ getSectionDescription(currentSection) }}
155
  </div>
156
 
157
- <div class="fields-grid">
158
  <div *ngFor="let field of getPrimaryFields(); let i = index; trackBy: trackByField"
159
- class="field-container"
160
  [class.filled]="formData[field] && formData[field].toString().trim() !== ''"
161
  [attr.data-field]="field"
162
  [@fieldAnimation]>
@@ -170,85 +198,63 @@
170
  [attr.aria-label]="'Info for ' + field">
171
  <i class="fas fa-info"></i>
172
  </button>
173
- <!-- Microphone icon for Remark field -->
174
- <ng-container *ngIf="field === 'Remark'">
175
- <span style="display:inline-flex;align-items:center;position:relative;">
176
- <button type="button"
177
- (mouseenter)="showMicPopup = true"
178
- (mouseleave)="showMicPopup = false"
179
- (click)="toggleRecording()"
180
- style="background:none;border:none;outline:none;cursor:pointer;padding:0;margin-left:6px;">
181
- <i class="fas fa-microphone"
182
- [ngStyle]="{
183
- color: isRecording ? '#e74c3c' : '#3498db',
184
- animation: 'micPulse 1s infinite',
185
- fontSize: '1.2em',
186
- transition: 'color 0.2s'
187
- }"
188
- style="transition:color 0.2s;">
189
- </i>
190
- </button>
191
- <!-- Popup on hover -->
192
- <div *ngIf="showMicPopup"
193
- style="position:absolute;top:120%;left:0;z-index:10;background:#fff;border:1px solid #ccc;padding:8px 12px;border-radius:6px;box-shadow:0 2px 8px rgba(0,0,0,0.12);white-space:nowrap;min-width:160px;">
194
- Click to record your remark.<br>Release to stop.<br>Recording is {{ isRecording ? 'ON' : 'OFF' }}.
195
- </div>
196
- <!-- Inline animation keyframes -->
197
- <style>
198
- @keyframes micPulse {
199
- 0% {
200
- transform: scale(1);
201
- filter: drop-shadow(0 0 0 #e74c3c);
202
- }
203
 
204
- 50% {
205
- transform: scale(1.2);
206
- filter: drop-shadow(0 0 8px #e74c3c);
207
- }
208
-
209
- 100% {
210
- transform: scale(1);
211
- filter: drop-shadow(0 0 0 #e74c3c);
212
- }
213
- }
214
- </style>
215
- </span>
216
- </ng-container>
 
 
 
 
 
 
 
 
217
  </label>
218
 
219
- <!-- Dynamic Field Rendering -->
220
  <div class="input-container">
221
- <!-- File Upload Fields -->
222
- <div *ngIf="fileFields.has(field); else nonFileField" class="file-upload-block">
223
- <div class="file-drop-zone"
224
- (dragover)="onDragOver($event)"
225
- (dragleave)="onDragLeave($event)"
226
- (drop)="onFileDrop(field, $event)"
227
- [class.drag-over]="isDragOver">
228
- <input type="file"
229
- (change)="onFileChange(field, $event)"
230
- multiple
231
- [accept]="getAcceptedFileTypes(field)"
232
- class="file-input" />
233
- <div class="drop-zone-content">
234
- <i class="fas fa-cloud-upload-alt"></i>
235
- <p>Drop files here or click to browse</p>
236
  </div>
237
- </div>
238
- <div class="file-list" *ngIf="uploadedFiles[field]?.length">
239
- <div *ngFor="let f of uploadedFiles[field]" class="file-chip">
240
- <i [class]="getFileIcon(f.name)"></i>
241
- <span [title]="f.name">{{ f.name | slice:0:15 }}{{ f.name.length > 15 ? '...' : '' }}</span>
242
- <button class="remove-file" (click)="removeFile(field, f)">
243
- <i class="fas fa-times"></i>
244
- </button>
 
 
 
 
 
 
245
  </div>
246
  </div>
247
- </div>
248
 
249
- <ng-template #nonFileField>
250
- <!-- Date/Time Fields -->
251
- <ng-container *ngIf="dateTimeFields.has(field); else nonDateTime">
252
  <input type="datetime-local"
253
  class="field-input"
254
  [class.compact]="isCompactField(field)"
@@ -256,66 +262,66 @@
256
  (input)="onFieldChange(field)" />
257
  </ng-container>
258
 
259
- <ng-template #nonDateTime>
260
- <ng-container *ngIf="dateFields.has(field); else nonDate">
261
- <input type="date"
262
- class="field-input"
263
- [class.compact]="isCompactField(field)"
264
- [(ngModel)]="formData[field]"
265
- (input)="onFieldChange(field)" />
266
- </ng-container>
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
- <ng-template #nonDate>
269
- <!-- Cascading Dropdowns -->
270
- <ng-container *ngIf="field === 'Country' || field === 'State' || field === 'District'; else standardField">
271
- <select class="field-input"
272
- [class.compact]="isCompactField(field)"
273
- [(ngModel)]="formData[field]"
274
- (change)="onSelectChange(field, $event)">
275
- <option value="">-- Select {{ field }} --</option>
276
- <option *ngFor="let opt of getOptions(field)" [value]="opt">{{ opt }}</option>
277
- </select>
278
- </ng-container>
279
 
280
- <ng-template #standardField>
281
- <!-- Standard Select or Text Input -->
282
- <select *ngIf="getOptions(field)?.length; else textInput"
283
- class="field-input"
284
- [class.compact]="isCompactField(field)"
285
- [(ngModel)]="formData[field]"
286
- (change)="onSelectChange(field, $event)">
287
- <option value="">-- Select Option --</option>
288
- <option *ngFor="let opt of getOptions(field)" [value]="opt">{{ opt }}</option>
289
- </select>
290
 
291
- <ng-template #textInput>
292
- <!-- Description fields as textarea -->
293
- <textarea *ngIf="field.toLowerCase().includes('description') || field === 'Remark'; else regularInput"
294
- class="field-input auto-scroll-textarea"
295
- [class.compact]="isCompactField(field)"
296
- [(ngModel)]="formData[field]"
297
- (input)="onFieldChange(field)"
298
- [placeholder]="getFieldPlaceholder(field)"
299
- [attr.maxlength]="field === 'Brief Description' ? null : getMaxLength(field)"
300
- rows="3"></textarea>
301
- <!-- Recording status for Remark -->
302
- <div *ngIf="field === 'Remark' && isRecording" style="margin-top:4px;color:#e74c3c;font-size:0.95em;">
303
- Recording... Speak now.
304
- </div>
305
- <ng-template #regularInput>
306
- <input [type]="getInputType(field)"
307
- class="field-input"
308
- [class.compact]="isCompactField(field)"
309
- [(ngModel)]="formData[field]"
310
- (input)="onFieldChange(field)"
311
- [placeholder]="getFieldPlaceholder(field)"
312
- [maxlength]="getMaxLength(field)" />
313
- </ng-template>
314
- </ng-template>
315
- </ng-template>
316
- </ng-template>
317
- </ng-template>
318
- </ng-template>
319
  </div>
320
 
321
  <!-- Field Help Popover -->
@@ -325,39 +331,87 @@
325
  </div>
326
  </div>
327
  </div>
 
328
  </div>
329
- <!-- Removed modern-nav-btns from here to prevent scrolls -->
330
  </div>
331
- </div>
332
 
333
- <!-- Floating Modern Navigation Button (fixed, no scroll) -->
334
- <div class="modern-floating-nav-btn">
335
- <!-- Next button appears on all pages except the last Remark page under notes -->
336
- <button *ngIf="!(isLastSubgroup() && currentSection === 'notes' && currentSubgroup === 'Remark')" type="button" class="modern-round-btn next-btn-animated" (click)="nextSubgroup()" [disabled]="!canNextSubgroup()" title="Next">
337
- <i class="fas fa-arrow-right"></i>
338
- </button>
339
- <!-- Submit button only on last Remark page under notes -->
340
- <button *ngIf="isLastSubgroup() && currentSection === 'notes' && currentSubgroup === 'Remark'" type="button" class="modern-round-btn submit-btn-animated" (click)="submitCurrentSection()" title="Submit">
341
- <i class="fas fa-paper-plane"></i>
342
- </button>
343
- </div>
 
344
 
345
- <!-- Submission Success Popup - Enhanced with Clickable Background and Navigation on Close -->
346
- <div *ngIf="showSubmitPopup" class="submit-popup-backdrop">
347
- <div class="submit-popup-modal">
348
- <div class="submit-popup-content" (click)="onSubmitPopupClose()" style="cursor:pointer;">
349
- <i class="fas fa-check-circle submit-popup-icon"></i>
350
- <h3>Submission Successful!</h3>
351
- <p>Your information has been submitted.</p>
352
- <button class="submit-popup-btn">OK</button>
353
  </div>
354
  </div>
355
- </div>
356
 
357
- <!-- Footer from provided design -->
358
- <footer>
359
- <p>© 2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
360
- </footer>
 
 
 
 
 
 
 
361
 
 
 
 
 
 
 
 
 
 
 
 
362
 
 
 
 
 
 
 
 
 
 
 
 
363
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  <i class="fas fa-save"></i>
30
  <span>{{ autoSaveStatus }}</span>
31
  </div>
32
+ <!-- Profile display (moved here so menu anchors under icon) -->
33
+ <div #profileDisplay class="profile-display" [class.open]="showProfileMenu" style="margin-left:12px; display: flex; align-items: center; cursor: pointer; color: #fff; position: relative; margin-bottom:15px;" (click)="toggleProfileMenu($event)">
34
+ <div class="profile-avatar" style="width: 2.2em; height: 2.2em; border-radius: 50%; background: linear-gradient(135deg, #1E3A8A, #2563eb); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.95em; overflow: hidden; margin-top: 17px;">
35
+ <img *ngIf="currentUser?.avatarUrl" [src]="currentUser?.avatarUrl" alt="avatar" style="width:100%;height:100%;object-fit:cover;" />
36
+ <span *ngIf="!currentUser?.avatarUrl">{{ getUserInitials() }}</span>
37
+ </div>
38
+ <!-- Profile menu anchored directly below the icon -->
39
+ <div *ngIf="showProfileMenu" class="profile-menu" [ngStyle]="profileMenuStyle" style="background:#222;color:#fff;border-radius:8px;padding:8px 10px;min-width:220px;box-shadow:0 6px 18px rgba(0,0,0,0.25);">
40
+ <div style="display:flex;align-items:center;gap:10px;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,0.06);margin-bottom:8px;">
41
+ <div style="width:42px;height:42px;border-radius:50%;background:#2b6ea6;display:flex;align-items:center;justify-content:center;font-weight:700;color:#fff;overflow:hidden;">
42
+ <img *ngIf="currentUser?.avatarUrl" [src]="currentUser?.avatarUrl" alt="avatar" style="width:100%;height:100%;object-fit:cover;" />
43
+ <span *ngIf="!currentUser?.avatarUrl">{{ getUserInitials() }}</span>
44
+ </div>
45
+ <div style="font-size:0.95em;">
46
+ <div>{{ currentUser?.name }}</div>
47
+ <div style="font-size:0.8em;opacity:0.9;">{{ currentUser?.email }}</div>
48
+ </div>
49
+ </div>
50
+ <button (click)="logout()" style="width:100%;display:flex;align-items:center;gap:8px;padding:8px 10px;border:none;border-radius:6px;background:transparent;color:#fff;cursor:pointer;">
51
+ <i class="fas fa-sign-out-alt" style="width:18px;text-align:center;"></i>
52
+ Logout
53
+ </button>
54
+ </div>
55
+ </div>
56
  <!-- View Records Button -->
57
+ <div class="view-records-btn" style="margin-left: 16px; vertical-align: middle; color: #fff; font-size: 1.5em; width: 2em; height: 2em; display: flex; align-items: center; justify-content: center; cursor: pointer; position: relative; background: linear-gradient(135deg, #1E3A8A, #2563eb);"
58
+ type="button"
59
+ (click)="goToRecords()"
60
+ (mouseenter)="showViewRecordsTooltip = true"
61
+ (mouseleave)="showViewRecordsTooltip = false">
62
  <i class="fas fa-folder-open"></i>
63
  <span *ngIf="showViewRecordsTooltip" style="position:absolute;top:100%;left:0%;transform:translateX(-50%);background:#222;color:#fff;padding:4px 12px;border-radius:6px;font-size:0.6em;white-space:nowrap;z-index:10;box-shadow:0 2px 8px rgba(0,0,0,0.12);">View Records</span>
64
  </div>
 
70
  <div class="section-navigation" [@fadeIn]>
71
  <div class="ai-neural-bg"></div>
72
  <div class="section-pills">
73
+ <button class="section-pill main-section-pill"
74
  *ngFor="let section of sectionKeys; let i = index"
75
  [class.active]="currentSection === section"
76
  [class.completed]="isSectionCompleted(section)"
 
77
  (click)="showSection(section)"
78
  [attr.tabindex]="0">
 
79
  <i [class]="sectionIcons[section]"></i>
80
  <span>{{ sections[section].title }}</span>
81
+ <i class="fas fa-chevron-right nav-chevron"
82
+ [class.rotated]="currentSection === section"></i>
83
  <div class="ai-completion-orb" *ngIf="isSectionCompleted(section)">
84
  <div class="orb-pulse"></div>
85
  <i class="fas fa-check"></i>
86
  </div>
 
87
  </button>
88
+
89
  </div>
90
  <div class="section-ai-grid"></div>
91
  </div>
 
118
  <div class="investigation-container">
119
 
120
  <!-- Universal Single Investigation Card -->
121
+ <div class="form-card primary-card" [class.is-physical-description]="isPhysicalDescriptionPage()" [@cardSlide] #formCard1>
122
  <div class="card-header compact-card-header">
123
  <div class="card-header-main">
124
  <h2 class="compact-title">
 
146
  <i class="fas fa-times"></i>
147
  </button>
148
  </div>
149
+ <!-- Small dynamic subtitle showing selection count -->
150
+ <div style="font-size:0.85rem;color:#9fc9de;margin:8px 12px 0 12px;">{{ getSelectedFieldCount() }} of {{ getTotalAvailableFieldsCount() }} fields selected</div>
151
  <div class="popup-fields-list">
152
  <div *ngFor="let field of getAvailableFields(); trackBy: trackByField" class="popup-field-row">
153
  <label class="popup-field-label">
154
  <input type="checkbox"
155
  [checked]="isFieldSelected(field)"
 
156
  (change)="toggleFieldSelection(field, $event)" />
157
  <span class="popup-field-text">{{ field }}</span>
158
  </label>
159
  </div>
160
  </div>
161
  <div class="popup-actions">
162
+ <button class="popup-action-btn clear-btn" (click)="resetFieldSelection($event)" type="button"
163
+ style="min-width:80px;padding:8px 10px;border:2px solid #e74c3c;background:transparent;color:#e74c3c;font-weight:600;border-radius:10px;display:inline-flex;align-items:center;gap:6px;">
164
+ <i class="fas fa-times-circle" style="color:#e74c3c;"></i> Clear All
165
  </button>
166
+ <button class="popup-action-btn selectall-btn" (click)="selectAllFields($event)" type="button"
167
+ style="min-width:96px;padding:8px 12px;border:2px solid #5fa8d4;background:transparent;color:#2b6ea6.font-weight:600;border-radius:10px;display:inline-flex;align-items:center;gap:6px;">
168
+ <i class="fas fa-check-double" style="color:#2b6ea6;"></i> Select All
169
  </button>
170
+ <button class="popup-action-btn save-btn" (click)="closeFieldSelector()" type="button"
171
+ style="min-width:110px;padding:8px 14px;border:none;border-radius:12px;background:linear-gradient(90deg,#38bdf80%, #23272b100%);color:#fff;font-weight:700;display:inline-flex;align-items:center;gap:8px;box-shadow:0 2px 12px rgba(56,189,248,0.55);">
172
+ <i class="fas fa-save" style="color:#fff;"></i> Save
173
  </button>
174
  </div>
175
  </div>
 
182
  <strong>{{ sections[currentSection].title }} - {{ currentSubgroup }}:</strong> {{ getSectionDescription(currentSection) }}
183
  </div>
184
 
185
+ <div class="fields-grid" [class.evidence-scene-grid]="currentSubgroup === 'Evidence & Scene'">
186
  <div *ngFor="let field of getPrimaryFields(); let i = index; trackBy: trackByField"
187
+ class="field-container" [class.photo-field]="field === 'Photo Upload' || field === 'Digital Evidence'"
188
  [class.filled]="formData[field] && formData[field].toString().trim() !== ''"
189
  [attr.data-field]="field"
190
  [@fieldAnimation]>
 
198
  [attr.aria-label]="'Info for ' + field">
199
  <i class="fas fa-info"></i>
200
  </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
+ <!-- Microphone button hidden for file fields -->
203
+ <span *ngIf="!fileFields.has(field)" style="display:inline-flex;align-items:center;position:relative;"
204
+ (mouseenter)="showMicPopupField = field"
205
+ (mouseleave)="showMicPopupField = null">
206
+ <button type="button"
207
+ (click)="toggleRecording(field)"
208
+ style="background:none;border:none;outline:none;cursor:pointer;padding:0;margin-left:6px;">
209
+ <i class="fas fa-microphone"
210
+ [ngStyle]="{
211
+ color: recordingField === field ? '#e74c3c' : '#3498db',
212
+ animation: recordingField === field ? 'micPulse1s infinite' : 'none',
213
+ fontSize: '1.2em'
214
+ }"></i>
215
+ </button>
216
+ <div *ngIf="showMicPopupField === field"
217
+ (mouseenter)="showMicPopupField = field"
218
+ (mouseleave)="showMicPopupField = null"
219
+ style="position:absolute;bottom:120%;left:0;z-index:5000;background:#fff;border:1px solid #ccc;padding:8px 12px;border-radius:6px;box-shadow:0 2px 8px rgba(0,0,0,0.12);white-space:nowrap;min-width:180px;pointer-events:auto;">
220
+ Click to record your input for "{{ field }}".<br>Click again to stop.<br>Recording is {{ recordingField === field ? 'ON' : 'OFF' }}.
221
+ </div>
222
+ </span>
223
  </label>
224
 
 
225
  <div class="input-container">
226
+ <!-- File upload vs various input types simplified to avoid complex nested templates -->
227
+
228
+ <ng-container *ngIf="fileFields.has(field)">
229
+ <div class="file-upload-block">
230
+ <div class="file-drop-zone" title="Click to upload files or drag and drop here" (dragover)="onDragOver($event)" (dragleave)="onDragLeave($event)" (drop)="onFileDrop(field, $event)" [class.drag-over]="isDragOver">
231
+ <input #fileInput type="file" (change)="onFileChange(field, $event)" multiple [accept]="getAcceptedFileTypes(field)" class="file-input" style="display:none;" />
232
+ <div class="drop-zone-content" (click)="fileInput.click()" style="cursor:pointer;">
233
+ <i [class]="getUploadZoneIcon(field)"></i>
234
+ <p style="margin:0;">Drop files here or click to browse</p>
235
+ </div>
 
 
 
 
 
236
  </div>
237
+ <div class="file-accepts" style="font-size:0.85rem;color:#9fb6c8;margin-top:6px;">
238
+ Accepts: {{ getAcceptedDisplay(field) }}
239
+ </div>
240
+ <div class="file-list" *ngIf="uploadedFiles[field]?.length">
241
+ <div *ngFor="let f of uploadedFiles[field]" class="file-chip">
242
+ <i [class]="getFileIcon(f.name)"></i>
243
+ <span [title]="f.name">{{ f.name | slice:0:15 }}{{ f.name.length >15 ? '...' : '' }}</span>
244
+ <button class="remove-file" (click)="removeFile(field, f)">
245
+ <i class="fas fa-times"></i>
246
+ </button>
247
+ </div>
248
+ </div>
249
+ <div *ngIf="uploadConfirmations[field]" class="upload-confirmation" style="color:#2b7a2b;margin-top:6px;font-size:0.95rem;">
250
+ {{ uploadConfirmations[field] }}
251
  </div>
252
  </div>
253
+ </ng-container>
254
 
255
+ <ng-container *ngIf="!fileFields.has(field)">
256
+ <!-- Date/time -->
257
+ <ng-container *ngIf="dateTimeFields.has(field)">
258
  <input type="datetime-local"
259
  class="field-input"
260
  [class.compact]="isCompactField(field)"
 
262
  (input)="onFieldChange(field)" />
263
  </ng-container>
264
 
265
+ <!-- Date only -->
266
+ <ng-container *ngIf="!dateTimeFields.has(field) && dateFields.has(field)">
267
+ <input type="date"
268
+ class="field-input"
269
+ [class.compact]="isCompactField(field)"
270
+ [(ngModel)]="formData[field]"
271
+ (input)="onFieldChange(field)" />
272
+ </ng-container>
273
+
274
+ <!-- Cascading selects for Country/State/District -->
275
+ <ng-container *ngIf="!dateTimeFields.has(field) && !dateFields.has(field) && (field === 'Country' || field === 'State' || field === 'District')">
276
+ <select class="field-input"
277
+ [class.compact]="isCompactField(field)"
278
+ [(ngModel)]="formData[field]"
279
+ (change)="onSelectChange(field, $event)"
280
+ [class.empty-select]="!formData[field]">
281
+ <option [value]="''" [selected]="!formData[field]">-- Select {{ field }} --</option>
282
+ <option *ngFor="let opt of getOptions(field)" [value]="opt">{{ opt }}</option>
283
+ </select>
284
+ </ng-container>
285
 
286
+ <!-- Select from predefined options when available -->
287
+ <ng-container *ngIf="!dateTimeFields.has(field) && !dateFields.has(field) && !(field === 'Country' || field === 'State' || field === 'District') && getOptions(field)?.length">
288
+ <select class="field-input"
289
+ [class.compact]="isCompactField(field)"
290
+ [(ngModel)]="formData[field]"
291
+ (change)="onSelectChange(field, $event)"
292
+ [class.empty-select]="!formData[field]">
293
+ <option [value]="''" [selected]="!formData[field]">-- Select {{ field }} --</option>
294
+ <option *ngFor="let opt of getOptions(field)" [value]="opt">{{ opt }}</option>
295
+ </select>
296
+ </ng-container>
297
 
298
+ <!-- Textarea for description/remark fields -->
299
+ <ng-container *ngIf="!dateTimeFields.has(field) && !dateFields.has(field) && !(field === 'Country' || field === 'State' || field === 'District') && !getOptions(field)?.length && (field.toLowerCase().includes('description') || field === 'Remark')">
300
+ <textarea
301
+ class="field-input auto-scroll-textarea"
302
+ [class.compact]="isCompactField(field)"
303
+ [(ngModel)]="formData[field]"
304
+ (input)="onFieldChange(field)"
305
+ [placeholder]="getFieldPlaceholder(field)"
306
+ [attr.maxlength]="field === 'Brief Description' ? null : getMaxLength(field)"
307
+ rows="3"></textarea>
308
 
309
+ <div *ngIf="recordingField === field" style="margin-top:4px;color:#e74c3c;font-size:0.95em;">
310
+ Recording... Speak now.
311
+ </div>
312
+ </ng-container>
313
+
314
+ <!-- Fallback regular input -->
315
+ <ng-container *ngIf="!dateTimeFields.has(field) && !dateFields.has(field) && !(field === 'Country' || field === 'State' || field === 'District') && !getOptions(field)?.length && !(field.toLowerCase().includes('description') || field === 'Remark')">
316
+ <input [type]="getInputType(field)"
317
+ class="field-input"
318
+ [class.compact]="isCompactField(field)"
319
+ [(ngModel)]="formData[field]"
320
+ (input)="onFieldChange(field)"
321
+ [placeholder]="getFieldPlaceholder(field)"
322
+ [maxlength]="getMaxLength(field)" />
323
+ </ng-container>
324
+ </ng-container>
 
 
 
 
 
 
 
 
 
 
 
 
325
  </div>
326
 
327
  <!-- Field Help Popover -->
 
331
  </div>
332
  </div>
333
  </div>
334
+ <!-- Removed modern-nav-btns from here to prevent scrolls -->
335
  </div>
 
336
  </div>
 
337
 
338
+ <!-- Floating Modern Navigation Button (fixed, no scroll) -->
339
+ <div class="modern-floating-nav-btn">
340
+ <div class="modern-nav-row">
341
+ <!-- Previous button appears when not on the very first subgroup of the first section -->
342
+ <button *ngIf="canPrevSubgroup()" type="button" class="modern-round-btn" (click)="previousSubgroup()" [disabled]="!canPrevSubgroup()" title="Previous">
343
+ <i class="fas fa-arrow-left"></i>
344
+ </button>
345
+
346
+ <!-- Submit button only on last Remark page under notes; placed next to Previous button -->
347
+ <button *ngIf="isLastSubgroup() && currentSection === 'notes' && currentSubgroup === 'Remark'" type="button" class="modern-round-btn submit-btn-animated" (click)="submitCurrentSection()" title="Submit & View Records">
348
+ <i class="fas fa-paper-plane"></i>
349
+ </button>
350
 
351
+ <!-- Next button appears on all pages except the last Remark page under notes -->
352
+ <button *ngIf="!(isLastSubgroup() && currentSection === 'notes' && currentSubgroup === 'Remark')" type="button" class="modern-round-btn next-btn-animated" (click)="nextSubgroup()" [disabled]="!canNextSubgroup()" title="Next">
353
+ <i class="fas fa-arrow-right"></i>
354
+ </button>
 
 
 
 
355
  </div>
356
  </div>
 
357
 
358
+ <!-- Submission Success Popup - Enhanced with Clickable Background and Navigation on Close -->
359
+ <div *ngIf="showSubmitPopup" class="submit-popup-backdrop">
360
+ <div class="submit-popup-modal">
361
+ <div class="submit-popup-content" (click)="onSubmitPopupClose()" style="cursor:pointer;">
362
+ <i class="fas fa-check-circle submit-popup-icon"></i>
363
+ <h3>Submission Successful!</h3>
364
+ <p>Your information has been submitted.</p>
365
+ <button class="submit-popup-btn">OK</button>
366
+ </div>
367
+ </div>
368
+ </div>
369
 
370
+ <!-- Case ID Exists Popup -->
371
+ <div *ngIf="showCaseIdExistsPopup" class="submit-popup-backdrop" (click)="closeCaseIdPopup()">
372
+ <div class="submit-popup-modal" (click)="$event.stopPropagation()">
373
+ <div class="submit-popup-content">
374
+ <i class="fas fa-exclamation-triangle submit-popup-icon" style="color:#ef4444;"></i>
375
+ <h3>Duplicate Case ID</h3>
376
+ <p>{{ caseIdExistsMessage }}</p>
377
+ <button class="submit-popup-btn" (click)="closeCaseIdPopup()">OK</button>
378
+ </div>
379
+ </div>
380
+ </div>
381
 
382
+ <!-- No-data Popup: shown when user tries to submit without entering data -->
383
+ <div *ngIf="showNoDataPopup" class="submit-popup-backdrop" (click)="closeNoDataPopup()">
384
+ <div class="submit-popup-modal" (click)="$event.stopPropagation()">
385
+ <div class="submit-popup-content">
386
+ <i class="fas fa-exclamation-circle submit-popup-icon" style="color:#f59e0b;"></i>
387
+ <h3>No Data Entered</h3>
388
+ <p>Please enter at least one field or upload a file before submitting. No case will be created.</p>
389
+ <button class="submit-popup-btn" (click)="closeNoDataPopup()">OK</button>
390
+ </div>
391
+ </div>
392
+ </div>
393
 
394
+ <!-- Add this loader overlay after the floating nav buttons but before the success popup -->
395
+ <div *ngIf="isSubmitting" class="submit-loader-backdrop">
396
+ <div class="submit-loader-modal">
397
+ <div class="submit-loader-content">
398
+ <div class="spinner-container">
399
+ <div class="spinner"></div>
400
+ <div class="spinner-ring"></div>
401
+ </div>
402
+ <h3>Submitting...</h3>
403
+ <p>Please wait while we save your case information.</p>
404
+ <div class="loader-progress">
405
+ <div class="loader-progress-bar" [style.width.%]="submitProgress"></div>
406
+ </div>
407
+ <p class="loader-subtext">Processing {{ submitProgress }}% complete</p>
408
+ </div>
409
+ </div>
410
+ </div>
411
+
412
+ <!-- Footer from provided design -->
413
+ <footer>
414
+ <p>©2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
415
+ </footer>
416
+
417
+ </div>
src/app/infopage/infopage.component.ts CHANGED
The diff for this file is too large to render. See raw diff
 
src/app/py-detect/py-detect.component.css CHANGED
@@ -1,1239 +1,1164 @@
1
- /* --- Crispy Modern Redesign --- */
2
- body, html {
3
- font-family: 'Poppins', 'Inter', Arial, sans-serif;
4
- background: #f6f8fa;
5
- color: #23272b;
6
- }
7
 
8
- .h-full {
9
- min-height: 100vh;
 
 
 
10
  }
11
 
12
- .bg-gray-900 {
13
- background-color: #1a202c;
 
 
 
 
14
  }
15
 
16
- .text-gray-200 {
17
- color: #e2e8f0;
 
 
 
 
 
 
18
  }
19
 
20
- .flex {
 
 
 
21
  display: flex;
22
- }
23
-
24
- .flex-col {
25
- flex-direction: column;
26
- }
27
-
28
- .justify-between {
29
  justify-content: space-between;
 
30
  }
31
 
32
- .items-center {
 
33
  align-items: center;
 
34
  }
35
 
36
- .p-4 {
37
- padding: 1rem;
 
 
 
 
 
38
  }
39
 
40
- .border-b {
41
- border-bottom: 1px solid #4a5568;
 
 
 
 
 
 
 
 
42
  }
43
 
44
- .bg-gray-800 {
45
- background-color: #2d3748;
46
  }
47
 
48
- .text-lg {
49
- font-size: 1.125rem;
50
- }
51
 
52
- .font-semibold {
53
- font-weight: 600;
 
 
54
  }
55
 
56
- .tracking-wide {
57
- letter-spacing: 0.05em;
58
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- .text-white {
61
- color: #fff;
62
- }
 
 
63
 
64
- .gap-4 {
65
- gap: 1rem;
66
- }
67
 
68
- .home-btn, .btn-start, .btn-stop {
69
- background: none;
70
- border: none;
71
- color: #fff;
72
- font-size: 1rem;
73
- padding: 0.5rem 1.2rem;
74
- border-radius: 6px;
75
- cursor: pointer;
76
- transition: background 0.2s;
77
- }
78
 
79
- .home-btn:hover, .btn-start:hover, .btn-stop:hover {
80
- background: #2b6cb0;
81
- }
 
82
 
83
- .max-w-4xl {
84
- max-width: 56rem;
85
- }
86
 
87
- .mx-auto {
88
- margin-left: auto;
89
- margin-right: auto;
 
90
  }
91
 
92
- .space-y-6 > * + * {
93
- margin-top: 1.5rem;
94
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
- .bg-gray-800 {
97
- background-color: #2d3748;
98
- }
 
 
99
 
100
- .border {
101
- border: 1px solid #4a5568;
102
- }
 
103
 
104
- .rounded-xl {
105
- border-radius: 1rem;
106
- }
107
 
108
- .shadow-md {
109
- box-shadow: 0 4px 24px rgba(0,0,0,0.12);
110
- }
 
 
 
111
 
112
- .text-sm {
113
- font-size: 0.875rem;
114
- }
115
 
116
- .text-gray-400 {
117
- color: #a0aec0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  }
119
 
120
- .mb-2 {
121
- margin-bottom: 0.5rem;
122
- }
 
 
 
 
 
 
 
 
123
 
124
- .text-lg {
125
- font-size: 1.125rem;
126
- }
 
 
127
 
128
- .font-semibold {
129
- font-weight: 600;
130
- }
131
 
132
- .text-gray-200 {
133
- color: #e2e8f0;
134
- }
135
 
136
- .mb-4 {
137
- margin-bottom: 1rem;
138
- }
 
 
 
139
 
140
- .flex {
141
- display: flex;
142
- }
143
 
144
- .items-center {
145
- align-items: center;
146
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
- .justify-between {
149
- justify-content: space-between;
150
- }
 
 
151
 
152
- .badge {
153
- padding: 4px 8px;
154
- border-radius: 9999px;
155
- font-size: 12px;
156
- letter-spacing: 0.5px;
157
- background: #2d3748;
158
- color: #fff;
159
- margin-right: 8px;
 
 
 
160
  }
161
 
162
- .badge.bg-blue-600 {
163
- background: #3182ce;
164
- }
165
- .badge.bg-green-600 {
166
- background: #38a169;
167
- }
168
- .badge.bg-yellow-600 {
169
- background: #ecc94b;
170
- color: #222;
171
- }
 
 
172
 
173
- .mic {
174
- width: 12px;
175
- height: 12px;
176
- border-radius: 50%;
177
- background: #4a5568;
178
- box-shadow: 0 0 0 0 rgba(31, 130, 68, 0.7);
179
- transition: box-shadow 0.25s ease;
180
- margin-right: 8px;
181
- }
182
- .mic.bg-green-600 {
183
- background: #2f855a;
184
- box-shadow: 0 0 8px 2px rgba(31, 130, 68, 0.7);
185
- }
186
 
187
- .recog {
188
- font-size: 12px;
189
- padding: 4px 10px;
190
- border-radius: 6px;
191
- border: 1px dashed #3a3f47;
192
- opacity: 0.8;
193
- background: #2d3748;
194
- color: #fff;
195
- }
196
- .recog.bg-blue-600 {
197
- border-style: solid;
198
- border-color: #3b82f6;
199
- }
200
 
201
- .text-xs {
202
- font-size: 0.75rem;
203
- }
204
- .text-gray-500 {
205
- color: #718096;
206
- }
207
- .flex-1 {
208
- flex: 1;
209
- }
210
- .mt-2 {
211
- margin-top: 0.5rem;
212
- }
213
- .space-x-6 > * + * {
214
- margin-left: 1.5rem;
215
- }
216
- .w-full {
217
- width: 100%;
218
- }
219
- .bg-gray-700 {
220
- background-color: #4a5568;
221
- }
222
- .rounded-xl {
223
- border-radius: 1rem;
224
  }
225
 
226
- /* Stylish font and button animation */
227
- .stylish-font {
228
- font-family: 'Montserrat', 'Poppins', 'Arial Black', Arial, sans-serif;
229
- letter-spacing: 2px;
230
- text-shadow: 0 0 8px #38bdf8, 0 2px 8px #22222244;
231
- transition: color 0.3s, text-shadow 0.3s;
232
  }
233
 
234
- .main-flex-layout {
 
235
  display: grid;
236
- grid-template-columns: 1fr 1.5fr;
237
  gap: 32px;
238
- margin: 32px auto 0 auto;
239
- max-width: 1700px;
240
- min-height: 80vh;
241
- background: linear-gradient(120deg, #f6f8fa 60%, #e0f2fe 100%);
242
- border-radius: 24px;
243
- box-shadow: 0 8px 32px #38bdf822;
244
- padding: 32px 32px 48px 32px;
245
  }
246
 
 
247
  .left-panel {
248
- display: flex;
249
- flex-direction: column;
250
- gap: 18px;
251
- justify-content: flex-start;
252
- align-items: flex-start;
253
- min-width: 750px;
254
- max-width: 800px;
255
- }
256
-
257
- .right-panel {
258
  display: flex;
259
  flex-direction: column;
260
  gap: 24px;
261
- align-items: stretch;
262
- min-width: 600px;
263
- max-width: 900px;
264
  }
265
 
266
- /* Card style for summary/case info */
267
- .case-meta {
268
- background: #fff;
269
- border-radius: 18px;
270
- box-shadow: 0 2px 16px #38bdf822;
271
- padding: 18px 24px;
272
- margin-bottom: 12px;
273
- display: flex;
274
- flex-direction: column;
275
- gap: 8px;
276
  position: relative;
277
- }
278
- .case-meta::before {
279
- content: '';
280
- position: absolute;
281
- top: 0; left: 0; right: 0; height: 6px;
282
- background: linear-gradient(90deg, #38bdf8 0%, #6366f1 100%);
283
- border-radius: 18px 18px 0 0;
284
  }
285
 
286
- /* Add avatar/icon for suspect/officer */
287
- .suspect-avatar {
288
- width: 54px;
289
- height: 54px;
290
- border-radius: 50%;
291
- object-fit: cover;
292
- box-shadow: 0 2px 8px #38bdf844;
293
- margin-right: 12px;
 
 
 
 
 
 
 
 
 
 
 
294
  }
295
 
296
- .case-row {
297
  display: flex;
298
  align-items: center;
299
- gap: 10px;
300
- margin-bottom: 2px;
301
- border-radius: 8px;
302
- padding: 2px 0;
303
- font-size: 1.02rem;
304
- }
305
- .case-label {
306
- color: #2563eb;
307
- font-weight: 600;
308
- min-width: 120px;
309
- }
310
- .case-value {
311
- color: #e3f6ff;
312
- font-weight: 500;
313
- }
314
- .divider-line {
315
- border-top: 1.5px solid #e5e7eb;
316
- margin: 10px 0 14px 0;
317
  }
318
 
319
- /* Button group for actions */
320
- .button-group {
 
 
 
 
321
  display: flex;
322
- gap: 12px;
323
- margin: 18px 0 0 0;
324
- justify-content: flex-end;
325
- }
326
-
327
- .summary-toggle-btn,
328
- .back-btn,
329
- .evidence-toggle-btn,
330
- .btn-start-gradient,
331
- .evidence-submit-btn,
332
- .submit-evaluate-btn {
333
- font-size: 0.92rem;
334
- padding: 0.38rem 0.85rem;
335
- border-radius: 8px;
336
- font-family: 'Montserrat', 'Poppins', 'Arial Black', Arial, sans-serif;
337
- font-weight: 700;
338
- letter-spacing: 1px;
339
- box-shadow: 0 2px 12px #38bdf844;
340
- transition: background 0.2s, color 0.2s, box-shadow 0.2s, transform 0.2s;
341
  }
342
 
343
- .summary-toggle-btn:hover,
344
- .back-btn:hover,
345
- .evidence-toggle-btn:hover,
346
- .btn-start-gradient:hover,
347
- .evidence-submit-btn:hover,
348
- .submit-evaluate-btn:hover {
349
- background: linear-gradient(90deg, #6366f1 0%, #38bdf8 100%);
350
- color: #bae6fd;
351
- box-shadow: 0 6px 32px #6366f1cc;
352
- transform: scale(1.04);
353
  }
354
 
355
- /* Animated divider */
356
- .animated-divider {
357
- width: 100%;
358
- height: 4px;
359
- background: linear-gradient(90deg, #38bdf8 0%, #6366f1 100%);
360
- border-radius: 2px;
361
- margin: 18px 0;
362
- animation: dividerPulse 2s infinite alternate;
363
- }
364
- @keyframes dividerPulse {
365
- 0% { box-shadow: 0 0 8px #38bdf844; }
366
- 100% { box-shadow: 0 0 24px #6366f144; }
367
- }
368
-
369
- /* Camera/video preview card */
370
- .video-preview {
371
- background: #374151;
372
- border: 2px dashed #64748b;
373
- border-radius: 18px;
374
- box-shadow: 0 2px 16px #23272b44;
375
- padding: 0;
376
- width: 100%;
377
- max-width: 921px;
378
- min-height: 368px;
379
- height: 368px;
380
- box-sizing: border-box;
381
  display: flex;
382
- flex-direction: column;
383
  align-items: center;
384
  justify-content: center;
 
 
385
  position: relative;
386
  overflow: hidden;
387
  }
388
- .camera-video {
389
- border-radius: 18px;
390
- border: none;
391
- background: #222;
392
- object-fit: cover;
393
- width: 100%;
394
- height: 100%;
395
- max-width: 100%;
396
- max-height: 100%;
397
- display: block;
398
- position: absolute;
399
- top: 0;
400
- left: 0;
 
 
 
 
401
  }
402
 
403
- /* Camera inactive styles */
404
- .camera-inactive-block {
405
- width: 100%;
406
- height: 100%;
407
- min-height: 368px;
 
408
  display: flex;
409
- flex-direction: column;
410
  align-items: center;
411
- justify-content: center;
412
- position: relative;
413
- }
414
- .camera-placeholder-img {
415
- width: 96px;
416
- height: 96px;
417
- object-fit: contain;
418
- margin-bottom: 18px;
419
- opacity: 0.7;
420
- }
421
- .camera-placeholder-icon {
422
- font-size: 4rem;
423
- color: #38bdf8;
424
- margin-bottom: 18px;
425
- opacity: 0.7;
426
  }
427
- .camera-inactive-title {
428
- font-size: 1.2rem;
429
- color: #bae6fd;
430
- font-weight: 700;
431
- margin-bottom: 8px;
 
 
 
432
  }
433
- .camera-inactive-sub {
434
- font-size: 1rem;
435
- color: #fff;
436
- opacity: 0.8;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  }
438
 
439
- /* Footer */
440
- footer {
441
- background: linear-gradient(to right, #011022, #01030a);
442
- color: #fff;
443
  text-align: center;
444
- padding: 10px 0px;
445
- position: fixed;
446
- bottom: 0;
447
- left: 0;
448
- width: 100%;
449
  }
450
 
451
- /* Evidence and summary sidebar styles restoration */
452
- .evidence-toggle-btn {
453
- position: fixed;
454
- top: 110px;
455
- right: 32px;
456
- z-index: 1202;
457
- background: linear-gradient(90deg, #38bdf8 0%, #6366f1 100%);
458
- color: #fff;
459
- font-family: 'Montserrat', 'Poppins', 'Arial Black', Arial, sans-serif;
460
- font-size: 1rem;
461
- font-weight: 700;
462
- border: none;
463
- border-radius: 16px;
464
- padding: 12px 28px;
465
- box-shadow: 0 2px 16px #38bdf888;
466
- cursor: pointer;
467
- transition: background 0.3s, box-shadow 0.3s, color 0.2s, transform 0.2s;
468
- animation: buttonPulse 1.2s infinite alternate;
469
- }
470
- .evidence-toggle-btn:hover {
471
- background: linear-gradient(90deg, #6366f1 0%, #38bdf8 100%);
472
- color: #bae6fd;
473
- box-shadow: 0 6px 32px #6366f1cc;
474
- transform: scale(1.04);
475
  }
476
- .summary-toggle-btn {
477
- position: fixed;
478
- top: 160px;
479
- right: 32px;
480
- z-index: 1202;
481
- background: linear-gradient(90deg, #38bdf8 0%, #6366f1 100%);
482
- color: #fff;
483
- font-family: 'Montserrat', 'Poppins', 'Arial Black', Arial, sans-serif;
484
- font-size: 1rem;
485
- font-weight: 700;
486
- border: none;
487
  border-radius: 16px;
488
- padding: 12px 28px;
489
- box-shadow: 0 2px 16px #38bdf888;
490
- cursor: pointer;
491
- transition: background 0.3s, box-shadow 0.3s, color 0.2s, transform 0.2s;
492
- animation: buttonPulse 1.2s infinite alternate;
493
  }
494
- .summary-toggle-btn:hover {
495
- background: linear-gradient(90deg, #6366f1 0%, #38bdf8 100%);
496
- color: #bae6fd;
497
- box-shadow: 0 6px 32px #6366f1cc;
498
- transform: scale(1.04);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
  }
500
- .evidence-sidebar, .summary-sidebar {
501
- position: fixed;
502
- top: 90px;
503
- right: -35vw;
504
- width: 35vw;
505
- max-width: 562px;
506
- height: 60vh;
507
- min-height: 340px;
508
- padding: 0;
509
- display: flex;
510
- flex-direction: column;
511
- justify-content: flex-start;
512
- align-items: stretch;
513
- background: rgba(30, 41, 59, 0.97);
514
- box-shadow: 0 8px 32px #38bdf844, 0 2px 16px #6366f144;
515
- border-radius: 24px 0 0 24px;
516
- z-index: 1201;
517
- transition: right 0.35s cubic-bezier(.23,1,.32,1);
518
- color: #fff;
519
- overflow-y: auto;
520
- backdrop-filter: blur(18px);
521
- -webkit-backdrop-filter: blur(18px);
522
- border: 2.5px solid #38bdf8;
523
- animation: fadeInPanel 0.7s cubic-bezier(.23,1,.32,1);
524
- }
525
- .evidence-sidebar.open, .summary-sidebar.open {
526
- right: 0;
527
- }
528
- .evidence-title-bar, .summary-title-bar {
529
  display: flex;
530
- align-items: center;
531
- justify-content: space-between;
532
- gap: 18px;
533
- padding: 24px 32px 0 32px;
534
- }
535
- .evidence-title, .summary-title {
536
- font-size: 1.3rem;
537
- font-weight: 700;
538
- color: #38bdf8;
539
- letter-spacing: 1px;
540
  }
541
- .evidence-close-btn, .summary-close-btn {
542
- background: none;
 
 
543
  border: none;
544
- font-size: 1.5rem;
545
- color: #38bdf8;
 
 
546
  cursor: pointer;
547
- }
548
- .evidence-form-card, .summary-content-card {
549
- background: rgba(44, 54, 74, 0.98);
550
- border-radius: 18px;
551
- box-shadow: 0 2px 16px #38bdf822;
552
- margin: 24px 24px 24px 24px;
553
- padding: 32px 36px 24px 36px;
554
- display: flex;
555
- flex-direction: column;
556
- align-items: flex-start;
557
- color: #fff;
558
- }
559
- .evidence-upload-form {
560
- display: flex;
561
- flex-direction: column;
562
- gap: 18px;
563
- width: 100%;
564
- }
565
- .evidence-upload-row {
566
  display: flex;
567
  align-items: center;
568
- gap: 14px;
569
- margin-bottom: 8px;
570
- color: #fff;
571
  }
572
- .evidence-upload-label {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
573
  display: flex;
574
  align-items: center;
575
  gap: 8px;
576
- font-size: 1.08rem;
577
- font-weight: 600;
578
- color: #fff;
579
- min-width: 160px;
580
- }
581
- .evidence-field-icon {
582
- font-size: 1.3rem;
583
- color: #38bdf8;
584
  }
585
- .evidence-summary-row {
 
 
 
 
 
 
 
 
 
586
  display: flex;
587
- flex-direction: column;
588
- gap: 6px;
589
- margin-top: 12px;
590
- width: 100%;
591
- }
592
- .evidence-summary-label {
593
- font-size: 1.05rem;
594
- font-weight: 600;
595
- color: #fff;
596
- margin-bottom: 2px;
597
- }
598
- .evidence-summary-textarea {
599
- border-radius: 8px;
600
- border: 1.5px solid #38bdf8;
601
- padding: 8px 12px;
602
- font-size: 1rem;
603
- background: #232b3e;
604
- color: #fff;
605
- resize: vertical;
606
- min-height: 48px;
607
- width: 100%;
608
- }
609
- .evidence-submit-btn {
610
- margin-top: 18px;
611
- background: linear-gradient(90deg, #38bdf8 0%, #6366f1 100%);
612
- color: #fff;
613
- font-family: 'Montserrat', 'Poppins', 'Arial Black', Arial, sans-serif;
614
- font-size: 1.08rem;
615
- font-weight: 700;
616
- border: none;
617
- border-radius: 12px;
618
- padding: 0.55rem 1.3rem;
619
- box-shadow: 0 2px 16px #38bdf888;
620
- cursor: pointer;
621
- transition: background 0.4s, box-shadow 0.4s, color 0.3s, transform 0.2s;
622
- letter-spacing: 1px;
623
- width: 140px;
624
- display: block;
625
- }
626
- .evidence-submit-btn:hover {
627
- background: linear-gradient(90deg, #6366f1 0%, #38bdf8 100%);
628
- color: #bae6fd;
629
- box-shadow: 0 6px 32px #6366f1cc;
630
- transform: scale(1.04);
631
  }
632
 
633
- /* Prevent page scroll */
634
- body, html {
635
- overflow: hidden !important;
636
- height: 100vh;
637
- }
638
- .main-flex-layout {
639
- max-height: calc(100vh - 60px);
640
- overflow: hidden;
641
  }
642
 
643
- /* Modern UI header styles from infopage */
644
- .site-header {
645
- background: #011329;
646
- box-shadow: 0 2px 12px #38bdf844;
647
- margin-bottom: 0;
648
- position: relative;
649
- z-index: 10;
650
- padding-bottom: 0;
651
  }
652
 
653
- .header-inner {
654
- display: flex;
655
- align-items: center;
656
- justify-content: flex-start;
657
- padding: 18px 32px 0 32px;
 
658
  position: relative;
 
659
  }
660
 
661
- .logo-cluster {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
662
  display: flex;
663
  align-items: center;
664
- gap: 18px;
665
- }
666
-
667
- .logo-img-header {
668
- width: 54px;
669
- height: 54px;
670
- border-radius: 50%;
671
- background: #fff;
672
- box-shadow: 0 2px 8px rgba(0,0,0,0.18);
673
- padding: 4px;
674
- margin-top: -6px;
675
- margin-bottom: 1vh;
676
  }
677
 
678
- .py-detect-title-header {
679
- font-size: 2.1rem;
680
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
681
- font-weight: 900;
682
- letter-spacing: 6px;
683
- color: #38bdf8;
684
  display: flex;
685
  align-items: center;
686
- gap: 2px;
687
- margin-bottom: 1.5vh;
688
  }
689
 
690
- .py-detect-title-header .py-letter.p {
691
- color: #e3f6ff;
692
- text-shadow: 0 0 6px #38bdf8;
693
  }
694
 
695
- .py-detect-title-header .py-letter.y {
696
- color: #38bdf8;
697
- text-shadow: 0 0 6px #38bdf8;
698
  }
699
 
700
- .py-detect-title-header .py-shape {
701
- color: #e3f6ff;
702
- background: #e3f6ff;
703
- text-shadow: 0 0 6px #38bdf8;
704
- box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff;
705
- border: 2px solid #23272b;
706
- width: 18px;
707
- height: 4px;
708
- display: inline-block;
709
- margin: 0 8px;
710
- border-radius: 2px;
711
  }
712
 
713
- .py-detect-title-header .py-letter.d {
714
- color: #e3f6ff;
715
- text-shadow: 0 0 6px #38bdf8;
716
  }
717
 
718
- .py-detect-title-header .py-letter.e {
719
- color: #38bdf8;
720
- text-shadow: 0 0 6px #38bdf8;
721
- }
 
 
722
 
723
- .py-detect-title-header .py-letter.t {
724
- color: #e3f6ff;
725
- text-shadow: 0 0 6px #38bdf8;
726
- }
 
727
 
728
- .py-detect-title-header .py-letter.e2 {
729
- color: #38bdf8;
730
- text-shadow: 0 0 6px #38bdf8;
731
- }
 
 
 
732
 
733
- .py-detect-title-header .py-letter.c {
734
- color: #e3f6ff;
735
- text-shadow: 0 0 6px #38bdf8;
736
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
737
 
738
- .py-detect-title-header .py-letter.t2 {
739
- color: #38bdf8;
740
- text-shadow: 0 0 6px #38bdf8;
741
  }
742
 
743
- .header-action-bar {
 
744
  display: flex;
745
- justify-content: space-between;
746
- align-items: center;
747
- width: 100%;
 
 
 
 
 
 
 
748
  position: relative;
749
- margin-top: 0;
750
- margin-bottom: 0;
751
- padding: 0 32px 18px 32px;
752
- min-height: 96px;
753
- box-sizing: border-box;
754
  }
755
- .header-action-left {
 
 
756
  display: flex;
 
757
  align-items: center;
758
- gap: 12px;
759
- height: 40px;
 
 
760
  }
761
- .header-action-right {
762
- display: flex;
763
- align-items: center;
764
- gap: 12px;
765
- height: 40px;
766
- position: absolute;
767
- top: 18px;
768
- right: 32px;
769
- z-index: 100;
770
- }
771
- .header-action-bar .small-btn,
772
- .header-action-bar .summary-toggle-btn,
773
- .header-action-bar .evidence-toggle-btn {
774
- font-size: 1.08rem;
775
- font-family: 'Montserrat', 'Poppins', 'Arial Black', Arial, sans-serif;
776
- font-weight: 700;
777
- letter-spacing: 1px;
778
- background: linear-gradient(90deg, #38bdf8 0%, #6366f1 100%);
779
- color: #fff;
780
- border: none;
781
- border-radius: 12px;
782
- padding: 0.55rem 1.3rem;
783
- box-shadow: 0 2px 16px #38bdf888;
784
- display: flex;
785
- align-items: center;
786
- gap: 8px;
787
- cursor: pointer;
788
- transition: background 0.4s, box-shadow 0.4s, color 0.3s, transform 0.2s;
789
- overflow: hidden;
790
- height: 40px;
791
- line-height: 1.2;
792
- }
793
- .header-action-bar .small-btn {
794
- margin-left: 8px;
795
- }
796
- .header-action-bar .summary-toggle-btn {
797
- margin-right: 8px;
798
- }
799
- .header-action-bar .evidence-toggle-btn {
800
- margin-right: 8px;
801
- }
802
- .header-action-bar .small-btn:hover,
803
- .header-action-bar .summary-toggle-btn:hover,
804
- .header-action-bar .evidence-toggle-btn:hover {
805
- background: linear-gradient(90deg, #6366f1 0%, #38bdf8 100%);
806
- color: #bae6fd;
807
- box-shadow: 0 6px 32px #6366f1cc;
808
- transform: scale(1.04);
809
- }
810
- .header-divider-line {
811
- width: calc(100% - 64px); /* 32px gap on both sides */
812
- height: 2px;
813
- background: linear-gradient(90deg, #38bdf8 0%, #6366f1 100%);
814
- border-radius: 2px;
815
- margin: 0 auto 18px auto;
816
- box-shadow: 0 2px 8px #38bdf844;
817
- position: relative;
818
- top: -19px;
819
  }
820
 
821
- .blur-bg {
822
- position: fixed;
823
- top: 0;
824
- left: 0;
825
- width: 100vw;
826
- height: 100vh;
827
- z-index: 1200;
828
- background: rgba(30,41,59,0.18);
829
- backdrop-filter: blur(8px);
830
- -webkit-backdrop-filter: blur(8px);
831
- transition: opacity 0.3s;
832
- opacity: 1;
833
- pointer-events: auto;
834
  }
835
- .blur-bg.hide {
836
- opacity: 0;
837
- pointer-events: none;
 
 
 
838
  }
839
 
840
- .tts-question-card {
841
- background: linear-gradient(120deg, #f6f8fa 60%, #bae6fd 100%);
842
- border-radius: 18px;
843
- box-shadow: 0 8px 32px #38bdf822, 0 2px 16px #6366f144;
844
- padding: 32px 36px 24px 36px;
845
- margin: 0 0 18px 0;
846
- display: flex;
847
- flex-direction: column;
848
- align-items: flex-start;
849
  position: relative;
850
- animation: fadeInUp 0.7s cubic-bezier(.39,.58,.57,1);
851
- min-height: 180px;
852
- max-width: 700px;
853
- width: 100%;
854
- z-index: 1;
855
- }
856
- .tts-question-title {
857
- font-size: 1.25rem;
858
- font-weight: 700;
859
- color: #2563eb;
860
- margin-bottom: 10px;
861
- letter-spacing: 1px;
862
- text-shadow: 0 2px 12px #38bdf844;
863
  }
864
- .tts-question-text {
865
- font-size: 1.12rem;
866
- color: #23272b;
867
- font-weight: 600;
868
- margin-bottom: 8px;
869
- text-shadow: 0 1px 8px #bae6fd44;
870
  }
871
- .tts-status-row {
872
- font-size: 1.05rem;
873
- color: #6366f1;
 
 
 
 
 
 
 
874
  font-weight: 500;
875
- margin-bottom: 4px;
876
- background: rgba(56,189,248,0.08);
877
- border-radius: 8px;
878
- padding: 6px 14px;
879
- box-shadow: 0 2px 8px #38bdf822;
880
- display: inline-block;
881
- animation: statusPulse 1.2s infinite alternate;
882
  }
883
- @keyframes statusPulse {
884
- 0% { box-shadow: 0 2px 8px #38bdf822; }
885
- 100% { box-shadow: 0 4px 16px #6366f144; }
 
 
 
 
 
886
  }
887
 
888
- /* Transcript Panel */
889
- .transcript-panel {
890
- background: #fff;
891
- border-radius: 14px;
892
- box-shadow: 0 2px 12px #6366f122;
893
- padding: 18px 18px 18px 18px;
894
- width: 100%;
895
- max-width: 889px;
896
- overflow: hidden;
897
  display: flex;
898
- flex-direction: column;
 
 
899
  }
900
 
901
- .transcript-title {
902
- font-weight: 700;
903
- color: #6366f1;
904
- margin-bottom: 8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
905
  }
906
 
907
- .transcript-scrollable {
 
 
 
 
908
  overflow-y: auto;
909
- max-height: 180px;
910
- padding-right: 8px;
911
  }
912
 
913
- .transcript-line {
914
- font-size: 1.05rem;
915
- color: #23272b;
916
- margin-bottom: 4px;
917
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
918
 
 
 
 
919
 
 
 
 
 
 
 
 
 
 
920
 
921
- /* Evidence Panel Sidebar Styles */
922
- .evidence-toggle-btn {
923
- display: block;
924
- position: relative;
925
- margin: 2px 0 0 auto;
926
- background: linear-gradient(90deg, #38bdf8 0%, #6366f1 100%);
927
- color: #fff;
928
- font-family: 'Poppins', 'Inter', Arial, sans-serif;
929
- font-size: 1rem;
930
- font-weight: 600;
931
  border: none;
932
- border-radius: 8px 8px 0 0;
933
- padding: 10px 22px;
934
- box-shadow: 0 2px 12px #38bdf844;
 
935
  cursor: pointer;
936
- transition: background 0.2s, color 0.2s, box-shadow 0.2s;
937
- z-index: 1200;
 
 
 
 
 
938
  }
939
 
940
- .evidence-toggle-btn:hover {
941
- background: linear-gradient(90deg, #6366f1 0%, #38bdf8 100%);
942
- color: #bae6fd;
943
  }
944
 
945
- .evidence-sidebar {
 
 
 
 
 
 
946
  position: fixed;
947
- top: 90px;
948
- right: -35vw;
949
- width: 35vw;
950
- max-width: 562px;
951
- height: 60vh;
952
- min-height: 340px;
953
- padding: 0;
954
- gap: 0;
955
- display: flex;
956
- flex-direction: column;
957
- justify-content: flex-start;
958
- align-items: stretch;
959
- background: rgba(30, 41, 59, 0.97);
960
- box-shadow: 0 8px 32px #38bdf844, 0 2px 16px #6366f144;
961
- border-radius: 24px 0 0 24px;
962
- z-index: 1201;
963
- transition: right 0.35s cubic-bezier(.23,1,.32,1);
964
- color: #fff;
965
  overflow-y: auto;
966
- backdrop-filter: blur(18px);
967
- -webkit-backdrop-filter: blur(18px);
968
- border: 2.5px solid #38bdf8;
969
- animation: fadeInPanel 0.7s cubic-bezier(.23,1,.32,1);
970
  }
971
 
972
- .evidence-sidebar.open {
 
973
  right: 0;
974
  }
975
 
976
- .evidence-title-bar {
 
 
977
  display: flex;
978
  align-items: center;
979
  justify-content: space-between;
980
- gap: 18px;
981
- padding: 24px 32px 0 32px;
982
- }
983
-
984
- .evidence-title {
985
- font-size: 1.3rem;
986
- font-weight: 700;
987
- color: #38bdf8;
988
- letter-spacing: 1px;
989
- }
990
-
991
- .evidence-form-card {
992
- background: rgba(44, 54, 74, 0.98);
993
- border-radius: 18px;
994
- box-shadow: 0 2px 16px #38bdf822;
995
- margin: 24px 24px 24px 24px;
996
- padding: 24px 28px 18px 28px;
997
- display: flex;
998
- flex-direction: column;
999
- align-items: stretch;
1000
  }
1001
 
1002
- .evidence-upload-form {
1003
- display: flex;
1004
- flex-direction: column;
1005
- gap: 18px;
1006
- }
1007
-
1008
- .evidence-upload-row {
1009
  display: flex;
1010
  align-items: center;
1011
- gap: 14px;
1012
- margin-bottom: 0;
1013
  }
1014
 
1015
- .evidence-upload-label {
 
 
 
 
 
 
 
 
1016
  display: flex;
1017
  align-items: center;
1018
- gap: 8px;
1019
- font-size: 1.08rem;
1020
- font-weight: 600;
1021
- color: #6366f1;
1022
- min-width: 160px;
1023
  }
1024
 
1025
- .evidence-field-icon {
1026
- font-size: 1.3rem;
1027
- color: #38bdf8;
1028
- }
1029
 
1030
- .evidence-divider {
1031
- border: none;
1032
- border-top: 1.5px solid #38bdf8;
1033
- margin: 12px 0 8px 0;
1034
  }
1035
 
1036
- .evidence-summary-row {
1037
- display: flex;
1038
- flex-direction: column;
1039
- gap: 6px;
 
 
 
 
 
 
 
 
 
1040
  }
1041
 
1042
- .evidence-summary-label {
1043
- font-size: 1.05rem;
1044
- font-weight: 600;
1045
- color: #38bdf8;
1046
- margin-bottom: 2px;
1047
- }
1048
 
1049
- .evidence-summary-textarea {
1050
- border-radius: 8px;
1051
- border: 1.5px solid #6366f1;
1052
- padding: 8px 12px;
1053
- font-size: 1rem;
1054
- background: #232b3e;
1055
- color: #fff;
1056
- resize: vertical;
1057
- min-height: 48px;
1058
  }
1059
 
1060
- .evidence-submit-btn {
1061
- margin-top: 12px;
1062
- background: linear-gradient(90deg, #2563eb 0%, #38bdf8 100%);
1063
- color: #fff;
1064
- font-family: 'Poppins', 'Inter', Arial, sans-serif;
1065
- font-size: 1rem;
1066
- font-weight: 600;
1067
- border: none;
1068
- border-radius: 8px;
1069
- padding: 10px 22px;
1070
- box-shadow: 0 2px 12px #38bdf844;
1071
- cursor: pointer;
1072
- transition: background 0.2s, color 0.2s, box-shadow 0.2s;
1073
- letter-spacing: 1px;
1074
- }
1075
 
1076
- .evidence-submit-btn:hover {
1077
- background: linear-gradient(90deg, #38bdf8 0%, #2563eb 100%);
1078
- color: #bae6fd;
 
 
1079
  }
1080
 
1081
- .evidence-upload-form input[type="file"] {
1082
- background: #232b3e;
1083
- color: #fff;
1084
- border-radius: 6px;
1085
- border: 1px solid #6366f1;
1086
- padding: 4px 8px;
1087
- font-size: 0.98rem;
1088
  }
1089
 
1090
- .evidence-upload-icon {
1091
- font-size: 1.15em;
1092
- margin-right: 6px;
1093
- vertical-align: middle;
1094
- }
1095
 
1096
- .back-btn {
1097
- font-size: 1.08rem;
1098
- font-family: 'Montserrat', 'Poppins', 'Arial Black', Arial, sans-serif;
1099
- font-weight: 700;
1100
- letter-spacing: 1px;
1101
- background: linear-gradient(90deg, #38bdf8 0%, #6366f1 100%);
1102
- color: #fff;
1103
- border: none;
1104
- border-radius: 12px;
1105
- padding: 0.55rem 1.3rem;
1106
- box-shadow: 0 2px 16px #38bdf888;
1107
- display: flex;
1108
- align-items: center;
1109
- gap: 8px;
1110
- cursor: pointer;
1111
- transition: background 0.4s, box-shadow 0.4s, color 0.3s, transform 0.2s;
1112
- overflow: hidden;
1113
  }
1114
 
1115
- .recording-status-bar {
1116
- display: flex;
1117
- align-items: center;
1118
- gap: 12px;
1119
- justify-content: flex-start;
1120
- background: linear-gradient(90deg, #e0f2fe 0%, #bae6fd 100%);
1121
- color: #23272b;
1122
- font-size: 1.08rem;
1123
- font-weight: 700;
1124
- border-radius: 12px;
1125
- box-shadow: 0 2px 8px #38bdf822;
1126
- padding: 8px 22px;
1127
- margin: 18px 0 0 0;
1128
- min-width: 220px;
1129
- max-width: 340px;
1130
- position: relative;
1131
- z-index: 2;
1132
- border: none;
1133
- outline: none;
1134
- cursor: default;
1135
- transition: none;
1136
- }
1137
- .recording-status-bar .status-icon {
1138
- font-size: 1.3em;
1139
- margin-right: 6px;
1140
- }
1141
- .recording-status-bar .status-timer {
1142
- font-size: 1.08em;
1143
- font-weight: 600;
1144
- color: #2563eb;
1145
- }
1146
 
1147
- .icon-btn {
1148
- background: linear-gradient(90deg, #38bdf8 0%, #6366f1 100%);
1149
- color: #fff;
1150
- border: none;
1151
- border-radius: 50%;
1152
- width: 44px;
1153
- height: 44px;
1154
- display: flex;
1155
- align-items: center;
1156
- justify-content: center;
1157
- font-size: 1.5rem;
1158
- box-shadow: 0 2px 12px #38bdf844;
1159
- margin-bottom: 0;
1160
- margin-top: 0;
1161
- margin-right: 8px;
1162
- cursor: pointer;
1163
- transition: background 0.3s, box-shadow 0.3s, color 0.2s, transform 0.2s;
1164
- }
1165
- .icon-btn:hover {
1166
- background: linear-gradient(90deg, #6366f1 0%, #38bdf8 100%);
1167
- color: #bae6fd;
1168
- box-shadow: 0 6px 32px #6366f1cc;
1169
- transform: scale(1.08);
1170
  }
1171
 
1172
- .header-actions-right-group {
1173
- position: absolute;
1174
- top: 18px;
1175
- right: 32px;
1176
- z-index: 100;
1177
- display: flex;
1178
- flex-direction: column;
1179
- align-items: flex-end;
1180
- gap: 10px;
1181
- }
1182
- .header-actions-icons {
1183
- display: flex;
1184
- flex-direction: row;
1185
- align-items: center;
1186
- gap: 10px;
1187
- margin-top: 37px;
1188
- }
1189
 
1190
- .guidance-tooltip {
1191
- display: flex;
1192
- align-items: center;
1193
- gap: 8px;
1194
- background: linear-gradient(90deg, #e0f2fe 0%, #bae6fd 100%);
1195
- color: #2563eb;
1196
- font-size: 1.08rem;
1197
- font-weight: 600;
1198
- border-radius: 12px;
1199
- box-shadow: 0 2px 12px #38bdf844;
1200
- padding: 6px 14px;
1201
- margin-top: 8px;
1202
- margin-left: 0;
1203
- position: absolute;
1204
- left: 32px;
1205
- top: 60px;
1206
- z-index: 101;
1207
- animation: fadeInTooltip 0.7s cubic-bezier(.39,.58,.57,1);
1208
- white-space: nowrap;
1209
- }
1210
- @keyframes fadeInTooltip {
1211
- 0% { opacity: 0; transform: translateY(-12px); }
1212
- 100% { opacity: 1; transform: translateY(0); }
1213
  }
1214
 
1215
- .recording-indicator {
1216
- position: absolute;
1217
- top: 18px;
1218
- left: 50%;
1219
- transform: translateX(-50%);
1220
- background: #ef4444;
1221
- color: #fff;
1222
- font-size: 1.15rem;
1223
- font-weight: 700;
1224
- padding: 8px 22px;
1225
- border-radius: 16px;
1226
- max-width: 1000px;
1227
- z-index: 10;
1228
- letter-spacing: 1px;
1229
- display: flex;
1230
- align-items: center;
1231
- gap: 8px;
1232
- animation: fadeInRecording 0.5s cubic-bezier(.39,.58,.57,1);
1233
- }
1234
- @keyframes fadeInRecording {
1235
- 0% { opacity: 0; transform: translateX(-50%) scale(0.8); }
1236
- 100% { opacity: 1; transform: translateX(-50%) scale(1); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1237
  }
1238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1239
 
 
 
 
 
 
 
 
1
 
2
+ /* ===== GLOBAL RESET & BASE ===== */
3
+ * {
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
  }
8
 
9
+ body, html {
10
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+ background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
12
+ color: #1e293b;
13
+ min-height: 100vh;
14
+ overflow-x: hidden;
15
  }
16
 
17
+ /* ===== HEADER ===== */
18
+ .site-header {
19
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
20
+ padding: 0 0 0 0;
21
+ position: sticky;
22
+ top: 0;
23
+ z-index: 1000;
24
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
25
  }
26
 
27
+ .header-inner {
28
+ max-width: 1400px;
29
+ margin: 0 auto;
30
+ padding: 0 32px;
31
  display: flex;
32
+ align-items: center;
 
 
 
 
 
 
33
  justify-content: space-between;
34
+ height: 70px;
35
  }
36
 
37
+ .logo-cluster {
38
+ display: flex;
39
  align-items: center;
40
+ gap: 16px;
41
  }
42
 
43
+ .logo-img-header {
44
+ width: 42px;
45
+ height: 42px;
46
+ background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%);
47
+ border-radius: 12px;
48
+ padding: 8px;
49
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
50
  }
51
 
52
+ .py-detect-title-header {
53
+ font-size: 22px;
54
+ font-weight: 700;
55
+ letter-spacing: 2px;
56
+ background: linear-gradient(135deg, #ffffff 0%, #cbd5e1 100%);
57
+ -webkit-background-clip: text;
58
+ background-clip: text;
59
+ -webkit-text-fill-color: transparent;
60
+ display: flex;
61
+ gap: 6px;
62
  }
63
 
64
+ .py-letter {
65
+ transition: transform 0.2s ease;
66
  }
67
 
68
+ .py-letter:hover {
69
+ transform: translateY(-2px);
70
+ }
71
 
72
+ .header-actions-right-group {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 16px;
76
  }
77
 
78
+ /* ===== BACK BUTTON ===== */
79
+ .back-btn {
80
+ background: linear-gradient(135deg, #7c3aed 0%, #8b5cf6 100%);
81
+ color: white;
82
+ border: none;
83
+ border-radius: 12px;
84
+ padding: 10px 20px;
85
+ font-size: 14px;
86
+ font-weight: 600;
87
+ cursor: pointer;
88
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 10px;
92
+ position: relative;
93
+ overflow: hidden;
94
+ box-shadow: 0 4px 15px rgba(124, 58, 237, 0.3);
95
+ }
96
+
97
+ .back-btn::before {
98
+ content: '';
99
+ position: absolute;
100
+ top: 0;
101
+ left: -100%;
102
+ width: 100%;
103
+ height: 100%;
104
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
105
+ transition: left 0.6s ease;
106
+ }
107
 
108
+ .back-btn:hover {
109
+ transform: translateY(-2px) scale(1.03);
110
+ box-shadow: 0 8px 25px rgba(124, 58, 237, 0.4);
111
+ background: linear-gradient(135deg, #6d28d9 0%, #7c3aed 100%);
112
+ }
113
 
114
+ .back-btn:hover::before {
115
+ left: 100%;
116
+ }
117
 
118
+ .back-btn:active {
119
+ transform: translateY(0) scale(0.98);
120
+ }
 
 
 
 
 
 
 
121
 
122
+ .back-btn i {
123
+ font-size: 16px;
124
+ transition: transform 0.3s ease;
125
+ }
126
 
127
+ .back-btn:hover i {
128
+ transform: translateX(-3px);
129
+ }
130
 
131
+ /* ===== HEADER ACTION ICONS CONTAINER ===== */
132
+ .header-actions-icons {
133
+ display: flex;
134
+ gap: 12px;
135
  }
136
 
137
+ /* ===== EVIDENCE UPLOAD BUTTON ===== */
138
+ .evidence-btn {
139
+ width: 44px;
140
+ height: 44px;
141
+ border-radius: 12px;
142
+ background: linear-gradient(135deg, #059669 0%, #10b981 100%);
143
+ color: white;
144
+ border: none;
145
+ display: flex;
146
+ align-items: center;
147
+ justify-content: center;
148
+ cursor: pointer;
149
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
150
+ position: relative;
151
+ overflow: hidden;
152
+ box-shadow: 0 4px 15px rgba(5, 150, 105, 0.3);
153
+ }
154
+
155
+ .evidence-btn::after {
156
+ content: '';
157
+ position: absolute;
158
+ top: 50%;
159
+ left: 50%;
160
+ width: 0;
161
+ height: 0;
162
+ border-radius: 50%;
163
+ background: rgba(255, 255, 255, 0.2);
164
+ transform: translate(-50%, -50%);
165
+ transition: width 0.4s ease, height 0.4s ease;
166
+ }
167
 
168
+ .evidence-btn:hover {
169
+ transform: translateY(-2px) rotate(5deg);
170
+ box-shadow: 0 8px 25px rgba(5, 150, 105, 0.4);
171
+ background: linear-gradient(135deg, #047857 0%, #0d9669 100%);
172
+ }
173
 
174
+ .evidence-btn:hover::after {
175
+ width: 60px;
176
+ height: 60px;
177
+ }
178
 
179
+ .evidence-btn:active {
180
+ transform: translateY(0) rotate(0);
181
+ }
182
 
183
+ .evidence-btn i {
184
+ font-size: 18px;
185
+ position: relative;
186
+ z-index: 1;
187
+ transition: transform 0.3s ease;
188
+ }
189
 
190
+ .evidence-btn:hover i {
191
+ transform: scale(1.1);
192
+ }
193
 
194
+ /* ===== SUMMARY BUTTON ===== */
195
+ .summary-btn {
196
+ width: 44px;
197
+ height: 44px;
198
+ border-radius: 12px;
199
+ background: linear-gradient(135deg, #d97706 0%, #f59e0b 100%);
200
+ color: white;
201
+ border: none;
202
+ display: flex;
203
+ align-items: center;
204
+ justify-content: center;
205
+ cursor: pointer;
206
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
207
+ position: relative;
208
+ overflow: hidden;
209
+ box-shadow: 0 4px 15px rgba(217, 119, 6, 0.3);
210
  }
211
 
212
+ .summary-btn::before {
213
+ content: '';
214
+ position: absolute;
215
+ top: 0;
216
+ left: 0;
217
+ right: 0;
218
+ bottom: 0;
219
+ background: linear-gradient(45deg, transparent 40%, rgba(255, 255, 255, 0.1) 50%, transparent 60%);
220
+ transform: translateX(-100%);
221
+ transition: transform 0.6s ease;
222
+ }
223
 
224
+ .summary-btn:hover {
225
+ transform: translateY(-2px) rotate(-5deg);
226
+ box-shadow: 0 8px 25px rgba(217, 119, 6, 0.4);
227
+ background: linear-gradient(135deg, #b45309 0%, #d97706 100%);
228
+ }
229
 
230
+ .summary-btn:hover::before {
231
+ transform: translateX(100%);
232
+ }
233
 
234
+ .summary-btn:active {
235
+ transform: translateY(0) rotate(0);
236
+ }
237
 
238
+ .summary-btn i {
239
+ font-size: 18px;
240
+ position: relative;
241
+ z-index: 1;
242
+ transition: transform 0.3s ease;
243
+ }
244
 
245
+ .summary-btn:hover i {
246
+ transform: scale(1.1);
247
+ }
248
 
249
+ /* ===== TOOLTIP FOR ICONS ===== */
250
+ .evidence-btn::after,
251
+ .summary-btn::after {
252
+ content: attr(title);
253
+ position: absolute;
254
+ bottom: -40px;
255
+ left: 50%;
256
+ transform: translateX(-50%);
257
+ background: rgba(0, 0, 0, 0.8);
258
+ color: white;
259
+ padding: 6px 12px;
260
+ border-radius: 6px;
261
+ font-size: 12px;
262
+ font-weight: 500;
263
+ white-space: nowrap;
264
+ opacity: 0;
265
+ pointer-events: none;
266
+ transition: opacity 0.3s ease, transform 0.3s ease;
267
+ z-index: 1001;
268
+ }
269
 
270
+ .evidence-btn:hover::after,
271
+ .summary-btn:hover::after {
272
+ opacity: 1;
273
+ transform: translateX(-50%) translateY(-5px);
274
+ }
275
 
276
+ /* ===== MAIN CONTENT CARD ===== */
277
+ .main-content-card {
278
+ max-width: 1400px;
279
+ margin: 32px auto;
280
+ background: white;
281
+ border-radius: 24px;
282
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
283
+ border: 1px solid rgba(255, 255, 255, 0.2);
284
+ overflow: hidden;
285
+ position: relative;
286
+ min-height: calc(100vh - 200px);
287
  }
288
 
289
+ /* Decorative elements for the card */
290
+ .main-content-card::before {
291
+ content: '';
292
+ position: absolute;
293
+ top: 0;
294
+ left: 0;
295
+ right: 0;
296
+ height: 6px;
297
+ background: linear-gradient(90deg, #2563eb, #3b82f6, #8b5cf6);
298
+ background-size: 300% 100%;
299
+ animation: gradient-shift 4s ease infinite;
300
+ }
301
 
302
+ .main-content-card::after {
303
+ content: '';
304
+ position: absolute;
305
+ top: 6px;
306
+ left: 20px;
307
+ right: 20px;
308
+ height: 1px;
309
+ background: linear-gradient(90deg, transparent, rgba(37, 99, 235, 0.2), transparent);
310
+ }
 
 
 
 
311
 
312
+ @keyframes gradient-shift {
313
+ 0%, 100% {
314
+ background-position: 0% 50%;
315
+ }
 
 
 
 
 
 
 
 
 
316
 
317
+ 50% {
318
+ background-position: 100% 50%;
319
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  }
321
 
322
+ .card-inner {
323
+ padding: 32px;
324
+ height: 100%;
 
 
 
325
  }
326
 
327
+ /* ===== CONTENT LAYOUT ===== */
328
+ .content-grid {
329
  display: grid;
330
+ grid-template-columns: 1fr 1fr;
331
  gap: 32px;
332
+ height: 100%;
 
 
 
 
 
 
333
  }
334
 
335
+ /* ===== LEFT PANEL ===== */
336
  .left-panel {
 
 
 
 
 
 
 
 
 
 
337
  display: flex;
338
  flex-direction: column;
339
  gap: 24px;
 
 
 
340
  }
341
 
342
+ /* Guidance Banner */
343
+ .guidance-banner {
344
+ background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%);
345
+ border-radius: 16px;
346
+ padding: 20px 24px;
347
+ color: white;
348
+ box-shadow: 0 8px 32px rgba(37, 99, 235, 0.2);
349
+ animation: slideIn 0.5s ease;
 
 
350
  position: relative;
351
+ overflow: hidden;
 
 
 
 
 
 
352
  }
353
 
354
+ .guidance-banner::before {
355
+ content: '';
356
+ position: absolute;
357
+ top: 0;
358
+ left: 0;
359
+ right: 0;
360
+ bottom: 0;
361
+ background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.1) 50%, transparent 70%);
362
+ animation: shimmer 2s infinite;
363
+ }
364
+
365
+ @keyframes shimmer {
366
+ 0% {
367
+ transform: translateX(-100%);
368
+ }
369
+
370
+ 100% {
371
+ transform: translateX(100%);
372
+ }
373
  }
374
 
375
+ .guidance-content {
376
  display: flex;
377
  align-items: center;
378
+ gap: 16px;
379
+ position: relative;
380
+ z-index: 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  }
382
 
383
+ .guidance-icon {
384
+ font-size: 28px;
385
+ background: rgba(255, 255, 255, 0.2);
386
+ width: 56px;
387
+ height: 56px;
388
+ border-radius: 12px;
389
  display: flex;
390
+ align-items: center;
391
+ justify-content: center;
392
+ backdrop-filter: blur(10px);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
  }
394
 
395
+ .guidance-text {
396
+ flex: 1;
 
 
 
 
 
 
 
 
397
  }
398
 
399
+ .guidance-text h3 {
400
+ font-size: 16px;
401
+ font-weight: 600;
402
+ margin-bottom: 4px;
403
+ }
404
+
405
+ .guidance-text p {
406
+ font-size: 14px;
407
+ opacity: 0.9;
408
+ }
409
+
410
+ /* Start Investigation Button */
411
+ .start-investigation-btn {
412
+ background: linear-gradient(135deg, #16a34a 0%, #22c55e 100%);
413
+ color: white;
414
+ border: none;
415
+ border-radius: 14px;
416
+ padding: 18px 32px;
417
+ font-size: 16px;
418
+ font-weight: 600;
419
+ cursor: pointer;
420
+ transition: all 0.3s ease;
 
 
 
 
421
  display: flex;
 
422
  align-items: center;
423
  justify-content: center;
424
+ gap: 12px;
425
+ box-shadow: 0 8px 24px rgba(22, 163, 74, 0.3);
426
  position: relative;
427
  overflow: hidden;
428
  }
429
+
430
+ .start-investigation-btn:hover {
431
+ transform: translateY(-3px);
432
+ box-shadow: 0 12px 32px rgba(22, 163, 74, 0.4);
433
+ }
434
+
435
+ .start-investigation-btn:active {
436
+ transform: translateY(-1px);
437
+ }
438
+
439
+ /* Question Section */
440
+ .question-section {
441
+ background: #f8fafc;
442
+ border-radius: 16px;
443
+ padding: 24px;
444
+ border: 1px solid #e2e8f0;
445
+ transition: all 0.3s ease;
446
  }
447
 
448
+ .question-section:hover {
449
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
450
+ transform: translateY(-2px);
451
+ }
452
+
453
+ .question-header {
454
  display: flex;
 
455
  align-items: center;
456
+ gap: 12px;
457
+ margin-bottom: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
458
  }
459
+
460
+ .question-badge {
461
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
462
+ color: white;
463
+ padding: 6px 16px;
464
+ border-radius: 20px;
465
+ font-size: 14px;
466
+ font-weight: 600;
467
  }
468
+
469
+ .question-content {
470
+ background: white;
471
+ border-radius: 12px;
472
+ padding: 20px;
473
+ border: 1px solid #e2e8f0;
474
+ min-height: 120px;
475
+ }
476
+
477
+ .question-content h3 {
478
+ font-size: 15px;
479
+ font-weight: 600;
480
+ color: #64748b;
481
+ margin-bottom: 12px;
482
+ text-transform: uppercase;
483
+ letter-spacing: 0.5px;
484
+ }
485
+
486
+ .question-text {
487
+ font-size: 16px;
488
+ line-height: 1.6;
489
+ color: #1e293b;
490
+ font-weight: 500;
491
  }
492
 
493
+ .no-question {
494
+ color: #94a3b8;
495
+ font-style: italic;
496
+ padding: 20px 0;
497
  text-align: center;
 
 
 
 
 
498
  }
499
 
500
+ .status-indicator {
501
+ margin-top: 16px;
502
+ padding: 12px;
503
+ background: #f0f9ff;
504
+ border-radius: 8px;
505
+ border-left: 4px solid #3b82f6;
506
+ font-size: 14px;
507
+ color: #1e40af;
508
+ display: flex;
509
+ align-items: center;
510
+ gap: 8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  }
512
+
513
+ /* Answer Section */
514
+ .answer-section {
515
+ background: #f8fafc;
 
 
 
 
 
 
 
516
  border-radius: 16px;
517
+ padding: 24px;
518
+ border: 1px solid #e2e8f0;
519
+ transition: all 0.3s ease;
 
 
520
  }
521
+
522
+ .answer-section:hover {
523
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
524
+ transform: translateY(-2px);
525
+ }
526
+
527
+ .answer-section h3 {
528
+ font-size: 18px;
529
+ font-weight: 600;
530
+ color: #1e293b;
531
+ margin-bottom: 20px;
532
+ display: flex;
533
+ align-items: center;
534
+ gap: 10px;
535
+ }
536
+
537
+ .answer-section h3::before {
538
+ content: '';
539
+ width: 4px;
540
+ height: 20px;
541
+ background: #2563eb;
542
+ border-radius: 2px;
543
+ }
544
+
545
+ .answer-input {
546
+ width: 100%;
547
+ min-height: 150px;
548
+ padding: 16px;
549
+ border: 2px solid #e2e8f0;
550
+ border-radius: 12px;
551
+ font-family: inherit;
552
+ font-size: 15px;
553
+ line-height: 1.6;
554
+ resize: vertical;
555
+ transition: all 0.3s ease;
556
+ margin-bottom: 20px;
557
+ background: white;
558
  }
559
+
560
+ .answer-input:focus {
561
+ outline: none;
562
+ border-color: #3b82f6;
563
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
564
+ }
565
+
566
+ .answer-input::placeholder {
567
+ color: #94a3b8;
568
+ }
569
+
570
+ .answer-actions {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
  display: flex;
572
+ gap: 12px;
573
+ margin-bottom: 20px;
 
 
 
 
 
 
 
 
574
  }
575
+
576
+ .btn-primary {
577
+ background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%);
578
+ color: white;
579
  border: none;
580
+ border-radius: 10px;
581
+ padding: 12px 24px;
582
+ font-size: 14px;
583
+ font-weight: 600;
584
  cursor: pointer;
585
+ transition: all 0.3s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
586
  display: flex;
587
  align-items: center;
588
+ gap: 8px;
589
+ flex: 1;
590
+ justify-content: center;
591
  }
592
+
593
+ .btn-primary:hover {
594
+ transform: translateY(-2px);
595
+ box-shadow: 0 8px 20px rgba(37, 99, 235, 0.3);
596
+ }
597
+
598
+ .btn-secondary {
599
+ background: white;
600
+ color: #2563eb;
601
+ border: 2px solid #2563eb;
602
+ border-radius: 10px;
603
+ padding: 12px 24px;
604
+ font-size: 14px;
605
+ font-weight: 600;
606
+ cursor: pointer;
607
+ transition: all 0.3s ease;
608
  display: flex;
609
  align-items: center;
610
  gap: 8px;
611
+ flex: 1;
612
+ justify-content: center;
 
 
 
 
 
 
613
  }
614
+
615
+ .btn-secondary:hover {
616
+ background: #f0f9ff;
617
+ transform: translateY(-2px);
618
+ }
619
+
620
+ .recording-status {
621
+ background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
622
+ border-radius: 10px;
623
+ padding: 12px 16px;
624
  display: flex;
625
+ align-items: center;
626
+ gap: 12px;
627
+ animation: pulse 2s infinite;
628
+ border: 1px solid #fbbf24;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
629
  }
630
 
631
+ .recording-dot {
632
+ width: 12px;
633
+ height: 12px;
634
+ background: #dc2626;
635
+ border-radius: 50%;
636
+ animation: blink 1.5s infinite;
 
 
637
  }
638
 
639
+ /* Results Section */
640
+ .results-section {
641
+ display: grid;
642
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
643
+ gap: 16px;
644
+ margin-top: 24px;
 
 
645
  }
646
 
647
+ .result-card {
648
+ background: white;
649
+ border-radius: 12px;
650
+ padding: 20px;
651
+ border: 1px solid #e2e8f0;
652
+ transition: all 0.3s ease;
653
  position: relative;
654
+ overflow: hidden;
655
  }
656
 
657
+ .result-card::before {
658
+ content: '';
659
+ position: absolute;
660
+ top: 0;
661
+ left: 0;
662
+ width: 4px;
663
+ height: 100%;
664
+ background: linear-gradient(to bottom, #2563eb, #3b82f6);
665
+ border-radius: 2px 0 0 2px;
666
+ }
667
+
668
+ .result-card:hover {
669
+ transform: translateY(-4px);
670
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.1);
671
+ }
672
+
673
+ .result-header {
674
  display: flex;
675
  align-items: center;
676
+ gap: 12px;
677
+ margin-bottom: 16px;
 
 
 
 
 
 
 
 
 
 
678
  }
679
 
680
+ .result-icon {
681
+ width: 40px;
682
+ height: 40px;
683
+ border-radius: 10px;
 
 
684
  display: flex;
685
  align-items: center;
686
+ justify-content: center;
687
+ font-size: 18px;
688
  }
689
 
690
+ .result-icon.truth {
691
+ background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
692
+ color: #1d4ed8;
693
  }
694
 
695
+ .result-icon.face {
696
+ background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
697
+ color: #b45309;
698
  }
699
 
700
+ .result-icon.involvement {
701
+ background: linear-gradient(135deg, #f3e8ff 0%, #e9d5ff 100%);
702
+ color: #7c3aed;
 
 
 
 
 
 
 
 
703
  }
704
 
705
+ .result-icon.emotion {
706
+ background: linear-gradient(135deg, #fce7f3 0%, #fbcfe8 100%);
707
+ color: #be185d;
708
  }
709
 
710
+ .result-title {
711
+ font-size: 14px;
712
+ font-weight: 600;
713
+ color: #475569;
714
+ flex: 1;
715
+ }
716
 
717
+ .result-value {
718
+ font-size: 24px;
719
+ font-weight: 700;
720
+ color: #1e293b;
721
+ }
722
 
723
+ .progress-bar {
724
+ height: 8px;
725
+ background: #e2e8f0;
726
+ border-radius: 4px;
727
+ overflow: hidden;
728
+ margin: 12px 0;
729
+ }
730
 
731
+ .progress-fill {
732
+ height: 100%;
733
+ background: linear-gradient(90deg, #10b981, #8b5cf6);
734
+ border-radius: 4px;
735
+ transition: width 1s ease;
736
+ }
737
+
738
+ .cues-container {
739
+ display: flex;
740
+ flex-wrap: wrap;
741
+ gap: 8px;
742
+ margin-top: 12px;
743
+ }
744
+
745
+ .cue-tag {
746
+ background: #f1f5f9;
747
+ color: #475569;
748
+ padding: 4px 12px;
749
+ border-radius: 20px;
750
+ font-size: 12px;
751
+ font-weight: 500;
752
+ transition: all 0.2s ease;
753
+ }
754
 
755
+ .cue-tag:hover {
756
+ background: #e2e8f0;
757
+ transform: translateY(-1px);
758
  }
759
 
760
+ /* ===== RIGHT PANEL ===== */
761
+ .right-panel {
762
  display: flex;
763
+ flex-direction: column;
764
+ gap: 32px;
765
+ }
766
+
767
+ /* Camera Section */
768
+ .camera-section {
769
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
770
+ border-radius: 20px;
771
+ overflow: hidden;
772
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2);
773
  position: relative;
774
+ border: 1px solid rgba(255, 255, 255, 0.1);
 
 
 
 
775
  }
776
+
777
+ .camera-placeholder {
778
+ height: 400px;
779
  display: flex;
780
+ flex-direction: column;
781
  align-items: center;
782
+ justify-content: center;
783
+ color: white;
784
+ padding: 40px;
785
+ text-align: center;
786
  }
787
+
788
+ .camera-icon {
789
+ font-size: 64px;
790
+ margin-bottom: 24px;
791
+ opacity: 0.7;
792
+ color: #cbd5e1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
793
  }
794
 
795
+ .camera-placeholder h3 {
796
+ font-size: 20px;
797
+ font-weight: 600;
798
+ margin-bottom: 12px;
799
+ background: linear-gradient(135deg, #ffffff 0%, #94a3b8 100%);
800
+ -webkit-background-clip: text;
801
+ background-clip: text;
802
+ -webkit-text-fill-color: transparent;
 
 
 
 
 
803
  }
804
+
805
+ .camera-placeholder p {
806
+ font-size: 14px;
807
+ color: #cbd5e1;
808
+ max-width: 300px;
809
+ line-height: 1.6;
810
  }
811
 
812
+ .video-active {
813
+ height: 400px;
 
 
 
 
 
 
 
814
  position: relative;
815
+ overflow: hidden;
 
 
 
 
 
 
 
 
 
 
 
 
816
  }
817
+
818
+ .camera-video {
819
+ width: 100%;
820
+ height: 100%;
821
+ object-fit: cover;
 
822
  }
823
+
824
+ .recording-overlay {
825
+ position: absolute;
826
+ top: 20px;
827
+ left: 20px;
828
+ background: rgba(220, 38, 38, 0.9);
829
+ color: white;
830
+ padding: 8px 20px;
831
+ border-radius: 20px;
832
+ font-size: 14px;
833
  font-weight: 500;
834
+ display: flex;
835
+ align-items: center;
836
+ gap: 8px;
837
+ backdrop-filter: blur(10px);
838
+ animation: pulse 2s infinite;
839
+ border: 1px solid rgba(255, 255, 255, 0.2);
 
840
  }
841
+
842
+ /* Transcription Section */
843
+ .transcription-section {
844
+ background: #f8fafc;
845
+ border-radius: 20px;
846
+ padding: 24px;
847
+ border: 1px solid #e2e8f0;
848
+ transition: all 0.3s ease;
849
  }
850
 
851
+ .transcription-section:hover {
852
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
853
+ }
854
+
855
+ .transcription-header {
 
 
 
 
856
  display: flex;
857
+ align-items: center;
858
+ justify-content: space-between;
859
+ margin-bottom: 20px;
860
  }
861
 
862
+ .transcription-header h3 {
863
+ font-size: 18px;
864
+ font-weight: 600;
865
+ color: #1e293b;
866
+ display: flex;
867
+ align-items: center;
868
+ gap: 8px;
869
+ }
870
+
871
+ .live-badge {
872
+ background: linear-gradient(135deg, #dc2626 0%, #ef4444 100%);
873
+ color: white;
874
+ padding: 4px 12px;
875
+ border-radius: 12px;
876
+ font-size: 12px;
877
+ font-weight: 600;
878
+ animation: pulse 2s infinite;
879
+ border: 1px solid rgba(255, 255, 255, 0.2);
880
  }
881
 
882
+ .transcription-content {
883
+ background: white;
884
+ border-radius: 12px;
885
+ padding: 20px;
886
+ max-height: 300px;
887
  overflow-y: auto;
888
+ border: 1px solid #e2e8f0;
 
889
  }
890
 
891
+ .transcription-line {
892
+ padding: 12px;
893
+ border-bottom: 1px solid #e2e8f0;
894
+ font-size: 14px;
895
+ line-height: 1.6;
896
+ color: #475569;
897
+ position: relative;
898
+ padding-left: 20px;
899
+ }
900
+
901
+ .transcription-line::before {
902
+ content: '';
903
+ position: absolute;
904
+ left: 0;
905
+ top: 16px;
906
+ width: 8px;
907
+ height: 8px;
908
+ background: #3b82f6;
909
+ border-radius: 50%;
910
+ }
911
 
912
+ .transcription-line:last-child {
913
+ border-bottom: none;
914
+ }
915
 
916
+ /* Final Action */
917
+ .final-action {
918
+ margin-top: 16px;
919
+ text-align: center;
920
+ padding: 20px;
921
+ background: #f8fafc;
922
+ border-radius: 16px;
923
+ border: 1px solid #e2e8f0;
924
+ }
925
 
926
+ .evaluate-btn {
927
+ background: linear-gradient(135deg, #059669 0%, #10b981 100%);
928
+ color: white;
 
 
 
 
 
 
 
929
  border: none;
930
+ border-radius: 14px;
931
+ padding: 18px 48px;
932
+ font-size: 16px;
933
+ font-weight: 600;
934
  cursor: pointer;
935
+ transition: all 0.3s ease;
936
+ display: inline-flex;
937
+ align-items: center;
938
+ gap: 12px;
939
+ box-shadow: 0 8px 24px rgba(5, 150, 105, 0.3);
940
+ position: relative;
941
+ overflow: hidden;
942
  }
943
 
944
+ .evaluate-btn:hover {
945
+ transform: translateY(-3px);
946
+ box-shadow: 0 12px 32px rgba(5, 150, 105, 0.4);
947
  }
948
 
949
+ .evaluate-btn:active {
950
+ transform: translateY(-1px);
951
+ }
952
+
953
+ /* ===== SIDEBARS ===== */
954
+ .evidence-sidebar,
955
+ .summary-sidebar {
956
  position: fixed;
957
+ top: 0;
958
+ right: -400px;
959
+ width: 380px;
960
+ height: 100vh;
961
+ background: white;
962
+ box-shadow: -8px 0 40px rgba(0, 0, 0, 0.15);
963
+ z-index: 2000;
964
+ transition: right 0.4s cubic-bezier(0.4, 0, 0.2, 1);
 
 
 
 
 
 
 
 
 
 
965
  overflow-y: auto;
 
 
 
 
966
  }
967
 
968
+ .evidence-sidebar.open,
969
+ .summary-sidebar.open {
970
  right: 0;
971
  }
972
 
973
+ .sidebar-header {
974
+ padding: 24px;
975
+ border-bottom: 1px solid #e2e8f0;
976
  display: flex;
977
  align-items: center;
978
  justify-content: space-between;
979
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
980
+ color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
981
  }
982
 
983
+ .sidebar-title {
984
+ font-size: 18px;
985
+ font-weight: 600;
 
 
 
 
986
  display: flex;
987
  align-items: center;
988
+ gap: 12px;
 
989
  }
990
 
991
+ .close-btn {
992
+ background: rgba(255, 255, 255, 0.1);
993
+ border: none;
994
+ width: 36px;
995
+ height: 36px;
996
+ border-radius: 10px;
997
+ color: white;
998
+ cursor: pointer;
999
+ transition: all 0.3s ease;
1000
  display: flex;
1001
  align-items: center;
1002
+ justify-content: center;
 
 
 
 
1003
  }
1004
 
1005
+ .close-btn:hover {
1006
+ background: rgba(255, 255, 255, 0.2);
1007
+ transform: rotate(90deg);
1008
+ }
1009
 
1010
+ .sidebar-content {
1011
+ padding: 24px;
 
 
1012
  }
1013
 
1014
+ /* ===== BLUR BACKGROUND ===== */
1015
+ .blur-bg {
1016
+ position: fixed;
1017
+ top: 0;
1018
+ left: 0;
1019
+ width: 100vw;
1020
+ height: 100vh;
1021
+ background: rgba(0, 0, 0, 0.5);
1022
+ backdrop-filter: blur(8px);
1023
+ z-index: 1999;
1024
+ opacity: 0;
1025
+ pointer-events: none;
1026
+ transition: opacity 0.4s ease;
1027
  }
1028
 
1029
+ .blur-bg.active {
1030
+ opacity: 1;
1031
+ pointer-events: all;
1032
+ }
 
 
1033
 
1034
+ /* ===== FOOTER ===== */
1035
+ footer {
1036
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
1037
+ color: white;
1038
+ text-align: center;
1039
+ padding: 24px 0;
1040
+ margin-top: 40px;
 
 
1041
  }
1042
 
1043
+ footer p {
1044
+ font-size: 14px;
1045
+ color: #cbd5e1;
1046
+ opacity: 0.8;
1047
+ }
 
 
 
 
 
 
 
 
 
 
1048
 
1049
+ /* ===== ANIMATIONS ===== */
1050
+ @keyframes slideIn {
1051
+ from {
1052
+ opacity: 0;
1053
+ transform: translateY(-20px);
1054
  }
1055
 
1056
+ to {
1057
+ opacity: 1;
1058
+ transform: translateY(0);
1059
+ }
 
 
 
1060
  }
1061
 
1062
+ @keyframes pulse {
1063
+ 0%, 100% {
1064
+ opacity: 1;
1065
+ }
 
1066
 
1067
+ 50% {
1068
+ opacity: 0.7;
1069
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1070
  }
1071
 
1072
+ @keyframes blink {
1073
+ 0%, 100% {
1074
+ opacity: 1;
1075
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1076
 
1077
+ 50% {
1078
+ opacity: 0.3;
1079
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1080
  }
1081
 
1082
+ /* ===== RESPONSIVE DESIGN ===== */
1083
+ @media (max-width: 1200px) {
1084
+ .content-grid {
1085
+ grid-template-columns: 1fr;
1086
+ gap: 24px;
1087
+ }
 
 
 
 
 
 
 
 
 
 
 
1088
 
1089
+ .results-section {
1090
+ grid-template-columns: repeat(2, 1fr);
1091
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1092
  }
1093
 
1094
+ @media (max-width: 768px) {
1095
+ .main-content-card {
1096
+ margin: 16px;
1097
+ border-radius: 20px;
1098
+ }
1099
+
1100
+ .card-inner {
1101
+ padding: 20px;
1102
+ }
1103
+
1104
+ .header-inner {
1105
+ padding: 0 16px;
1106
+ }
1107
+
1108
+ .content-grid {
1109
+ gap: 16px;
1110
+ }
1111
+
1112
+ .results-section {
1113
+ grid-template-columns: 1fr;
1114
+ }
1115
+
1116
+ .answer-actions {
1117
+ flex-direction: column;
1118
+ }
1119
+
1120
+ .video-active,
1121
+ .camera-placeholder {
1122
+ height: 300px;
1123
+ }
1124
+
1125
+ .evidence-sidebar,
1126
+ .summary-sidebar {
1127
+ width: 100%;
1128
+ right: -100%;
1129
+ }
1130
  }
1131
 
1132
+ @media (max-width: 480px) {
1133
+ .py-detect-title-header {
1134
+ font-size: 18px;
1135
+ letter-spacing: 1px;
1136
+ }
1137
+
1138
+ .header-actions-right-group {
1139
+ gap: 12px;
1140
+ flex-direction: column;
1141
+ align-items: flex-end;
1142
+ }
1143
+
1144
+ .back-btn span:last-child {
1145
+ display: none;
1146
+ }
1147
+
1148
+ .back-btn {
1149
+ padding: 10px 15px;
1150
+ font-size: 13px;
1151
+ }
1152
+
1153
+ .evidence-btn,
1154
+ .summary-btn {
1155
+ width: 40px;
1156
+ height: 40px;
1157
+ }
1158
+
1159
+ .main-content-card {
1160
+ margin: 8px;
1161
+ border-radius: 16px;
1162
+ }
1163
+ }
1164
 
src/app/py-detect/py-detect.component.html CHANGED
@@ -1,8 +1,8 @@
1
- <!-- Modern UI header with logo and PyDetect title -->
2
- <div class="site-header">
3
  <div class="header-inner">
4
  <div class="logo-cluster">
5
- <span (click)="navigateHome()" style="cursor:pointer;display:flex;align-items:center;">
6
  <img src="/assets/pykara-logo.png" alt="PyDetect Logo" class="logo-img-header" />
7
  </span>
8
  <div class="py-detect-title-header">
@@ -19,218 +19,340 @@
19
  </div>
20
  <div class="header-actions-right-group">
21
  <button class="back-btn" (click)="navigateBackToCaseDetails()">
22
- <span class="back-icon">←</span> Back to Case Details
 
23
  </button>
24
  <div class="header-actions-icons">
25
- <button class="icon-btn" (click)="showEvidencePanel = !showEvidencePanel" title="Upload Evidence">
26
  <i class="fas fa-file-upload"></i>
27
  </button>
28
- <button class="icon-btn" (click)="showSummaryPanel = !showSummaryPanel" title="Investigation Summary">
29
  <i class="fas fa-clipboard-list"></i>
30
  </button>
31
  </div>
32
  </div>
33
  </div>
34
- </div>
35
- <!-- Header action bar with buttons and divider line -->
36
- <div class="header-action-bar">
37
- <div class="header-action-left" style="position:relative;">
38
- <button class="small-btn" (click)="onStartInvestigation()">
39
- <i class="fas fa-search"></i> Start Investigation
40
- </button>
41
- <div *ngIf="currentQuestionIndex < 0" class="guidance-tooltip">
42
- <span style="font-size:1.3em;">💡</span>
43
- Click Start Investigation to activate camera and begin questioning.
44
- </div>
45
- </div>
46
- </div>
47
- <div class="header-divider-line"></div>
48
- <!-- Investigation Summary Toggle Button (moved below header, above camera box) -->
49
- <main class="main-flex-layout">
50
- <!-- Left Panel: Case Info + Question -->
51
- <section class="left-panel">
52
- <div class="animated-divider"></div>
53
- <!-- Remove old question/status block, keep only the new decorated card -->
54
- <!-- In tts-question-card, show AI question if available -->
55
- <div class="tts-question-card">
56
- <div class="tts-question-title">Question {{ currentQuestionIndex + 1 }}</div>
57
- <div class="tts-question-text">
58
- <ng-container *ngIf="questions.length > 0; else noQuestion">
59
- {{ questions[currentQuestionIndex] }}
60
- </ng-container>
61
- <ng-template #noQuestion>
62
- <span *ngIf="currentQuestionText">{{ currentQuestionText }}</span>
63
- <span *ngIf="!currentQuestionText">No question available.</span>
64
- </ng-template>
65
- </div>
66
- <div *ngIf="infoText" class="tts-status-row">{{ infoText }}</div>
67
-
68
- <!-- Manual answer input for testing -->
69
- <div class="answer-section">
70
- <div class="answer-card" style="background: #f8fbff; border-radius: 12px; box-shadow: 0 2px 8px #0001; padding: 18px 20px; margin-top: 10px; display: flex; flex-direction: column; gap: 18px; max-width: 650px; width: 100%;">
71
- <label for="answerInput" style="font-weight: 500; margin-bottom: 4px; color: #2a3b5c;">Your Answer:</label>
72
- <textarea id="answerInput" [(ngModel)]="textAnswer" (focus)="captureTextStart()" class="answer-input" placeholder="Type or speak your answer here..." rows="4" maxlength="3000" style="width: 100%; font-size: 1.08em; border-radius: 8px; border: 1px solid #bcd0ee; padding: 10px; resize: vertical; background: #fff; box-shadow: 0 1px 4px #0001; min-height: 60px;"></textarea>
73
- <div style="display: flex; gap: 10px; margin-top: 6px;">
74
- <button (click)="submitCombinedAnswer()" class="small-btn" style="background: linear-gradient(90deg,#3a8bfd,#6ad1ff); color: #fff; border-radius: 6px; font-weight: 500; padding: 7px 18px; border: none; box-shadow: 0 1px 4px #0001; cursor: pointer;">Submit Answer</button>
75
- <button (click)="toggleVoiceRecording()" class="mic-btn" style="background: #fff; color: #3a8bfd; border-radius: 6px; font-weight: 500; padding: 7px 18px; border: 1px solid #3a8bfd; box-shadow: 0 1px 4px #0001; cursor: pointer;">
76
- <span *ngIf="!isVoiceRecording">🎤 Start Recording</span>
77
- <span *ngIf="isVoiceRecording">⏹️ Stop Recording</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  </button>
 
 
 
 
 
 
 
 
 
 
79
  </div>
80
- <div *ngIf="isVoiceRecording" class="voice-hint" style="color: #e74c3c; font-weight: 500; margin-top: 4px;">🎤 Recording... Speak now.</div>
81
- <!-- Results Section: Truth Score, Face Detection, Involvement -->
82
- <div class="results-section" style="display: flex; flex-direction: column; gap: 16px; margin-top: 10px;">
83
- <div *ngIf="truthScore !== null" class="result-card" style="background: #e0f7fa; border-radius: 10px; box-shadow: 0 1px 6px #38bdf822; padding: 12px 16px; margin-bottom: 0;">
84
- <div style="display: flex; align-items: center; gap: 10px;">
85
- <i class="fas fa-check-circle" style="color: #38bdf8; font-size: 1.3em;"></i>
86
- <span style="font-weight: 600; color: #2563eb; font-size: 1.08em;">Truth Score</span>
87
- <span style="font-weight: 700; color: #222; font-size: 1.15em; margin-left: auto;">{{ truthScore }}</span>
 
88
  </div>
 
 
89
  </div>
90
- <div *ngIf="faceDetectionScore !== null" class="result-card" style="background: #fffde7; border-radius: 10px; box-shadow: 0 1px 6px #ffe08244; padding: 12px 16px; margin-bottom: 0;">
91
- <div style="display: flex; align-items: center; gap: 10px;">
92
- <i class="fas fa-user-check" style="color: #ff9800; font-size: 1.3em;"></i>
93
- <span style="font-weight: 600; color: #ff9800; font-size: 1.08em;">Face Detection Score</span>
94
- <span style="font-weight: 700; color: #222; font-size: 1.15em; margin-left: auto;">{{ faceDetectionScore }}</span>
 
 
95
  </div>
 
 
96
  </div>
97
- <div *ngIf="involvementScore !== null" class="result-card" style="background: #f3e8ff; border-radius: 10px; box-shadow: 0 1px 6px #a78bfa44; padding: 12px 16px; margin-bottom: 0;">
98
- <div style="font-weight:600;color:#6d28d9;display:flex;align-items:center;gap:8px;">
99
- <i class="fas fa-user-tag" style="color: #6d28d9; font-size: 1.3em;"></i>
100
- <span>Involvement Score</span>
101
- <span style="font-weight: 700; color: #222; font-size: 1.15em; margin-left: auto;">{{ involvementScore | number:'1.1-1' }}</span>
 
 
102
  </div>
103
- <div style="flex:1;background:#e0ecf8;height:14px;border-radius:6px;overflow:hidden;margin-top:8px;">
104
- <div [style.width]="involvementScore + '%'" [style.background]="'linear-gradient(90deg,#4caf50,#ff9800,#f44336)'" style="height:100%;"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  </div>
106
- <div *ngIf="involvementCues.length" style="margin-top:6px;display:flex;flex-wrap:wrap;gap:6px;">
107
- <span *ngFor="let cue of involvementCues" style="background:#3a8bfd11;color:#1d3e63;padding:4px 10px;border:1px solid #3a8bfd33;border-radius:16px;font-size:0.75rem;font-weight:500;">{{ cue.replace('_cue','').replace('_',' ') }}</span>
108
  </div>
109
- <div *ngIf="dominantInvestigativeExpression" style="margin-top:4px;font-size:0.75rem;color:#445;">Dominant Investigative Expression: <strong>{{ dominantInvestigativeExpression }}</strong></div>
110
- <div class="body-language-explanation" *ngIf="bodyLanguageMeaning || bodyLanguageExplanation">
111
- <span *ngIf="bodyLanguageMeaning" class="explanation-label">Body Language Meaning:</span>
112
- <span *ngIf="bodyLanguageMeaning" class="explanation-text">{{ bodyLanguageMeaning }}</span><br *ngIf="bodyLanguageMeaning && bodyLanguageExplanation">
113
- <span *ngIf="bodyLanguageExplanation" class="explanation-label">Body Language Explanation:</span>
114
- <span *ngIf="bodyLanguageExplanation" class="explanation-text">{{ bodyLanguageExplanation }}</span>
 
 
115
  </div>
116
- </div>
117
- <div *ngIf="ferEmotion" class="result-card" style="background: #e3f6ff; border-radius: 10px; box-shadow: 0 1px 6px #38bdf822; padding: 12px 16px; margin-bottom: 0;">
118
- <div style="display: flex; align-items: center; gap: 10px;">
119
- <i class="fas fa-smile" style="color: #38bdf8; font-size: 1.3em;"></i>
120
- <span style="font-weight: 600; color: #2563eb; font-size: 1.08em;">Emotion (FER)</span>
121
- <span style="font-weight: 700; color: #222; font-size: 1.15em; margin-left: auto;">{{ ferEmotion }}</span>
122
- </div>
123
  </div>
124
  </div>
125
- <div *ngIf="guidanceCommand" style="margin-top:6px;font-size:0.72rem;color:#555;background:#ffeecd;padding:6px 8px;border-radius:6px;box-shadow:0 1px 3px #0001;">Guidance: {{ guidanceCommand }}</div>
126
  </div>
127
  </div>
128
- </div>
129
- </section>
130
- <!-- Right Panel: Video + Transcript -->
131
- <section class="right-panel">
132
- <div class="video-preview">
133
- <ng-container *ngIf="!videoStream">
134
- <div class="camera-inactive-block">
135
- <i class="fas fa-camera camera-placeholder-icon"></i>
136
- <div class="camera-inactive-title">Camera Inactive</div>
137
- <div class="camera-inactive-sub">Click "Start Investigation" to begin video recording</div>
138
- </div>
139
- </ng-container>
140
- <ng-container *ngIf="videoStream">
141
- <video #videoElement [srcObject]="videoStream" autoplay muted playsinline class="camera-video"></video>
142
- <div *ngIf="isRecording && currentQuestionIndex >= 0 && currentQuestionIndex < totalQuestions" class="recording-indicator">
143
- <span style="font-size:1.3em;">🔴</span> Recording...
144
- </div>
145
- <div class="video-status stylish-font">{{ videoStatus }}</div>
146
- <div class="waveform-animation" *ngIf="isRecording">
147
- <span></span><span></span><span></span><span></span><span></span>
148
- </div>
149
- </ng-container>
150
- </div>
151
- <div class="section-line"></div>
152
- <div class="transcript-panel">
153
- <div class="transcript-title">Transcription (Live):</div>
154
- <div class="transcript-scrollable">
155
- <div *ngFor="let line of transcriptLines" class="transcript-line">{{ line }}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  </div>
157
  </div>
158
- <button class="submit-evaluate-btn" (click)="navigateToValidationPage()">
159
- Submit & Evaluate
160
- </button>
161
- <!-- <a *ngIf="recordedVideoUrl" [href]="recordedVideoUrl" download="investigation-video.webm" class="download-btn" style="margin-left: 12px; background: #3a8bfd; color: #fff; padding: 10px 22px; border-radius: 8px; font-weight: 500; text-decoration: none; box-shadow: 0 1px 4px #0001; vertical-align: middle;">Download Video</a> -->
162
- </section>
163
- </main>
164
- <!-- Evidence Panel Sidebar (directly below button bar) -->
165
  <div class="evidence-sidebar" [class.open]="showEvidencePanel">
166
- <div class="evidence-title-bar">
167
- <span class="evidence-title">Evidence Panel</span>
168
- <button class="evidence-close-btn" (click)="showEvidencePanel = false" title="Close">
 
 
 
169
  <i class="fas fa-times"></i>
170
  </button>
171
  </div>
172
- <div class="evidence-form-card">
173
  <form class="evidence-upload-form" (submit)="$event.preventDefault(); uploadDocument()">
174
- <div class="evidence-upload-row">
175
- <label for="evidenceFileDoc" class="evidence-upload-label">
176
- <i class="fas fa-file-upload evidence-field-icon"></i>
177
- <span>Upload Document</span>
178
- </label>
179
- <input id="evidenceFileDoc" type="file" (change)="onEvidenceFileSelect($event, 'document')" />
180
  </div>
181
- <div class="evidence-upload-row">
182
- <label for="evidenceFilePhoto" class="evidence-upload-label">
183
- <i class="fas fa-image evidence-field-icon"></i>
184
- <span>Upload Photo</span>
185
- </label>
186
- <input id="evidenceFilePhoto" type="file" accept="image/*" (change)="onEvidenceFileSelect($event, 'photo')" />
187
  </div>
188
- <div class="evidence-upload-row">
189
- <label for="evidenceFileRec" class="evidence-upload-label">
190
- <i class="fas fa-microphone evidence-field-icon"></i>
191
- <span>Upload Recording</span>
192
- </label>
193
- <input id="evidenceFileRec" type="file" accept="audio/*,video/*" (change)="onEvidenceFileSelect($event, 'recording')" />
194
  </div>
195
- <hr class="evidence-divider" />
196
- <div class="evidence-summary-row">
197
- <label for="evidenceSummary" class="evidence-summary-label">Remark</label>
198
- <textarea id="evidenceSummary" [(ngModel)]="evidenceSummary" [ngModelOptions]="{standalone: true}" rows="3" placeholder="Enter Remark..." class="evidence-summary-textarea"></textarea>
 
 
199
  </div>
200
- <button type="submit" class="evidence-submit-btn">Submit</button>
 
 
 
 
201
  </form>
202
  </div>
203
  </div>
 
204
  <!-- Summary Sidebar -->
205
  <div class="summary-sidebar" [class.open]="showSummaryPanel">
206
- <div class="summary-title-bar">
207
- <span class="summary-title">Investigation Summary</span>
208
- <button class="summary-close-btn" (click)="showSummaryPanel = false" title="Close">
 
 
 
209
  <i class="fas fa-times"></i>
210
  </button>
211
  </div>
212
- <div class="summary-content-card">
213
- <div class="case-section-title">Case Details</div>
214
- <div class="case-row"><span class="case-label">Case ID:</span> <span class="case-value">{{ caseId }}</span></div>
215
- <div class="case-row"><span class="case-label">Crime Type:</span> <span class="case-value">{{ crimeType }}</span></div>
216
- <div class="case-row"><span class="case-label">Date & Time:</span> <span class="case-value">{{ dateTime }}</span></div>
217
- <div class="case-row"><span class="case-label">Location:</span> <span class="case-value">{{ location }}</span></div>
218
- <div class="divider-line"></div>
219
- <div class="case-section-title">Person Details</div>
220
- <div class="case-row"><span class="case-label">Suspect Name:</span> <span class="case-value">{{ suspectName }}</span></div>
221
- <div class="case-row"><span class="case-label">Investigation Officer:</span> <span class="case-value">{{ investigationOfficer }}</span></div>
222
- <div class="divider-line"></div>
223
- <div class="case-section-title">Progress Details</div>
224
- <div class="case-row"><span class="case-label">Status:</span> <span class="case-value">{{ statusText }}</span></div>
225
- <div class="case-row"><span class="case-label">Progress:</span> <span class="case-value">{{progress}}% (Stage: {{ progressStage }})</span></div>
226
- <div class="case-row"><span class="case-label">Session Time:</span> <span class="case-value">{{ sessionTime }}</span></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  </div>
228
  </div>
229
 
230
- <!-- Blur background when evidence or summary panel is open -->
231
- <div class="blur-bg" [class.hide]="!showEvidencePanel && !showSummaryPanel"></div>
232
 
 
233
  <footer>
234
  <p>© 2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
235
  </footer>
236
-
 
1
+ <!-- Header -->
2
+ <header class="site-header">
3
  <div class="header-inner">
4
  <div class="logo-cluster">
5
+ <span (click)="navigateHome()" style="cursor:pointer">
6
  <img src="/assets/pykara-logo.png" alt="PyDetect Logo" class="logo-img-header" />
7
  </span>
8
  <div class="py-detect-title-header">
 
19
  </div>
20
  <div class="header-actions-right-group">
21
  <button class="back-btn" (click)="navigateBackToCaseDetails()">
22
+ <i class="fas fa-arrow-left"></i>
23
+ <span>Back to Case Details</span>
24
  </button>
25
  <div class="header-actions-icons">
26
+ <button class="evidence-btn" (click)="showEvidencePanel = !showEvidencePanel" title="Upload Evidence">
27
  <i class="fas fa-file-upload"></i>
28
  </button>
29
+ <button class="summary-btn" (click)="showSummaryPanel = !showSummaryPanel" title="Investigation Summary">
30
  <i class="fas fa-clipboard-list"></i>
31
  </button>
32
  </div>
33
  </div>
34
  </div>
35
+ </header>
36
+
37
+ <!-- Main Content Card -->
38
+ <div class="main-content-card">
39
+ <div class="card-inner">
40
+ <div class="content-grid">
41
+ <!-- Left Column -->
42
+ <div class="left-panel">
43
+ <!-- Guidance Banner -->
44
+ <div *ngIf="currentQuestionIndex < 0" class="guidance-banner">
45
+ <div class="guidance-content">
46
+ <div class="guidance-icon">
47
+ <i class="fas fa-lightbulb"></i>
48
+ </div>
49
+ <div class="guidance-text">
50
+ <h3>Ready to Begin Investigation</h3>
51
+ <p>Click "Start Investigation" to activate camera and begin questioning</p>
52
+ </div>
53
+ </div>
54
+ </div>
55
+
56
+ <!-- Start Investigation Button -->
57
+ <button class="start-investigation-btn" (click)="onStartInvestigation()">
58
+ <i class="fas fa-search"></i>
59
+ Start Investigation
60
+ </button>
61
+
62
+ <!-- Question Section -->
63
+ <div class="question-section">
64
+ <div class="question-header">
65
+ <div class="question-badge">Question {{ currentQuestionIndex + 1 }}</div>
66
+ <div class="status-indicator" *ngIf="infoText">
67
+ <i class="fas fa-info-circle"></i>
68
+ {{ infoText }}
69
+ </div>
70
+ </div>
71
+ <div class="question-content">
72
+ <h3>Current Question</h3>
73
+ <div class="question-text">
74
+ <ng-container *ngIf="questions.length > 0 && currentQuestionIndex >= 0">
75
+ {{ questions[currentQuestionIndex] }}
76
+ </ng-container>
77
+ <ng-container *ngIf="!questions.length || currentQuestionIndex < 0">
78
+ <span *ngIf="currentQuestionText">{{ currentQuestionText }}</span>
79
+ <span *ngIf="!currentQuestionText" class="no-question">
80
+ Awaiting first question. Click "Start Investigation" to begin.
81
+ </span>
82
+ </ng-container>
83
+ </div>
84
+ </div>
85
+ </div>
86
+
87
+ <!-- Answer Section -->
88
+ <div class="answer-section">
89
+ <h3>Your Answer</h3>
90
+ <textarea id="answerInput"
91
+ [(ngModel)]="textAnswer"
92
+ (focus)="captureTextStart()"
93
+ class="answer-input"
94
+ placeholder="Type or speak your answer here..."
95
+ rows="5"
96
+ maxlength="3000">
97
+ </textarea>
98
+
99
+ <div class="answer-actions">
100
+ <button (click)="submitCombinedAnswer()" class="btn-primary">
101
+ <i class="fas fa-paper-plane"></i>
102
+ Submit Answer
103
  </button>
104
+ <button (click)="toggleVoiceRecording()" class="btn-secondary">
105
+ <i class="fas fa-microphone"></i>
106
+ <span *ngIf="!isVoiceRecording">Start Recording</span>
107
+ <span *ngIf="isVoiceRecording">Stop Recording</span>
108
+ </button>
109
+ </div>
110
+
111
+ <div *ngIf="isVoiceRecording" class="recording-status">
112
+ <div class="recording-dot"></div>
113
+ <span>Recording... Speak now</span>
114
  </div>
115
+ </div>
116
+
117
+ <!-- Results Section -->
118
+ <div class="results-section">
119
+ <!-- Truth Score -->
120
+ <div *ngIf="truthScore !== null" class="result-card">
121
+ <div class="result-header">
122
+ <div class="result-icon truth">
123
+ <i class="fas fa-check-circle"></i>
124
  </div>
125
+ <div class="result-title">Truth Score</div>
126
+ <div class="result-value">{{ truthScore }}</div>
127
  </div>
128
+ </div>
129
+
130
+ <!-- Face Detection -->
131
+ <div *ngIf="faceDetectionScore !== null" class="result-card">
132
+ <div class="result-header">
133
+ <div class="result-icon face">
134
+ <i class="fas fa-user-check"></i>
135
  </div>
136
+ <div class="result-title">Face Detection</div>
137
+ <div class="result-value">{{ faceDetectionScore }}</div>
138
  </div>
139
+ </div>
140
+
141
+ <!-- Involvement Score -->
142
+ <div *ngIf="involvementScore !== null" class="result-card">
143
+ <div class="result-header">
144
+ <div class="result-icon involvement">
145
+ <i class="fas fa-user-tag"></i>
146
  </div>
147
+ <div class="result-title">Involvement Score</div>
148
+ <div class="result-value">{{ involvementScore | number:'1.1-1' }}</div>
149
+ </div>
150
+ <div class="progress-bar">
151
+ <div class="progress-fill" [style.width]="involvementScore + '%'"></div>
152
+ </div>
153
+
154
+ <div *ngIf="involvementCues.length" class="cues-container">
155
+ <span *ngFor="let cue of involvementCues" class="cue-tag">
156
+ {{ cue.replace('_cue','').replace('_',' ') }}
157
+ </span>
158
+ </div>
159
+
160
+ <div *ngIf="bodyLanguageMeaning || bodyLanguageExplanation" style="margin-top: 12px; font-size: 12px; color: #64748b;">
161
+ <div *ngIf="bodyLanguageMeaning" style="margin-bottom: 4px;">
162
+ <strong>Body Language:</strong> {{ bodyLanguageMeaning }}
163
  </div>
164
+ <div *ngIf="bodyLanguageExplanation">
165
+ <strong>Analysis:</strong> {{ bodyLanguageExplanation }}
166
  </div>
167
+ </div>
168
+ </div>
169
+
170
+ <!-- FER Emotion -->
171
+ <div *ngIf="ferEmotion" class="result-card">
172
+ <div class="result-header">
173
+ <div class="result-icon emotion">
174
+ <i class="fas fa-smile"></i>
175
  </div>
176
+ <div class="result-title">Emotion (FER)</div>
177
+ <div class="result-value">{{ ferEmotion }}</div>
 
 
 
 
 
178
  </div>
179
  </div>
 
180
  </div>
181
  </div>
182
+
183
+ <!-- Right Column -->
184
+ <div class="right-panel">
185
+ <!-- Camera Section -->
186
+ <div class="camera-section">
187
+ <ng-container *ngIf="!videoStream">
188
+ <div class="camera-placeholder">
189
+ <div class="camera-icon">
190
+ <i class="fas fa-video"></i>
191
+ </div>
192
+ <h3>Camera Inactive</h3>
193
+ <p>Click "Start Investigation" to begin video recording and analysis</p>
194
+ </div>
195
+ </ng-container>
196
+
197
+ <ng-container *ngIf="videoStream">
198
+ <div class="video-active">
199
+ <video #videoElement [srcObject]="videoStream" autoplay muted playsinline class="camera-video"></video>
200
+ <div *ngIf="isRecording && currentQuestionIndex >= 0 && currentQuestionIndex < totalQuestions" class="recording-overlay">
201
+ <i class="fas fa-circle" style="font-size: 8px;"></i>
202
+ LIVE RECORDING
203
+ </div>
204
+ </div>
205
+ </ng-container>
206
+ </div>
207
+
208
+ <!-- Transcription Section -->
209
+ <div class="transcription-section">
210
+ <div class="transcription-header">
211
+ <h3>
212
+ <i class="fas fa-comments"></i>
213
+ Transcription
214
+ <span class="live-badge">LIVE</span>
215
+ </h3>
216
+ </div>
217
+ <div class="transcription-content">
218
+ <div *ngFor="let line of transcriptLines" class="transcription-line">
219
+ {{ line }}
220
+ </div>
221
+ <div *ngIf="!transcriptLines.length" class="transcription-line" style="text-align: center; color: #94a3b8;">
222
+ <i class="fas fa-microphone-alt"></i>
223
+ <div style="margin-top: 8px;">Waiting for speech input...</div>
224
+ </div>
225
+ </div>
226
+ </div>
227
+
228
+ <!-- Final Action -->
229
+ <div class="final-action">
230
+ <button class="evaluate-btn" (click)="navigateToValidationPage()">
231
+ <i class="fas fa-check-circle"></i>
232
+ Submit & Evaluate
233
+ </button>
234
+ </div>
235
  </div>
236
  </div>
237
+ </div>
238
+ </div>
239
+
240
+ <!-- Evidence Sidebar -->
 
 
 
241
  <div class="evidence-sidebar" [class.open]="showEvidencePanel">
242
+ <div class="sidebar-header">
243
+ <div class="sidebar-title">
244
+ <i class="fas fa-file-upload"></i>
245
+ Evidence Panel
246
+ </div>
247
+ <button class="close-btn" (click)="showEvidencePanel = false" title="Close">
248
  <i class="fas fa-times"></i>
249
  </button>
250
  </div>
251
+ <div class="sidebar-content">
252
  <form class="evidence-upload-form" (submit)="$event.preventDefault(); uploadDocument()">
253
+ <div style="margin-bottom: 24px;">
254
+ <label style="display: block; margin-bottom: 8px; font-weight: 600; color: #1e293b;">Upload Document</label>
255
+ <input type="file" (change)="onEvidenceFileSelect($event, 'document')"
256
+ style="width: 100%; padding: 12px; border: 2px dashed #cbd5e1; border-radius: 10px; background: #f8fafc; cursor: pointer;">
 
 
257
  </div>
258
+ <div style="margin-bottom: 24px;">
259
+ <label style="display: block; margin-bottom: 8px; font-weight: 600; color: #1e293b;">Upload Photo</label>
260
+ <input type="file" accept="image/*" (change)="onEvidenceFileSelect($event, 'photo')"
261
+ style="width: 100%; padding: 12px; border: 2px dashed #cbd5e1; border-radius: 10px; background: #f8fafc; cursor: pointer;">
 
 
262
  </div>
263
+ <div style="margin-bottom: 24px;">
264
+ <label style="display: block; margin-bottom: 8px; font-weight: 600; color: #1e293b;">Upload Recording</label>
265
+ <input type="file" accept="audio/*,video/*" (change)="onEvidenceFileSelect($event, 'recording')"
266
+ style="width: 100%; padding: 12px; border: 2px dashed #cbd5e1; border-radius: 10px; background: #f8fafc; cursor: pointer;">
 
 
267
  </div>
268
+
269
+ <div style="margin-bottom: 24px;">
270
+ <label style="display: block; margin-bottom: 8px; font-weight: 600; color: #1e293b;">Remarks</label>
271
+ <textarea [(ngModel)]="evidenceSummary" [ngModelOptions]="{standalone: true}"
272
+ rows="4" placeholder="Enter any additional remarks..."
273
+ style="width: 100%; padding: 12px; border: 2px solid #cbd5e1; border-radius: 10px; background: white; font-family: inherit;"></textarea>
274
  </div>
275
+
276
+ <button type="submit" class="btn-primary" style="width: 100%;">
277
+ <i class="fas fa-paper-plane"></i>
278
+ Submit Evidence
279
+ </button>
280
  </form>
281
  </div>
282
  </div>
283
+
284
  <!-- Summary Sidebar -->
285
  <div class="summary-sidebar" [class.open]="showSummaryPanel">
286
+ <div class="sidebar-header">
287
+ <div class="sidebar-title">
288
+ <i class="fas fa-clipboard-list"></i>
289
+ Investigation Summary
290
+ </div>
291
+ <button class="close-btn" (click)="showSummaryPanel = false" title="Close">
292
  <i class="fas fa-times"></i>
293
  </button>
294
  </div>
295
+ <div class="sidebar-content">
296
+ <div style="margin-bottom: 24px;">
297
+ <h4 style="color: #2563eb; margin-bottom: 12px; font-size: 14px; font-weight: 600;">Case Details</h4>
298
+ <div style="background: #f0f9ff; border-radius: 10px; padding: 16px;">
299
+ <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
300
+ <span style="color: #475569; font-size: 13px;">Case ID:</span>
301
+ <span style="font-weight: 600; color: #1e293b;">{{ caseId }}</span>
302
+ </div>
303
+ <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
304
+ <span style="color: #475569; font-size: 13px;">Crime Type:</span>
305
+ <span style="font-weight: 600; color: #1e293b;">{{ crimeType }}</span>
306
+ </div>
307
+ <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
308
+ <span style="color: #475569; font-size: 13px;">Date & Time:</span>
309
+ <span style="font-weight: 600; color: #1e293b;">{{ dateTime }}</span>
310
+ </div>
311
+ <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
312
+ <span style="color: #475569; font-size: 13px;">Location:</span>
313
+ <span style="font-weight: 600; color: #1e293b;">{{ location }}</span>
314
+ </div>
315
+ </div>
316
+ </div>
317
+
318
+ <div style="margin-bottom: 24px;">
319
+ <h4 style="color: #2563eb; margin-bottom: 12px; font-size: 14px; font-weight: 600;">Person Details</h4>
320
+ <div style="background: #f0f9ff; border-radius: 10px; padding: 16px;">
321
+ <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
322
+ <span style="color: #475569; font-size: 13px;">Suspect Name:</span>
323
+ <span style="font-weight: 600; color: #1e293b;">{{ suspectName }}</span>
324
+ </div>
325
+ <div style="display: flex; justify-content: space-between;">
326
+ <span style="color: #475569; font-size: 13px;">Investigation Officer:</span>
327
+ <span style="font-weight: 600; color: #1e293b;">{{ investigationOfficer }}</span>
328
+ </div>
329
+ </div>
330
+ </div>
331
+
332
+ <div>
333
+ <h4 style="color: #2563eb; margin-bottom: 12px; font-size: 14px; font-weight: 600;">Progress Details</h4>
334
+ <div style="background: #f0f9ff; border-radius: 10px; padding: 16px;">
335
+ <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
336
+ <span style="color: #475569; font-size: 13px;">Status:</span>
337
+ <span style="font-weight: 600; color: #1e293b;">{{ statusText }}</span>
338
+ </div>
339
+ <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
340
+ <span style="color: #475569; font-size: 13px;">Progress:</span>
341
+ <span style="font-weight: 600; color: #1e293b;">{{progress}}% ({{ progressStage }})</span>
342
+ </div>
343
+ <div style="display: flex; justify-content: space-between;">
344
+ <span style="color: #475569; font-size: 13px;">Session Time:</span>
345
+ <span style="font-weight: 600; color: #1e293b;">{{ sessionTime }}</span>
346
+ </div>
347
+ </div>
348
+ </div>
349
  </div>
350
  </div>
351
 
352
+ <!-- Blur Background -->
353
+ <div class="blur-bg" [class.active]="showEvidencePanel || showSummaryPanel"></div>
354
 
355
+ <!-- Footer -->
356
  <footer>
357
  <p>© 2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
358
  </footer>
 
src/app/py-detect/py-detect.component.ts CHANGED
@@ -197,70 +197,70 @@ export class PyDetectComponent implements OnInit, OnDestroy {
197
  ttsEnabled: boolean = false;
198
  isListening: boolean = false;
199
  speechRecognition: any = null;
200
- // Combined answer submission: prefer text box, fallback to transcript
201
- public submitCombinedAnswer() {
202
- // Accept answer from text box or voice transcript
203
- let answerText = (this.textAnswer && this.textAnswer.trim()) ? this.textAnswer.trim() : (this.transcriptSoFar && this.transcriptSoFar.trim()) ? this.transcriptSoFar.trim() : '';
204
- if (!answerText || !this.sessionId || !this.questions[this.currentQuestionIndex]) {
205
- this.infoText = 'Please provide your answer before submitting.';
206
- return;
207
- }
208
- this.stopAudioRecording();
209
- this.infoText = 'Submitting answer...';
210
- this.textAnswer = '';
211
- this.transcriptSoFar = '';
212
- const endTs = Date.now();
213
- this.answerEndAt = endTs;
214
- if (!this.answerStartAt) this.answerStartAt = this.questionWindowStartAt || endTs;
215
- const durationMs = this.answerEndAt - this.answerStartAt;
216
- this.stopFrameStreaming();
217
- this.pyDetectService.submitResponse(
218
- this.sessionId,
219
- answerText,
220
- this.questions[this.currentQuestionIndex],
221
- {
222
- answer_start_at: this.answerStartAt,
223
- answer_end_at: this.answerEndAt,
224
- duration_ms: durationMs,
225
- mode: this.answerMode
226
- }
227
- ).subscribe({
228
- next: async (res) => {
229
- // Extract truth score if present
230
- this.truthScore = (res && (res.truth_score || res.score)) ? Number(res.truth_score || res.score) : null;
231
- this.infoText = 'Answer submitted.' + (this.truthScore !== null ? ` Truth Score: ${this.truthScore}` : '');
232
- // Pull involvement metrics
233
- this.fetchLatestInvolvement();
234
- // Fetch next question from backend
235
- const response = await this.pyDetectService.askQuestion(
236
- this.sessionId,
237
- this.crimeType,
238
- this.briefDescription
239
- ).toPromise();
240
- if (response && response.question) {
241
- this.questions.push(response.question);
242
- this.currentQuestionIndex++;
243
- this.questionNumber = this.currentQuestionIndex + 1;
244
- this.cdr.detectChanges();
245
- await this.startCamera();
246
- await this.startVideoRecording();
247
- await this.speakQuestion(response.question);
248
- // Restart window for next question
249
- this.startQuestionWindow();
250
- // Reset answer timing
251
- this.answerStartAt = null;
252
- this.answerEndAt = null;
253
- this.answerMode = 'text';
254
- } else {
255
- this.infoText = 'No more questions.';
256
- this.showSummary = true;
257
- }
258
- },
259
- error: () => {
260
- this.infoText = 'Error submitting answer.';
261
- }
262
- });
263
- }
264
  public stopAudioRecording() {
265
  if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
266
  this.mediaRecorder.stop();
@@ -269,7 +269,7 @@ export class PyDetectComponent implements OnInit, OnDestroy {
269
  this.infoText = 'Voice recording stopped.';
270
  // Stop speech recognition when recording stops
271
  if (this.recognition) {
272
- try { this.recognition.stop(); } catch {}
273
  }
274
  }
275
  }
@@ -491,8 +491,8 @@ export class PyDetectComponent implements OnInit, OnDestroy {
491
  private async fetchNextQuestion(): Promise<string> {
492
  // Replace this with HTTP call to your backend if needed.
493
  // Example: const { question } = await this.http.get<{question:string}>('/api/next-question').toPromise();
494
- // Legacy fetchNextQuestion logic removed. Use backend-driven workflow only.
495
- return '';
496
  }
497
 
498
  // ======== TTS (question playback) ========
@@ -535,7 +535,7 @@ export class PyDetectComponent implements OnInit, OnDestroy {
535
  audio: { channelCount: 1, echoCancellation: true, noiseSuppression: true },
536
  video: false
537
  });
538
- // ...existing code...
539
 
540
  // 2) prepare MediaRecorder
541
  const mime = this.chooseMimeType();
@@ -567,7 +567,7 @@ export class PyDetectComponent implements OnInit, OnDestroy {
567
  this.stopAnalyser();
568
  this.stopRecognition();
569
  this.cleanupMediaStream();
570
- // ...existing code...
571
 
572
  // 7) build audio URL
573
  const blob = new Blob(this.audioChunks, { type: mime });
@@ -586,7 +586,7 @@ export class PyDetectComponent implements OnInit, OnDestroy {
586
  private waitForSilenceOrContinue() {
587
  if (this.silenceTimeout) clearTimeout(this.silenceTimeout);
588
  this.silenceTimeout = setTimeout(() => {
589
- // ...existing code...
590
  }, 5002); // Timeout after 5 seconds of silence
591
  }
592
 
@@ -613,7 +613,7 @@ export class PyDetectComponent implements OnInit, OnDestroy {
613
  this.sourceNode.connect(this.analyser);
614
 
615
  // Use correct constructor for Float32Array
616
- this.analyserBuffer = new Float32Array(this.analyser.fftSize);
617
 
618
  const tick = () => {
619
  if (!this.analyser || !this.analyserBuffer) return;
@@ -723,7 +723,7 @@ export class PyDetectComponent implements OnInit, OnDestroy {
723
  };
724
  }
725
 
726
- private removeFillerWords(text: string): string {
727
  const fillerWords = ['um', 'ah', 'like', 'you know', 'so', 'actually', 'basically'];
728
  const regex = new RegExp(`\\b(${fillerWords.join('|')})\\b`, 'gi');
729
  return text.replace(regex, '').replace(/\s+/g, ' ').trim(); // Clean extra spaces after removal
@@ -894,18 +894,18 @@ export class PyDetectComponent implements OnInit, OnDestroy {
894
  public async onStartInvestigation() {
895
  this.isLoading = true;
896
  this.infoText = 'Starting investigation...';
897
- await this.startCamera();
898
  await this.startVideoRecording();
899
  await this.startSession();
900
  // Ensure questions are loaded and index is set
901
  // Use fallback logic for brief description
902
  let briefDescriptionToSend = this.briefDescription?.trim() || '';
903
  if (!briefDescriptionToSend) {
904
- briefDescriptionToSend =
905
- sessionStorage.getItem('briefDescription')?.trim() ||
906
- this.caseData?.briefDescription?.trim() ||
907
- this.caseData?.police?.information?.trim() ||
908
- this.caseData?.crime?.trim() ||
909
  '';
910
  }
911
  const response = await this.pyDetectService.askQuestion(
@@ -935,150 +935,150 @@ export class PyDetectComponent implements OnInit, OnDestroy {
935
  }
936
 
937
  // Backend-driven session start and first question fetch
938
- public async startSession(): Promise<void> {
939
- try {
940
- this.isLoading = true;
941
- if (this.voiceSupported) {
942
- this.ttsEnabled = true;
943
- setTimeout(() => {
944
- this.speakQuestion('Investigation starting. I will ask you questions and you can respond using voice or text.');
945
- }, 1000);
946
- }
947
- let briefDescriptionToSend = this.briefDescription?.trim() || '';
948
- if (!briefDescriptionToSend) {
949
- briefDescriptionToSend =
950
- sessionStorage.getItem('briefDescription')?.trim() ||
951
- this.caseData?.briefDescription?.trim() ||
952
- this.caseData?.police?.information?.trim() ||
953
- this.caseData?.crime?.trim() ||
954
- '';
955
- }
956
- const sessionResponse = await this.pyDetectService.startSession(briefDescriptionToSend).toPromise();
957
- this.sessionId = sessionResponse.session_id;
958
- sessionStorage.setItem('sessionId', this.sessionId);
959
- localStorage.setItem('sessionId', this.sessionId);
960
- const caseData = this.caseData || {};
961
- const caseDataToSend = {
962
- ...caseData,
963
- brief_description: briefDescriptionToSend
964
- };
965
- await this.pyDetectService.submitCaseDetails(
966
- this.sessionId,
967
- caseDataToSend,
968
- briefDescriptionToSend
969
- ).toPromise();
970
- if (briefDescriptionToSend && briefDescriptionToSend.length > 0) {
971
- const questionsResponse = await this.pyDetectService.askQuestion(
972
  this.sessionId,
973
- this.crimeType,
974
  briefDescriptionToSend
975
  ).toPromise();
976
- if (questionsResponse && questionsResponse.questions && questionsResponse.questions.length > 0) {
977
- this.questions = questionsResponse.questions;
978
- this.currentQuestion = this.questions[0];
979
- this.currentQuestionIndex = 0;
980
- this.questionCount = this.questions.length;
981
- this.questionNumber = 1;
982
- // Optionally, update UI to show the first question
983
- } else {
984
- this.questions = [];
985
- this.currentQuestionIndex = -1;
 
 
 
 
 
 
 
986
  }
 
 
 
 
 
 
 
987
  }
988
- this.isSessionStarted = true;
989
- this.investigationStarted = true;
990
- this.investigationActive = true;
991
- this.isLoading = false;
992
- } catch (error) {
993
- alert('Failed to connect to backend. Please check if the Flask server is running on port 5002.');
994
- this.isLoading = false;
995
- }
996
  }
997
  public isVoiceRecording: boolean = false;
998
 
999
- // Start audio recording and speech recognition
1000
- public async startAudioRecording() {
1001
- // Debounce: Prevent rapid start
1002
- if (this.isVoiceRecording) return;
1003
- // Clear previous answer before starting new recording
1004
- this.textAnswer = '';
1005
- console.log('[PyDetect] Voice recording started.');
1006
- try {
1007
- // Request microphone access
1008
- this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
1009
- // Setup MediaRecorder
1010
- const mimeType = this.chooseMimeType();
1011
- this.mediaRecorder = new MediaRecorder(this.mediaStream, { mimeType });
1012
- this.audioChunks = [];
1013
- this.mediaRecorder.ondataavailable = (e) => {
1014
- if (e.data && e.data.size > 0) this.audioChunks.push(e.data);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1015
  };
1016
- this.mediaRecorder.onstop = () => {
1017
- console.log('[PyDetect] Voice recording stopped.');
1018
- // Stop speech recognition when recording stops
1019
- if (this.recognition) {
1020
- try { this.recognition.stop(); } catch {}
 
 
1021
  }
1022
- // UI feedback if no transcript
 
 
 
 
 
 
1023
  if (!this.transcriptSoFar) {
1024
  this.infoText = 'No voice detected. Please try again or type your answer.';
1025
  } else {
1026
  this.infoText = 'Voice recording stopped.';
1027
  }
1028
  };
1029
- this.mediaRecorder.start();
1030
- // Setup speech recognition
1031
- const Ctor = window.webkitSpeechRecognition || window.SpeechRecognition;
1032
- if (Ctor) {
1033
- this.recognition = new Ctor();
1034
- this.recognition.lang = 'en-IN';
1035
- this.recognition.continuous = true;
1036
- this.recognition.interimResults = false;
1037
- this.transcriptSoFar = '';
1038
- this.recognition.onstart = () => {
1039
- this.infoText = 'Listening...';
1040
- };
1041
- this.recognition.onresult = (event: any) => {
1042
- let finalText = '';
1043
- for (let i = event.resultIndex; i < event.results.length; i++) {
1044
- const result = event.results[i];
1045
- if (result.isFinal) {
1046
- finalText += result[0].transcript.trim();
1047
- }
1048
- }
1049
- this.transcriptSoFar = finalText.trim();
1050
- this.textAnswer = this.transcriptSoFar;
1051
- };
1052
- this.recognition.onerror = (error: any) => {
1053
- this.infoText = 'Speech recognition error: ' + error.error;
1054
- };
1055
- this.recognition.onend = () => {
1056
- if (!this.transcriptSoFar) {
1057
- this.infoText = 'No voice detected. Please try again or type your answer.';
1058
- } else {
1059
- this.infoText = 'Voice recording stopped.';
1060
- }
1061
- };
1062
- this.recognition.start();
1063
- } else {
1064
- this.infoText = 'Speech Recognition not supported.';
1065
- }
1066
- this.isVoiceRecording = true;
1067
- } catch (err) {
1068
- this.infoText = 'Could not start audio recording.';
1069
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1070
  }
1071
- public async toggleVoiceRecording() {
1072
- if (this.isVoiceRecording) {
1073
- this.stopAudioRecording();
1074
- this.isVoiceRecording = false;
1075
- this.infoText = 'Voice recording stopped.';
1076
- } else {
1077
- await this.startAudioRecording();
1078
- this.isVoiceRecording = true;
1079
- this.infoText = 'Voice recording started. Speak your answer.';
1080
  }
1081
- }
1082
 
1083
- // Ensure the class is properly closed
1084
  }
 
197
  ttsEnabled: boolean = false;
198
  isListening: boolean = false;
199
  speechRecognition: any = null;
200
+ // Combined answer submission: prefer text box, fallback to transcript
201
+ public submitCombinedAnswer() {
202
+ // Accept answer from text box or voice transcript
203
+ let answerText = (this.textAnswer && this.textAnswer.trim()) ? this.textAnswer.trim() : (this.transcriptSoFar && this.transcriptSoFar.trim()) ? this.transcriptSoFar.trim() : '';
204
+ if (!answerText || !this.sessionId || !this.questions[this.currentQuestionIndex]) {
205
+ this.infoText = 'Please provide your answer before submitting.';
206
+ return;
207
+ }
208
+ this.stopAudioRecording();
209
+ this.infoText = 'Submitting answer...';
210
+ this.textAnswer = '';
211
+ this.transcriptSoFar = '';
212
+ const endTs = Date.now();
213
+ this.answerEndAt = endTs;
214
+ if (!this.answerStartAt) this.answerStartAt = this.questionWindowStartAt || endTs;
215
+ const durationMs = this.answerEndAt - this.answerStartAt;
216
+ this.stopFrameStreaming();
217
+ this.pyDetectService.submitResponse(
218
+ this.sessionId,
219
+ answerText,
220
+ this.questions[this.currentQuestionIndex],
221
+ {
222
+ answer_start_at: this.answerStartAt,
223
+ answer_end_at: this.answerEndAt,
224
+ duration_ms: durationMs,
225
+ mode: this.answerMode
226
+ }
227
+ ).subscribe({
228
+ next: async (res) => {
229
+ // Extract truth score if present
230
+ this.truthScore = (res && (res.truth_score || res.score)) ? Number(res.truth_score || res.score) : null;
231
+ this.infoText = 'Answer submitted.' + (this.truthScore !== null ? ` Truth Score: ${this.truthScore}` : '');
232
+ // Pull involvement metrics
233
+ this.fetchLatestInvolvement();
234
+ // Fetch next question from backend
235
+ const response = await this.pyDetectService.askQuestion(
236
+ this.sessionId,
237
+ this.crimeType,
238
+ this.briefDescription
239
+ ).toPromise();
240
+ if (response && response.question) {
241
+ this.questions.push(response.question);
242
+ this.currentQuestionIndex++;
243
+ this.questionNumber = this.currentQuestionIndex + 1;
244
+ this.cdr.detectChanges();
245
+ // await this.startCamera();
246
+ await this.startVideoRecording();
247
+ await this.speakQuestion(response.question);
248
+ // Restart window for next question
249
+ this.startQuestionWindow();
250
+ // Reset answer timing
251
+ this.answerStartAt = null;
252
+ this.answerEndAt = null;
253
+ this.answerMode = 'text';
254
+ } else {
255
+ this.infoText = 'No more questions.';
256
+ this.showSummary = true;
257
+ }
258
+ },
259
+ error: () => {
260
+ this.infoText = 'Error submitting answer.';
261
+ }
262
+ });
263
+ }
264
  public stopAudioRecording() {
265
  if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
266
  this.mediaRecorder.stop();
 
269
  this.infoText = 'Voice recording stopped.';
270
  // Stop speech recognition when recording stops
271
  if (this.recognition) {
272
+ try { this.recognition.stop(); } catch { }
273
  }
274
  }
275
  }
 
491
  private async fetchNextQuestion(): Promise<string> {
492
  // Replace this with HTTP call to your backend if needed.
493
  // Example: const { question } = await this.http.get<{question:string}>('/api/next-question').toPromise();
494
+ // Legacy fetchNextQuestion logic removed. Use backend-driven workflow only.
495
+ return '';
496
  }
497
 
498
  // ======== TTS (question playback) ========
 
535
  audio: { channelCount: 1, echoCancellation: true, noiseSuppression: true },
536
  video: false
537
  });
538
+ // ...existing code...
539
 
540
  // 2) prepare MediaRecorder
541
  const mime = this.chooseMimeType();
 
567
  this.stopAnalyser();
568
  this.stopRecognition();
569
  this.cleanupMediaStream();
570
+ // ...existing code...
571
 
572
  // 7) build audio URL
573
  const blob = new Blob(this.audioChunks, { type: mime });
 
586
  private waitForSilenceOrContinue() {
587
  if (this.silenceTimeout) clearTimeout(this.silenceTimeout);
588
  this.silenceTimeout = setTimeout(() => {
589
+ // ...existing code...
590
  }, 5002); // Timeout after 5 seconds of silence
591
  }
592
 
 
613
  this.sourceNode.connect(this.analyser);
614
 
615
  // Use correct constructor for Float32Array
616
+ this.analyserBuffer = new Float32Array(this.analyser.fftSize);
617
 
618
  const tick = () => {
619
  if (!this.analyser || !this.analyserBuffer) return;
 
723
  };
724
  }
725
 
726
+ private removeFillerWords(text: string): string {
727
  const fillerWords = ['um', 'ah', 'like', 'you know', 'so', 'actually', 'basically'];
728
  const regex = new RegExp(`\\b(${fillerWords.join('|')})\\b`, 'gi');
729
  return text.replace(regex, '').replace(/\s+/g, ' ').trim(); // Clean extra spaces after removal
 
894
  public async onStartInvestigation() {
895
  this.isLoading = true;
896
  this.infoText = 'Starting investigation...';
897
+ // await this.startCamera();
898
  await this.startVideoRecording();
899
  await this.startSession();
900
  // Ensure questions are loaded and index is set
901
  // Use fallback logic for brief description
902
  let briefDescriptionToSend = this.briefDescription?.trim() || '';
903
  if (!briefDescriptionToSend) {
904
+ briefDescriptionToSend =
905
+ sessionStorage.getItem('briefDescription')?.trim() ||
906
+ this.caseData?.briefDescription?.trim() ||
907
+ this.caseData?.police?.information?.trim() ||
908
+ this.caseData?.crime?.trim() ||
909
  '';
910
  }
911
  const response = await this.pyDetectService.askQuestion(
 
935
  }
936
 
937
  // Backend-driven session start and first question fetch
938
+ public async startSession(): Promise<void> {
939
+ try {
940
+ this.isLoading = true;
941
+ if (this.voiceSupported) {
942
+ this.ttsEnabled = true;
943
+ setTimeout(() => {
944
+ this.speakQuestion('Investigation starting. I will ask you questions and you can respond using voice or text.');
945
+ }, 1000);
946
+ }
947
+ let briefDescriptionToSend = this.briefDescription?.trim() || '';
948
+ if (!briefDescriptionToSend) {
949
+ briefDescriptionToSend =
950
+ sessionStorage.getItem('briefDescription')?.trim() ||
951
+ this.caseData?.briefDescription?.trim() ||
952
+ this.caseData?.police?.information?.trim() ||
953
+ this.caseData?.crime?.trim() ||
954
+ '';
955
+ }
956
+ const sessionResponse = await this.pyDetectService.startSession(briefDescriptionToSend).toPromise();
957
+ this.sessionId = sessionResponse.session_id;
958
+ sessionStorage.setItem('sessionId', this.sessionId);
959
+ localStorage.setItem('sessionId', this.sessionId);
960
+ const caseData = this.caseData || {};
961
+ const caseDataToSend = {
962
+ ...caseData,
963
+ brief_description: briefDescriptionToSend
964
+ };
965
+ await this.pyDetectService.submitCaseDetails(
 
 
 
 
 
 
966
  this.sessionId,
967
+ caseDataToSend,
968
  briefDescriptionToSend
969
  ).toPromise();
970
+ if (briefDescriptionToSend && briefDescriptionToSend.length > 0) {
971
+ const questionsResponse = await this.pyDetectService.askQuestion(
972
+ this.sessionId,
973
+ this.crimeType,
974
+ briefDescriptionToSend
975
+ ).toPromise();
976
+ if (questionsResponse && questionsResponse.questions && questionsResponse.questions.length > 0) {
977
+ this.questions = questionsResponse.questions;
978
+ this.currentQuestion = this.questions[0];
979
+ this.currentQuestionIndex = 0;
980
+ this.questionCount = this.questions.length;
981
+ this.questionNumber = 1;
982
+ // Optionally, update UI to show the first question
983
+ } else {
984
+ this.questions = [];
985
+ this.currentQuestionIndex = -1;
986
+ }
987
  }
988
+ this.isSessionStarted = true;
989
+ this.investigationStarted = true;
990
+ this.investigationActive = true;
991
+ this.isLoading = false;
992
+ } catch (error) {
993
+ alert('Failed to connect to backend. Please check if the Flask server is running on port 5002.');
994
+ this.isLoading = false;
995
  }
 
 
 
 
 
 
 
 
996
  }
997
  public isVoiceRecording: boolean = false;
998
 
999
+ // Start audio recording and speech recognition
1000
+ public async startAudioRecording() {
1001
+ // Debounce: Prevent rapid start
1002
+ if (this.isVoiceRecording) return;
1003
+ // Clear previous answer before starting new recording
1004
+ this.textAnswer = '';
1005
+ console.log('[PyDetect] Voice recording started.');
1006
+ try {
1007
+ // Request microphone access
1008
+ this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
1009
+ // Setup MediaRecorder
1010
+ const mimeType = this.chooseMimeType();
1011
+ this.mediaRecorder = new MediaRecorder(this.mediaStream, { mimeType });
1012
+ this.audioChunks = [];
1013
+ this.mediaRecorder.ondataavailable = (e) => {
1014
+ if (e.data && e.data.size > 0) this.audioChunks.push(e.data);
1015
+ };
1016
+ this.mediaRecorder.onstop = () => {
1017
+ console.log('[PyDetect] Voice recording stopped.');
1018
+ // Stop speech recognition when recording stops
1019
+ if (this.recognition) {
1020
+ try { this.recognition.stop(); } catch { }
1021
+ }
1022
+ // UI feedback if no transcript
1023
+ if (!this.transcriptSoFar) {
1024
+ this.infoText = 'No voice detected. Please try again or type your answer.';
1025
+ } else {
1026
+ this.infoText = 'Voice recording stopped.';
1027
+ }
1028
+ };
1029
+ this.mediaRecorder.start();
1030
+ // Setup speech recognition
1031
+ const Ctor = window.webkitSpeechRecognition || window.SpeechRecognition;
1032
+ if (Ctor) {
1033
+ this.recognition = new Ctor();
1034
+ this.recognition.lang = 'en-IN';
1035
+ this.recognition.continuous = true;
1036
+ this.recognition.interimResults = false;
1037
+ this.transcriptSoFar = '';
1038
+ this.recognition.onstart = () => {
1039
+ this.infoText = 'Listening...';
1040
  };
1041
+ this.recognition.onresult = (event: any) => {
1042
+ let finalText = '';
1043
+ for (let i = event.resultIndex; i < event.results.length; i++) {
1044
+ const result = event.results[i];
1045
+ if (result.isFinal) {
1046
+ finalText += result[0].transcript.trim();
1047
+ }
1048
  }
1049
+ this.transcriptSoFar = finalText.trim();
1050
+ this.textAnswer = this.transcriptSoFar;
1051
+ };
1052
+ this.recognition.onerror = (error: any) => {
1053
+ this.infoText = 'Speech recognition error: ' + error.error;
1054
+ };
1055
+ this.recognition.onend = () => {
1056
  if (!this.transcriptSoFar) {
1057
  this.infoText = 'No voice detected. Please try again or type your answer.';
1058
  } else {
1059
  this.infoText = 'Voice recording stopped.';
1060
  }
1061
  };
1062
+ this.recognition.start();
1063
+ } else {
1064
+ this.infoText = 'Speech Recognition not supported.';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1065
  }
1066
+ this.isVoiceRecording = true;
1067
+ } catch (err) {
1068
+ this.infoText = 'Could not start audio recording.';
1069
+ }
1070
+ }
1071
+ public async toggleVoiceRecording() {
1072
+ if (this.isVoiceRecording) {
1073
+ this.stopAudioRecording();
1074
+ this.isVoiceRecording = false;
1075
+ this.infoText = 'Voice recording stopped.';
1076
+ } else {
1077
+ await this.startAudioRecording();
1078
+ this.isVoiceRecording = true;
1079
+ this.infoText = 'Voice recording started. Speak your answer.';
1080
  }
 
 
 
 
 
 
 
 
 
1081
  }
 
1082
 
1083
+ // Ensure the class is properly closed
1084
  }
src/app/recordpage/recordpage.component.css CHANGED
@@ -1,2025 +1,1042 @@
1
- /* ===== Header ===== */
2
  :root {
3
- --masthead-min-height:140px;
4
- --analytics-blue-height:110px;
5
- --primary-accent: #2563eb;
6
- --primary-accent-light: #38bdf8;
7
- --primary-accent-dark: #1e40af;
8
- --secondary-accent: #7c3aed;
9
- --card-radius:14px;
10
- --card-shadow:06px24px rgba(30,41,59,0.13);
11
- --section-gap:32px;
12
- }
13
-
14
- body, main.content {
15
- background: #f4f6fa;
16
- min-height:100vh;
17
- margin:0;
18
- overflow-y: auto;
19
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
20
- animation: fadeInPage0.7s cubic-bezier(0.4,0.2,0.2,1) both;
21
- }
22
-
23
- @keyframes fadeInPage {
24
- from { opacity:0; transform: translateY(32px); }
25
- to { opacity:1; transform: none; }
26
- }
27
-
28
- /* Card hover animation */
29
- .record-card, .summary-card, .section-block, .subgroup-block {
30
- transition: box-shadow 0.25s, transform 0.18s;
31
- }
32
- .record-card:hover, .summary-card:hover, .section-block:hover, .subgroup-block:hover {
33
- box-shadow:08px 32px #2563eb33,02px 8px #38bdf822;
34
- /*transform: translateY(-2px) scale(1.012);*/
35
- }
36
-
37
- /* Table row hover animation */
38
- .record-table tr {
39
- transition: background 0.18s, box-shadow 0.18s, transform 0.18s;
40
- }
41
- .record-table tr:hover td {
42
- background: #e0f2fe;
43
- box-shadow:02px 12px #38bdf822;
44
- transform: scale(1.01);
45
- }
46
-
47
- /* Button and icon-btn animation */
48
- .btn, .icon-btn {
49
- transition: background 0.18s, color 0.18s, box-shadow 0.18s, transform 0.18s;
50
- }
51
- .btn:hover, .icon-btn:hover {
52
- transform: scale(1.08) rotate(-2deg);
53
- box-shadow:04px 16px #2563eb33;
54
  }
55
 
56
- /* Modal fade/slide in */
57
- .modal-backdrop {
58
- animation: fadeInModalBg0.4s cubic-bezier(0.4,0.2,0.2,1) both;
59
- }
60
- @keyframes fadeInModalBg {
61
- from { opacity:0; }
62
- to { opacity:1; }
63
- }
64
- .modal {
65
- animation: slideInModal0.5s cubic-bezier(0.4,0.2,0.2,1) both;
66
- }
67
- @keyframes slideInModal {
68
- from { opacity:0; transform: translateY(-40px) scale(0.98); }
69
- to { opacity:1; transform: none; }
70
  }
71
 
72
- /* Status badge pulse for Open */
73
- .status-label.status-open, .status-open {
74
- animation: pulseStatusOpen1.6s infinite alternate;
75
- }
76
- @keyframes pulseStatusOpen {
77
- from { box-shadow:0000px #22c55e44; }
78
- to { box-shadow:0008px #22c55e11; }
79
  }
80
 
81
- /* Animated icons */
82
- .icon-btn .fa-spin, .summary-icon .fa-spin {
83
- animation: fa-spin1.2s infinite linear;
84
- }
85
- @keyframes fa-spin {
86
- 0% { transform: rotate(0deg); }
87
- 100% { transform: rotate(359deg); }
88
- }
89
- .icon-btn .fa-bounce, .summary-icon .fa-bounce {
90
- animation: fa-bounce1.2s infinite alternate;
91
- }
92
- @keyframes fa-bounce {
93
- 0% { transform: translateY(0); }
94
- 100% { transform: translateY(-8px); }
95
- }
96
- .icon-btn .fa-beat, .summary-icon .fa-beat {
97
- animation: fa-beat1.1s infinite alternate;
98
- }
99
- @keyframes fa-beat {
100
- 0% { transform: scale(1); }
101
- 100% { transform: scale(1.18); }
102
  }
103
 
104
- /* Subtle fade for overlays */
105
- .modal-blur-overlay {
106
- animation: fadeInModalBg0.4s cubic-bezier(0.4,0.2,0.2,1) both;
107
- }
 
 
 
 
 
 
108
 
109
- /* Modern Searchbar animation */
110
- .modern-searchbar-form {
111
- transition: box-shadow 0.18s, transform 0.18s;
112
- }
113
- .modern-searchbar-form:focus-within {
114
- box-shadow:06px 24px #38bdf855;
115
- transform: scale(1.03);
116
- }
117
 
118
- /* ===== Header ===== */
119
- :root {
120
- --masthead-min-height: 140px;
121
- --analytics-blue-height:110px; /* Default, can be overridden inline or in other CSS */
122
- --primary-accent: #2563eb;
123
- --primary-accent-light: #38bdf8;
124
- --primary-accent-dark: #1e40af;
125
- --secondary-accent: #7c3aed;
126
- --card-radius:14px;
127
- --card-shadow:06px24px rgba(30,41,59,0.13);
128
- --section-gap:32px;
129
- }
130
-
131
- .masthead {
132
  display: flex;
133
  align-items: center;
134
- gap: 24px;
135
- padding: 24px 24px 12px;
136
- min-height: var(--masthead-min-height);
137
- background: transparent;
138
- margin-bottom: 48px;
139
- }
140
-
141
-
142
- .logo {
143
- width: 120px;
144
- height: 120px;
145
- margin-left: 0;
146
- margin-top: 0;
147
- position: absolute;
148
- left: 0;
149
- top: 0;
150
- z-index: 40;
151
  }
152
 
153
- .logo-fixed {
154
- position: fixed;
155
- top: 0;
156
- left: 0;
157
- width: 120px;
158
- height: 120px;
159
- z-index: 100;
160
- margin: 0;
161
  }
162
 
163
- /* Add gap between logo and browser border */
164
- .logo-img {
165
- width: 6vw;
166
- position: fixed;
167
- top: 21px;
168
- left: 36px;
169
- z-index: 300;
170
- margin: 0;
171
- background: #ffffff;
172
- max-width: 4.2vw;
173
- min-width: 56px;
174
- height: auto;
175
  border-radius: 50%;
176
- padding: 4px;
177
- box-shadow: 04px 10px rgba(0, 0, 0, 0.25);
178
- transition: transform .25s ease;
 
 
179
  }
180
 
181
- /* Move Py-Detect text further down by increasing top value of .logo-title-row */
182
- .logo-title-row {
183
- display: flex;
184
- flex-direction: row;
185
- align-items: center;
186
- position: absolute;
187
- top: 180px; /* Increased from 120px to move text further down */
188
- left: 48px;
189
- z-index: 300;
190
- gap: 32px;
191
- justify-content: flex-start;
192
- width: 100%;
193
- }
194
 
195
- .py-detect-title {
196
- position: fixed;
197
- margin-left: 97px;
198
- margin-top: -228px;
199
- text-align: left;
200
- font-size: 3vw;
201
- color: #38bdf8;
202
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
203
  font-weight: 900;
204
- letter-spacing: 6px;
205
- background: none;
206
- border: none;
207
- box-shadow: none;
208
- min-width: unset;
209
- max-width: unset;
210
  }
211
 
212
- /* Py-Detect title: each letter with its own color */
213
- .py-detect-title .py-letter.p {
 
 
214
  color: #e3f6ff;
215
  text-shadow: 0 0 6px #38bdf8;
216
  }
217
 
218
- .py-detect-title .py-letter.y {
 
 
 
219
  color: #38bdf8;
220
  text-shadow: 0 0 6px #38bdf8;
221
  }
222
 
223
- .py-detect-title .py-shape {
224
- color: #e3f6ff;
225
- background: #e3f6ff;
226
- text-shadow: 0 0 6px #38bdf8;
227
- box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff;
228
- border: 2px solid #23272b;
229
- }
230
-
231
- .py-shape {
232
- display: inline-block;
233
- width: 18px;
234
- height: 4px;
235
- background: #38bdf8;
236
- margin: 0 8px;
237
- vertical-align: middle;
238
- border-radius: 2px;
239
- box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff;
240
- border: 2px solid #23272b;
241
- }
242
-
243
- .py-detect-title .py-letter.d {
244
- color: #e3f6ff;
245
- text-shadow: 0 0 6px #38bdf8;
246
- }
247
-
248
- .py-detect-title .py-letter.e {
249
- color: #38bdf8;
250
- text-shadow: 0 0 6px #38bdf8;
251
- }
252
-
253
- .py-detect-title .py-letter.t {
254
- color: #e3f6ff;
255
- text-shadow: 0 0 6px #38bdf8;
256
- }
257
-
258
- .py-detect-title .py-letter.e2 {
259
- color: #38bdf8;
260
- text-shadow: 0 0 6px #38bdf8;
261
- }
262
-
263
- .py-detect-title .py-letter.c {
264
- color: #e3f6ff;
265
- text-shadow: 0 0 6px #38bdf8;
266
- }
267
-
268
- .py-detect-title .py-letter.t2 {
269
- color: #38bdf8;
270
- text-shadow: 0 0 6px #38bdf8;
271
- }
272
-
273
-
274
  .py-shape {
275
  display: inline-block;
276
- width: 18px;
277
  height: 4px;
278
- background: #38bdf8;
279
- margin: 0 8px;
280
- vertical-align: middle;
281
  border-radius: 2px;
282
- box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff;
283
- border: 2px solid #23272b;
284
  }
285
 
286
- /*.title {
287
- font-weight: 800;
288
- margin-bottom: 12px;
289
- }*/
290
- /* header tools */
291
- .toolbar {
292
  display: flex;
293
- justify-content: flex-end;
294
  align-items: center;
295
- position: absolute;
296
- right: 32px;
297
- top: -211%;
298
- transform: translateY(-50%);
299
- width: auto;
300
- margin-bottom: 0;
301
- z-index: 2;
302
  }
303
 
304
- .search {
305
- padding: 10px;
306
- border: 1px solid #cbd5e0;
 
307
  border-radius: 6px;
308
- width: min(360px, 100%);
309
- }
310
-
311
- /* layout: keep table centered and scrollable */
312
- .padded-table-wrap {
313
- width: 100%;
314
- padding-left: 48px;
315
- padding-right: 48px;
316
- box-sizing: border-box;
317
  display: flex;
318
- justify-content: center;
319
- background: rgba(30, 41, 59, 0.55); /* semi-transparent dark background */
320
- border-radius: 18px;
321
- box-shadow: 0 4px 32px rgba(30,41,59,0.12);
322
  position: relative;
323
- z-index: 1;
 
324
  }
325
 
326
- .table-wrap {
327
- width: 100%;
328
- max-width: 1700px;
329
- margin: 0 auto;
330
- max-height: 80vh;
331
- min-height: 252px;
332
- overflow: auto;
333
  }
334
 
335
- /* --- Modern Table Refactor Inspired by Example --- */
336
-
337
- .record-table {
338
- width:100%;
339
- border-collapse: separate;
340
- border-spacing:0;
341
- background: #fff;
342
- border-radius:12px;
343
- box-shadow:0 2px 12px #2563eb11;
344
- overflow: hidden;
345
- }
346
-
347
- .record-table th, .record-table td {
348
- padding:14px 14px;
349
- font-size:1.08rem;
350
- border-bottom:1.5px solid #e5e7eb;
351
- text-align: left;
352
- white-space: nowrap;
353
- }
354
-
355
- .record-table th {
356
- background: #f8fafc;
357
- color: #222b45;
358
- font-weight:700;
359
- font-size:1.08rem;
360
- letter-spacing:0.5px;
361
- border-bottom:2.5px solid #e5e7eb;
362
- text-shadow: none;
363
- position: sticky;
364
- top:0;
365
- z-index:2;
366
- }
367
-
368
- .record-table th i {
369
- color: #e50808;
370
- margin-right: 7px;
371
- font-size: 1.1em;
372
  }
373
 
374
- .record-table td {
375
- background: #fff;
376
- font-size:1.05rem;
377
- color: #23272b;
378
- vertical-align: middle;
379
- }
380
-
381
- /* Remove alternate row color: force all rows to white */
382
- .record-table tr:nth-child(odd) td {
383
- background: #fff !important;
384
- }
385
-
386
- .record-table tr:hover td {
387
- background: #e0f2fe;
388
- transition: background 0.18s;
389
- }
390
-
391
- /* Checkbox column */
392
- .record-table td.select-col, .record-table th.select-col {
393
- width:36px;
394
- text-align: center;
395
- padding-left:10px;
396
- padding-right:10px;
397
- }
398
-
399
- /* Status badge */
400
- .status-label {
401
- display: inline-flex;
402
- align-items: center;
403
- gap:6px;
404
- padding:3px 14px;
405
- border-radius:12px;
406
- font-weight:700;
407
- font-size:1em;
408
- letter-spacing:0.5px;
409
- min-width:70px;
410
- text-align: center;
411
- background: #e0e7ff;
412
- color: var(--primary-accent);
413
- box-shadow:0 2px 8px #2563eb11;
414
  }
415
- .status-dot {
416
- display: inline-block;
417
- width:9px;
418
- height:9px;
419
- border-radius:50%;
420
- margin-right:4px;
421
- }
422
- .status-open { background: #d1fae5; color: #059669; }
423
- .status-open .status-dot { background: #059669; }
424
- .status-under { background: #dbeafe; color: #2563eb; }
425
- .status-under .status-dot { background: #2563eb; }
426
- .status-closed { background: #fee2e2; color: #dc2626; }
427
- .status-closed .status-dot { background: #dc2626; }
428
-
429
- .record-table td.actions-col, .record-table th.actions-col {
430
- min-width:80px;
431
- text-align: left;
432
- padding-right:18px;
433
- }
434
-
435
- .icon-btn {
436
- margin:02px;
437
- font-size:1.18em;
438
- border-radius:6px;
439
- padding:6px 10px;
440
- transition: background 0.15s, color 0.15s, box-shadow 0.15s;
441
- background: none;
442
- border: none;
443
- cursor: pointer;
444
- }
445
- .icon-btn.verify { color: #22c55e; }
446
- .icon-btn.view { color: #2563eb; }
447
- .icon-btn.edit { color: #7c3aed; }
448
- .icon-btn.delete { color: #ef4444; }
449
- .icon-btn:hover { background: #f0f7ff; }
450
- .icon-btn.delete:hover { background: #fff0f0; color: #b91c1c; }
451
- .icon-btn.edit:hover { background: #f3e8ff; color: #5b21b6; }
452
- .icon-btn.view:hover { background: #e0f2fe; color: #0ea5e9; }
453
- .icon-btn.verify:hover { background: #e0ffe6; color: #15803d; }
454
-
455
- /* Responsive: stack columns on small screens */
456
- @media (max-width:900px) {
457
- .record-table th, .record-table td {
458
- padding:10px6px;
459
- font-size:0.98rem;
460
- }
461
- .record-table {
462
- font-size:0.98rem;
463
- }
464
  }
465
 
466
- .empty {
467
- text-align: center;
468
- color: #718096;
469
- padding: 24px;
470
- font-size: 1.1em;
 
471
  }
472
 
473
- .records {
474
- width: 100%;
475
- min-width: 1500px;
476
- max-width: 1700px;
477
- border-collapse: collapse;
478
- background: #fff;
479
- border: 1px solid #e2e8f0;
480
- border-radius: 8px;
481
  margin: 0 auto;
 
482
  }
483
 
484
- .record-table th,
485
- .record-table td {
486
- white-space: nowrap;
487
- min-width: 90px;
 
 
 
 
 
488
  }
489
 
490
- .record-table td {
491
- vertical-align: middle;
 
 
 
 
 
 
 
 
 
 
492
  }
493
 
494
- .record-table th {
495
- vertical-align: middle;
 
 
496
  }
497
 
498
- .record-table td a {
499
- white-space: nowrap;
500
- display: inline-block;
501
- min-width: 80px;
 
 
 
502
  }
503
 
504
- @media (max-width: 1800px) {
505
- .records {
506
- max-width: 100vw;
507
- min-width: 1000px;
508
  }
509
 
510
- .table-wrap {
511
- max-width: 100vw;
512
- min-height: 400px;
513
- }
 
 
 
 
 
 
514
  }
515
 
516
- @media (max-width: 900px) {
517
- .records {
518
- min-width: 0;
519
- width: 100%;
520
- max-width: 100vw;
521
  }
522
 
523
- .table-wrap {
524
- min-height: 200px;
525
- }
526
 
527
- .padded-table-wrap {
528
- padding-left: 4px;
529
- padding-right: 4px;
530
- }
531
 
532
- .records-header-row {
533
- flex-direction: column;
534
- align-items: stretch;
535
- gap: 12px;
536
- padding-left: 4px;
537
- padding-right: 4px;
538
- }
 
 
 
539
 
540
- .page-title {
541
- text-align: center;
542
- margin-right: 0;
 
 
543
  }
544
 
545
- .toolbar {
546
- position: static;
547
- transform: none;
548
- width: 100%;
549
- justify-content: flex-end;
550
- margin-top: 8px;
551
  }
552
- }
553
-
554
- .records th, .records td {
555
- padding: 10px 12px;
556
- border-bottom: 1px solid #edf2f7;
557
- text-align: left;
558
- white-space: nowrap;
559
- min-width: 90px;
560
- }
561
 
562
- .records td {
563
- vertical-align: middle;
 
 
564
  }
565
 
566
- .records th {
567
- vertical-align: middle;
 
 
 
 
 
 
 
 
 
 
 
568
  }
569
 
570
- .records td a {
571
- white-space: nowrap;
572
- display: inline-block;
573
- min-width: 80px;
574
- }
575
 
576
- .records thead th {
577
- position: sticky;
578
- top: 0;
579
- z-index: 1;
580
- background: linear-gradient(90deg, #23272b 0%, #0ea5e9 100%);
581
- color: #fff;
582
- font-weight: 800;
583
- font-size: 1.08rem;
584
- letter-spacing: 0.5px;
585
- box-shadow: 0 2px 8px rgba(30,41,59,0.10);
586
- border-bottom: 2.5px solid #0ea5e9;
587
- text-shadow: 0 1px 4px rgba(30,41,59,0.10);
588
- padding-top: 14px;
589
- padding-bottom: 14px;
590
  }
591
 
592
- .records th.actions-col, .records td.actions {
593
- color: #fff;
594
- text-align: left;
595
- padding-left: 18px;
596
- padding-right: 18px;
597
- width: 1%;
598
- white-space: nowrap;
599
  }
600
 
601
- .records tbody tr:last-child td {
602
- border-bottom: none;
 
 
 
603
  }
604
 
605
- .records tbody tr {
606
- transition: background 0.18s;
607
- }
608
 
609
- .records tbody tr:nth-child(odd) {
610
- background: #fafafa;
611
  }
612
 
613
- .records tbody tr:hover {
614
- background: #eef5ff;
615
  }
616
 
617
- /* column widths */
618
- .records th:nth-child(1), .records td:nth-child(1) {
619
- width: 90px;
 
 
 
 
 
 
620
  }
621
 
622
- .records th:nth-child(2), .records td:nth-child(2) {
623
- width: 140px;
624
  }
625
 
626
- .records th:nth-child(3), .records td:nth-child(3) {
627
- width: 180px;
628
  }
629
 
630
- .records th:nth-child(4), .records td:nth-child(4) {
631
- min-width: 220px;
632
  }
633
 
634
- .records th:nth-child(5), .records td:nth-child(5) {
635
- width: 130px;
 
 
 
 
 
 
 
636
  }
637
 
638
- .records th:nth-child(6), .records td:nth-child(6) {
639
- width: 140px;
 
 
 
 
 
 
 
 
640
  }
641
 
642
- .actions-col {
643
- width: 200px;
 
644
  }
645
 
646
- /* badges + helpers */
647
- .status {
648
- display: inline-block;
649
- padding: 2px 8px;
650
- border-radius: 999px;
651
- font-weight: 600;
652
- font-size: 12px;
 
 
 
 
 
653
  }
654
 
655
- .status.open {
656
- background: #dcfce7;
657
- color: #166534;
 
658
  }
659
 
660
- .status.under {
661
- background: #fff7ed;
662
- color: #9a3412;
663
- }
 
 
 
 
 
 
 
 
664
 
665
- .status.closed {
666
- background: #fee2e2;
667
- color: #991b1b;
668
  }
669
 
670
- .status-label {
671
- display: inline-block;
672
- padding: 3px 16px;
673
- border-radius: 12px;
674
- font-weight: 700;
675
- font-size: 1em;
676
- letter-spacing: 0.5px;
677
- min-width: 70px;
678
- text-align: center;
679
- background: #e5e7eb;
680
- color: #22223b;
681
- }
682
-
683
- .status-open {
684
- background: #d1fae5;
685
- color: #059669;
686
- }
687
 
688
- .status-under {
689
- background: #dbeafe;
690
- color: #2563eb;
691
- }
 
692
 
693
- .status-closed {
694
- background: #fee2e2;
695
- color: #dc2626;
696
- }
 
697
 
698
- .mono {
699
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
700
- }
701
 
702
- .ellipsis {
 
 
703
  overflow: hidden;
704
- text-overflow: ellipsis;
705
- white-space: nowrap;
706
- }
707
-
708
- /* buttons */
709
- .actions {
710
  display: flex;
711
- gap: 8px;
 
712
  }
713
 
714
- .btn {
715
- padding: 8px 12px;
716
- border: 1px solid #cbd5e0;
717
- border-radius: 8px;
718
- background: #f7fafc;
719
- cursor: pointer;
720
  }
721
 
722
- .btn.view {
723
- background: #2b6cb0;
724
- color: #fff;
725
- border-color: #2b6cb0;
726
  }
727
 
728
- .btn.edit {
729
- background: #805ad5;
730
- color: #fff;
731
- border-color: #805ad5;
732
  }
733
 
734
- .btn.delete {
735
- background: #ef4444;
736
- color: #fff;
737
- border-color: #ef4444;
738
- box-shadow: 0 2px 8px #ef444422;
739
- transition: background 0.18s, color 0.18s;
740
  }
741
 
742
- .btn.delete:hover {
743
- background: #991b1b;
744
- color: #fff;
745
  }
746
 
747
- /* Icon buttons */
748
- .icon-btn {
749
- background: none;
750
- border: none;
751
- padding: 6px 10px;
752
- margin: 0 2px;
753
- border-radius: 6px;
754
- cursor: pointer;
755
- font-size: 1.18em;
756
- transition: background 0.15s, color 0.15s, box-shadow 0.15s;
757
- vertical-align: middle;
758
- outline: none;
759
- display: inline-flex;
760
- align-items: center;
761
- justify-content: center;
762
  }
763
 
764
- .icon-btn.view {
765
- margin: 0;
766
- padding: 0 8px;
767
- background: none;
768
- border: none;
769
- cursor: pointer;
770
- color: #2563eb;
771
- font-size: 1.2em;
772
- display: inline-flex;
773
- align-items: center;
774
- justify-content: center;
775
  }
776
 
777
- .icon-btn.edit {
778
- color: #7c3aed;
 
 
 
 
 
 
 
779
  }
780
 
781
- .icon-btn.delete {
782
- color: #ef4444;
783
- }
 
 
784
 
785
- .icon-btn:hover {
786
- background: #f0f7ff;
787
- box-shadow: 0 2px 8px #2563eb22;
788
- }
789
 
790
- .icon-btn.delete:hover {
791
- background: #fff0f0;
792
- color: #b91c1c;
793
- }
794
 
795
- .icon-btn.edit:hover {
796
- background: #f3e8ff;
797
- color: #5b21b6;
798
- }
 
 
 
 
 
 
 
799
 
800
- .icon-btn.view:hover {
801
- background: #e0f2fe;
802
- color: #0ea5e9;
803
  }
804
 
805
- .icon-btn.verify {
806
- color: #22c55e;
807
  }
808
 
809
- .icon-btn.verify:hover {
810
- background: #e0ffe6;
811
- color: #15803d;
812
- }
813
 
814
- .empty {
815
- text-align: center;
816
- color: #718096;
817
- padding: 24px;
 
 
 
818
  }
819
 
820
- /* modal */
821
- .modal-backdrop {
822
- position: fixed;
823
- inset: 0;
824
- background: rgba(0,0,0,.45);
825
  }
826
 
827
- .modal {
828
- position: fixed;
829
- top: 0;
830
- left: 0;
831
- width: 100vw;
832
- height: 100vh;
833
- min-width: 0;
834
- min-height: 0;
835
- max-width: 100vw;
836
- max-height: 100vh;
837
- background: #fff;
838
- border-radius: 0;
839
- box-shadow: none;
840
- overflow-y: auto;
841
- z-index: 2000;
842
- padding: 0;
843
- font-size: 1.13rem;
844
- animation: none;
845
- display: flex;
846
- flex-direction: column;
847
- }
848
 
849
- .modal-header, .modal-footer {
850
- padding: 24px 40px 18px 40px;
851
- background: #f8fafc;
852
- font-weight: 700;
853
- font-size: 1.18rem;
854
- color: #23272b;
855
  }
856
 
857
- .modal-body {
858
- flex: 1 1 auto;
859
- padding: 32px 48px 32px 48px;
860
- background: #fff;
861
- color: #23272b;
862
- font-size: 1.08rem;
863
- overflow-y: auto;
864
  }
865
 
866
- .detail-row {
867
- display: grid;
868
- grid-template-columns: 180px 1fr;
869
- gap: 12px;
870
- padding: 10px 0;
871
- border-bottom: 1px solid #e5e7eb;
 
 
 
 
 
 
 
 
 
 
872
  }
873
 
874
- .detail-row:last-child {
875
- border-bottom: none;
 
876
  }
877
 
878
- .detail-row span {
879
- color: #2563eb;
880
- font-weight: 600;
881
- font-size: 1.07em;
882
- }
883
 
884
- .detail-row b {
885
- color: #23272b;
886
- font-weight: 500;
887
- font-size: 1.07em;
888
  }
889
 
890
- .detail-block {
891
- margin-top: 18px;
892
- margin-bottom: 8px;
893
- }
894
 
895
- .detail-block .label {
896
- font-weight: 700;
897
- color: #2563eb;
898
- margin-bottom: 6px;
899
- font-size: 1.09em;
900
  }
901
 
902
- .explain {
903
- white-space: pre-wrap;
904
- color: #23272b;
905
- font-size: 1.07em;
906
- }
907
 
908
- .modal-footer {
909
- text-align: right;
 
 
 
910
  }
911
 
912
- .btn {
913
- padding: 8px 18px;
914
- border: 1px solid #cbd5e0;
915
- border-radius: 8px;
916
- background: #f7fafc;
917
- cursor: pointer;
918
- font-size: 1.05em;
919
- font-weight: 600;
920
- margin-left: 8px;
921
  }
922
 
923
- .btn:hover {
924
- background: #e0e7ef;
925
- }
 
926
 
927
- .records-center {
928
  display: flex;
929
- flex-direction: column;
930
- align-items: center;
931
- margin-top: 90px;
932
  }
933
 
934
- .records-header-row {
 
 
 
 
 
935
  display: flex;
936
- flex-direction: row;
937
  align-items: center;
938
  justify-content: center;
939
- width: 100%;
940
- margin-bottom: 24px;
941
- padding-left: 48px;
942
- padding-right: 48px;
943
- box-sizing: border-box;
944
- position: relative;
945
- }
946
-
947
- .page-title {
948
- text-align: center;
949
- width: 100%;
950
- color: #fff;
951
- font-weight: 800;
952
- margin-bottom: 32px; /* Increased from 0 to 32px for more space */
953
- margin-right: 0;
954
- font-size: 2.1rem;
955
  position: relative;
956
- z-index: 1;
957
  }
958
 
959
- /* sort indicators */
960
- .sort {
961
- display: inline-block;
962
- width: 0;
963
- height: 0;
964
- border-left: 4px solid transparent;
965
- border-right: 4px solid transparent;
966
- margin-left: 6px;
967
- }
 
 
 
 
 
 
 
 
 
 
 
 
968
 
969
- .sort.asc {
970
- border-bottom: 6px solid #4a5568;
 
971
  }
972
 
973
- .sort.desc {
974
- border-top: 6px solid #4a5568;
 
 
 
 
 
 
975
  }
976
 
977
- /* Remove global blur and pointer-events when modal is open */
978
- /*
979
- .show-details body > *:not(.cdk-overlay-container):not(app-root),
980
- .show-details app-root > *:not(.modal):not(.modal-backdrop) {
981
- filter: blur(8px) !important;
982
- pointer-events: none !important;
983
- user-select: none !important;
 
984
  }
985
- */
986
 
987
- .show-details .modal,
988
- .show-details .modal-backdrop {
989
- filter: none !important;
990
- pointer-events: auto !important;
991
- user-select: auto !important;
992
- z-index: 200;
993
- }
994
 
995
- /* Remove previous ::before blur if present */
996
- .show-details main.content::before {
997
- display: none !important;
 
 
 
 
998
  }
999
 
1000
- /* Modal blur overlay for white shadow and blur effect */
1001
- .modal-blur-overlay {
1002
- position: fixed;
1003
- inset: 0;
1004
- z-index: 199;
1005
- background: rgba(255,255,255,0.45); /* white shadow */
1006
- backdrop-filter: blur(8px);
1007
- pointer-events: none;
1008
- display: block;
 
 
 
 
 
 
 
1009
  }
1010
 
1011
- .show-details .modal-blur-overlay {
1012
- display: block;
 
 
1013
  }
1014
 
1015
- /* Modern Searchbar Styles */
1016
- .modern-searchbar-container {
1017
- width: 100%;
1018
  display: flex;
1019
- justify-content: center;
1020
- margin: 48px 0 32px 0;
1021
- z-index: 10;
 
 
1022
  }
1023
 
1024
- .modern-searchbar-container.compact {
1025
- width: auto;
1026
- margin: 0 0 0 24px;
1027
- align-items: center;
1028
  }
1029
 
1030
- .modern-searchbar-form {
1031
  display: flex;
1032
  align-items: center;
1033
- background: linear-gradient(90deg, #38bdf8 0%, #7c3aed 100%);
1034
- border-radius: 48px;
1035
- box-shadow: 0 4px 24px rgba(30,41,59,0.18);
1036
- padding: 0 16px 0 18px;
1037
- width: min(600px, 90vw);
1038
- height: 72px;
1039
- position: relative;
1040
  }
1041
 
1042
- .modern-searchbar-form.compact {
1043
- height: 44px;
1044
- min-width: 260px;
1045
- width: 320px;
1046
- padding: 0 8px 0 10px;
 
 
 
1047
  }
1048
 
1049
- .modern-searchbar-icon {
1050
  display: flex;
1051
  align-items: center;
1052
- margin-right: 12px;
1053
  }
1054
 
1055
- .modern-searchbar-input {
1056
- flex: 1;
1057
- border: none;
1058
- outline: none;
1059
- background: transparent;
1060
- color: #fff;
1061
- font-size: 1.05rem;
1062
- font-weight: 500;
1063
- letter-spacing: 0.5px;
1064
- padding: 0 6px;
1065
- height: 32px;
 
1066
  }
1067
 
1068
- .modern-searchbar-input::placeholder {
1069
- color: #e0e7ef;
1070
- opacity: 1;
1071
- font-weight: 400;
1072
  }
1073
 
1074
- .modern-searchbar-btn {
1075
- background: #fff;
1076
- color: #7c3aed;
1077
- border: none;
1078
- border-radius: 32px;
1079
- font-size: 0.98rem;
1080
- font-weight: 700;
1081
- padding: 0 18px;
1082
- height: 32px;
1083
- margin-left: 10px;
1084
- cursor: pointer;
1085
- box-shadow: 0 2px 8px rgba(124,58,237,0.10);
1086
- transition: background 0.18s, color 0.18s;
1087
- }
1088
-
1089
- .modern-searchbar-btn:hover {
1090
- background: #ede9fe;
1091
- color: #4f46e5;
1092
  }
1093
 
1094
- .modern-searchbar-form.white-bg {
1095
- background: #fff !important;
1096
- box-shadow: 0 4px 24px rgba(30,41,59,0.10);
 
1097
  }
1098
 
1099
- .modern-searchbar-input.white-bg {
1100
- background: #fff !important;
1101
- color: #23272b !important;
 
 
 
 
 
 
 
 
 
1102
  }
1103
 
1104
- .modern-searchbar-input.white-bg::placeholder {
1105
- color: #64748b !important;
1106
- opacity: 1;
 
 
 
 
 
1107
  }
1108
 
1109
- .modern-searchbar-icon svg {
1110
- stroke: #64748b !important;
 
 
1111
  }
1112
 
1113
- .modern-searchbar-btn {
1114
- background: #64748b;
 
1115
  color: #fff;
1116
- border: none;
1117
- border-radius: 32px;
1118
- font-size: 0.98rem;
1119
- font-weight: 700;
1120
- padding: 0 18px;
1121
- height: 32px;
1122
- margin-left: 10px;
1123
- cursor: pointer;
1124
- box-shadow: 0 2px 8px rgba(124,58,237,0.10);
1125
- transition: background 0.18s, color 0.18s;
1126
  }
1127
 
1128
- .modern-searchbar-btn:hover {
1129
- background: #38bdf8;
1130
- color: #fff;
 
1131
  }
1132
 
 
 
 
1133
 
1134
- .back-colo {
1135
- background: #011329;
1136
- width: 100%;
1137
- height: 7vw;
1138
- position: fixed;
1139
- }
1140
- /* Search bar at top right corner */
1141
- .searchbar-topright {
1142
- position: fixed;
1143
- top: 20px;
1144
- right: 48px;
1145
- z-index: 1001;
1146
- display: flex;
1147
- align-items: center;
1148
  }
1149
 
1150
- .back-colo {
1151
- background: #011329;
1152
- width: 100%;
1153
- height: 7vw;
1154
- position: fixed;
1155
- }
 
 
 
 
 
1156
 
1157
- @media (max-width: 900px) {
1158
- .searchbar-topright {
1159
- top: 12px;
1160
- right: 8px;
1161
- width: 95vw;
1162
- justify-content: flex-end;
1163
  }
1164
 
1165
- .modern-searchbar-form.compact {
 
1166
  width: 100%;
1167
- min-width: 0;
1168
  }
1169
- }
1170
 
1171
- /* Modern UI header styles from infopage */
1172
- .site-header {
1173
- background: #011329;
1174
- box-shadow: 0 2px 12px #38bdf844;
1175
- margin-bottom: 0;
1176
- position: relative;
1177
- z-index: 10;
1178
- padding-bottom: 0;
1179
- }
1180
 
1181
- .header-inner {
1182
- display: flex;
1183
- align-items: center;
1184
- justify-content: space-between;
1185
- padding: 18px 32px 0 32px;
1186
- position: relative;
 
 
 
 
 
 
 
1187
  }
1188
 
1189
- .logo-cluster {
1190
- display: flex;
1191
- align-items: center;
1192
- gap: 18px;
1193
- }
1194
-
1195
- .logo-img-header {
1196
- width: 54px;
1197
- height: 54px;
1198
- border-radius: 50%;
1199
- background: #fff;
1200
- box-shadow: 0 2px 8px rgba(0,0,0,0.18);
1201
- padding: 4px;
1202
- margin-top: -6px;
1203
- margin-bottom: 1vh;
1204
- }
1205
-
1206
- .py-detect-title-header {
1207
- font-size: 2.1rem;
1208
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
1209
- font-weight: 900;
1210
- letter-spacing: 6px;
1211
- color: #38bdf8;
1212
- display: flex;
1213
- align-items: center;
1214
- gap: 2px;
1215
- margin-bottom: 1.5vh;
1216
- }
1217
-
1218
- .py-detect-title-header .py-letter.p {
1219
- color: #e3f6ff;
1220
- text-shadow: 0 0 6px #38bdf8;
1221
- }
1222
-
1223
- .py-detect-title-header .py-letter.y {
1224
- color: #38bdf8;
1225
- text-shadow: 0 0 6px #38bdf8;
1226
- }
1227
-
1228
- .py-detect-title-header .py-shape {
1229
- color: #e3f6ff;
1230
- background: #e3f6ff;
1231
- text-shadow: 0 0 6px #38bdf8;
1232
- box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff;
1233
- border: 2px solid #23272b;
1234
- width: 18px;
1235
- height: 4px;
1236
- display: inline-block;
1237
- margin: 0 8px;
1238
- border-radius: 2px;
1239
- }
1240
-
1241
- .py-detect-title-header .py-letter.d {
1242
- color: #e3f6ff;
1243
- text-shadow: 0 0 6px #38bdf8;
1244
- }
1245
-
1246
- .py-detect-title-header .py-letter.e {
1247
- color: #38bdf8;
1248
- text-shadow: 0 0 6px #38bdf8;
1249
- }
1250
-
1251
- .py-detect-title-header .py-letter.t {
1252
- color: #e3f6ff;
1253
- text-shadow: 0 0 6px #38bdf8;
1254
- }
1255
-
1256
- .py-detect-title-header .py-letter.e2 {
1257
- color: #38bdf8;
1258
- text-shadow: 0 0 6px #38bdf8;
1259
- }
1260
-
1261
- .py-detect-title-header .py-letter.c {
1262
- color: #e3f6ff;
1263
- text-shadow: 0 0 6px #38bdf8;
1264
- }
1265
-
1266
- .py-detect-title-header .py-letter.t2 {
1267
- color: #38bdf8;
1268
- text-shadow: 0 0 6px #38bdf8;
1269
- }
1270
-
1271
- header {
1272
- position: relative;
1273
- z-index: 10;
1274
- }
1275
-
1276
- .header-actions-right {
1277
- position: absolute;
1278
- right: 32px;
1279
- top: 27px;
1280
- display: flex;
1281
- align-items: center;
1282
- z-index: 100;
1283
- }
1284
-
1285
- .logout-btn {
1286
- font-family: 'Montserrat', 'Poppins', 'Arial Black', Arial, sans-serif;
1287
- font-size: 1.05rem;
1288
- font-weight: 700;
1289
- letter-spacing: 2px;
1290
- background: linear-gradient(90deg, #ef4444 0%, #23272b 100%);
1291
- color: #fff;
1292
- box-shadow: 0 2px 16px #ef444488;
1293
- border: none;
1294
- border-radius: 12px;
1295
- padding: 0.55rem 1.3rem;
1296
- margin: 0 0.3rem;
1297
- cursor: pointer;
1298
- transition: background 0.4s, box-shadow 0.4s, color 0.3s, transform 0.2s;
1299
- overflow: hidden;
1300
- }
1301
-
1302
- .logout-btn:hover {
1303
- background: linear-gradient(90deg, #23272b 0%, #ef4444 100%);
1304
- color: #fff;
1305
- box-shadow: 0 2px 24px #ef444488;
1306
- transform: scale(1.04);
1307
- }
1308
-
1309
- .logout-icon {
1310
- font-size: 1.2em;
1311
- margin-right: 6px;
1312
- }
1313
-
1314
- body, main.content {
1315
- background: #f4f6fa;
1316
- min-height: 100vh;
1317
- margin: 0;
1318
- overflow-y: auto; /* enables scrolling */
1319
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
1320
- }
1321
-
1322
- .record-card {
1323
- background: #fff;
1324
- border-radius: 10px;
1325
- box-shadow: 0 2px 8px #0001, 0 1.5px 0 #e5e7eb;
1326
- border: 1.5px solid #e5e7eb;
1327
- margin: 24px auto 0 auto;
1328
- max-width: 98vw;
1329
- width: 98vw;
1330
- min-width: 320px;
1331
- padding: 0;
1332
- overflow-x: auto;
1333
- overflow-y: visible;
1334
- }
1335
-
1336
- .record-header {
1337
- display: flex;
1338
- align-items: center;
1339
- justify-content: space-between;
1340
- padding: 18px 24px 8px 24px;
1341
- border-bottom: 1.5px solid #e5e7eb;
1342
- background: linear-gradient(90deg, #38bdf8 0%, #6366f1 100%);
1343
- border-radius: 10px 10px 0 0;
1344
- }
1345
-
1346
- .record-title-group {
1347
- display: flex;
1348
- align-items: center;
1349
- gap: 12px;
1350
- }
1351
-
1352
- .record-title {
1353
- font-size: 1.25rem;
1354
- font-weight: 700;
1355
- color: #fff;
1356
- }
1357
-
1358
- .record-dropdown {
1359
- font-size: 1.05rem;
1360
- color: #222b45;
1361
- background: #fff;
1362
- border: 1px solid #e5e7eb;
1363
- border-radius: 6px;
1364
- padding: 4px 12px;
1365
- margin-left: 8px;
1366
- }
1367
-
1368
- .record-header-actions {
1369
- display: flex;
1370
- align-items: center;
1371
- gap: 10px;
1372
- }
1373
-
1374
- .record-search {
1375
- border: 1.5px solid #e5e7eb;
1376
- border-radius: 6px;
1377
- padding: 6px 12px;
1378
- font-size: 1rem;
1379
- background: #fff;
1380
- margin-right: 8px;
1381
- min-width: 220px;
1382
- }
1383
-
1384
- .record-new-btn {
1385
- background: #2563eb;
1386
- color: #fff;
1387
- font-weight: 600;
1388
- border: none;
1389
- border-radius: 6px;
1390
- padding: 7px 22px;
1391
- font-size: 1rem;
1392
- box-shadow: 0 2px 8px #2563eb22;
1393
- cursor: pointer;
1394
- transition: background 0.18s;
1395
- }
1396
-
1397
- .record-new-btn:hover {
1398
- background: #1e40af;
1399
- }
1400
-
1401
- .record-table {
1402
- width: 100%;
1403
- border-collapse: separate;
1404
- border-spacing: 0;
1405
- background: #4654ff;
1406
- }
1407
-
1408
- .record-table th, .record-table td {
1409
- border-bottom: 1.5px solid #e5e7eb;
1410
  padding: 12px 16px;
1411
- text-align: left;
1412
- font-size: 1.05rem;
1413
- }
1414
-
1415
- .record-table th {
1416
- background: #f4f6fa;
1417
- color: #222b45;
1418
- font-weight: 700;
1419
  }
1420
 
1421
- .record-table tr:last-child td {
1422
- border-bottom: none;
 
1423
  }
1424
 
1425
- .record-table tr {
1426
- transition: background 0.15s;
 
1427
  }
1428
 
1429
-
1430
-
1431
- .record-table a {
1432
- color: #2563eb;
1433
- text-decoration: underline;
1434
- cursor: pointer;
1435
  }
1436
 
1437
- .record-table .record-status {
1438
- font-weight: 500;
1439
- border-radius: 6px;
1440
- padding: 2px 10px;
1441
- background: #f1f5f9;
1442
- color: #2563eb;
1443
- font-size: 0.98em;
1444
  }
1445
 
1446
- .vertical-sections {
1447
- display: flex;
1448
- flex-direction: column;
1449
- gap: 32px;
1450
- }
1451
-
1452
- .horizontal-sections {
1453
- display: flex;
1454
- flex-direction: column;
1455
- gap: 36px;
1456
- background: linear-gradient(120deg, #f0f7ff 0%, #e0f2fe 100%);
1457
- border-radius: 24px;
1458
- box-shadow: 0 8px 32px #2563eb22;
1459
- padding: 32px 0 32px 0;
1460
- }
1461
-
1462
- .section-block {
1463
- background: rgba(255,255,255,0.98);
1464
- border-radius: 18px;
1465
- box-shadow: 0 4px 24px #38bdf822;
1466
- padding: 28px 38px 18px 38px;
1467
- margin-bottom: 0;
1468
- display: flex;
1469
- flex-direction: column;
1470
- gap: 22px;
1471
- border-left: 6px solid #38bdf8;
1472
- transition: box-shadow 0.2s, border 0.2s;
1473
- }
1474
-
1475
- .section-block:hover {
1476
- box-shadow: 0 8px 32px #2563eb33;
1477
- border-left: 6px solid #2563eb;
1478
  }
1479
 
1480
- .section-title {
1481
- font-size: 1.32em;
1482
- font-weight: 900;
1483
- color: #1d4ed8;
1484
- margin-bottom: 12px;
1485
- letter-spacing: 1px;
1486
- text-shadow: 0 1px 0 #fff;
1487
- display: flex;
1488
- align-items: center;
1489
- gap: 10px;
1490
- }
1491
-
1492
- .subgroup-row {
1493
- display: flex;
1494
- flex-direction: row;
1495
- gap: 36px;
1496
- overflow-x: auto;
1497
- }
1498
-
1499
- .subgroup-block {
1500
- min-width: 260px;
1501
- flex: 1 1 0;
1502
- background: rgba(248,250,252,0.95);
1503
- border-radius: 14px;
1504
- box-shadow: 0 2px 8px #38bdf822;
1505
- padding: 18px 20px 10px 20px;
1506
- margin-bottom: 0;
1507
- margin-top: 8px;
1508
- display: flex;
1509
- flex-direction: column;
1510
- gap: 0;
1511
- border: 1.5px solid #e0e7ef;
1512
- transition: box-shadow 0.18s, border 0.18s, background 0.18s;
1513
- }
1514
-
1515
- .subgroup-block:hover {
1516
- box-shadow: 0 4px 16px #38bdf855;
1517
- border: 1.5px solid #38bdf8;
1518
- background: #e0f2fe;
1519
  }
1520
 
1521
- .subgroup-title {
1522
- font-size: 1.11em;
1523
- font-weight: 700;
1524
- color: #0ea5e9;
1525
- margin-bottom: 12px;
1526
- letter-spacing: 0.5px;
1527
- text-shadow: 0 1px 0 #fff;
1528
- display: flex;
1529
- align-items: center;
1530
- gap: 8px;
1531
- }
1532
-
1533
- .fields-table {
1534
- display: flex;
1535
- flex-direction: column;
1536
- gap: 0;
1537
- background: none;
1538
- box-shadow: none;
1539
- border-radius: 0;
1540
- padding: 0;
1541
- }
1542
-
1543
- .field-row {
1544
- display: flex;
1545
- flex-direction: row;
1546
- align-items: flex-start;
1547
- padding: 14px 0 14px 0;
1548
- border-bottom: 1px solid #e0e7ef;
1549
- font-size: 1.09em;
1550
- background: none;
1551
- box-shadow: none;
1552
- border-radius: 0;
1553
- transition: background 0.18s;
1554
- gap: 32px;
1555
- }
1556
-
1557
- .field-row:last-child {
1558
- border-bottom: none;
1559
- }
1560
-
1561
- .field-row span {
1562
- color: #1e293b;
1563
- font-weight: 700;
1564
- letter-spacing: 0.2px;
1565
- min-width: 180px;
1566
- font-size: 1.08em;
1567
- margin-right: 32px;
1568
- text-align: left;
1569
- display: inline-block;
1570
  }
1571
 
1572
- .field-row b {
1573
- color: #2563eb;
1574
- font-weight: 700;
1575
- word-break: break-word;
1576
- letter-spacing: 0.1px;
1577
- font-size: 1.13em;
1578
- margin-left: 8px;
1579
- text-align: left;
1580
- display: inline-block;
1581
  }
1582
 
1583
- .field-row b:hover {
1584
- color: #0ea5e9;
1585
- }
1586
-
1587
- .subgroup-pills {
1588
- display: flex;
1589
- flex-wrap: wrap;
1590
- gap: 12px;
1591
- margin-bottom: 18px;
1592
- }
1593
-
1594
- .subgroup-pills button {
1595
- background: linear-gradient(90deg, #38bdf8 0%, #2563eb 100%);
1596
- border: none;
1597
- border-radius: 20px;
1598
- padding: 7px 22px;
1599
- font-size: 1em;
1600
- color: #fff;
1601
- font-weight: 700;
1602
- cursor: pointer;
1603
- box-shadow: 0 2px 8px #2563eb22;
1604
- transition: background 0.18s, color 0.18s, box-shadow 0.18s;
1605
- outline: none;
1606
  }
1607
-
1608
- .subgroup-pills button.active,
1609
- .subgroup-pills button:focus {
1610
- background: linear-gradient(90deg, #2563eb 0%, #38bdf8 100%);
1611
- color: #fff;
1612
- box-shadow: 0 4px 16px #38bdf855;
1613
- }
1614
-
1615
- .subgroup-pills button:hover {
1616
- background: linear-gradient(90deg, #0ea5e9 0%, #2563eb 100%);
1617
- color: #fff;
1618
- }
1619
-
1620
- .field-card {
1621
- display: flex;
1622
- width: 30%;
1623
- gap: 9vw;
1624
- justify-content: space-between;
1625
  }
1626
 
1627
- /* Responsive tweaks */
1628
- @media (max-width: 900px) {
1629
- .section-block {
1630
- padding: 18px 8vw 12px 8vw;
1631
  }
1632
 
1633
- .subgroup-block {
1634
- min-width: 180px;
1635
- padding: 12px 8px 8px 8px;
1636
  }
1637
 
1638
- .field-row span {
1639
- min-width: 120px;
1640
  }
1641
- }
1642
 
1643
- @media (max-width: 600px) {
1644
- .section-block {
1645
- padding: 10px 2vw 8px 2vw;
1646
  }
1647
 
1648
- .subgroup-block {
1649
- min-width: 120px;
1650
- padding: 8px 2px 6px 2px;
 
1651
  }
1652
 
1653
- .field-row span {
1654
- min-width: 80px;
1655
- font-size: 0.98em;
1656
- }
1657
-
1658
- .field-row b {
1659
- font-size: 1em;
1660
  }
1661
- }
1662
 
1663
- /* Modern styles for the filter bar, dropdowns, and buttons */
1664
- .filter-bar {
1665
- display: flex;
1666
- gap: 16px;
1667
- align-items: center;
1668
- margin: 24px 0 12px 0;
1669
- padding: 12px 18px;
1670
- background: linear-gradient(90deg, #38bdf8 0%, #6366f1 100%);
1671
- border-radius: 12px;
1672
- box-shadow: 0 2px 8px #2563eb11;
1673
- }
1674
-
1675
- .filter-bar select {
1676
- padding: 6px 18px;
1677
- border-radius: 6px;
1678
- border: 1.5px solid #cbd5e1;
1679
- font-size: 1em;
1680
- color: #2563eb;
1681
- background: #fff;
1682
- font-weight: 600;
1683
- outline: none;
1684
- transition: border 0.15s;
1685
  }
1686
 
1687
- .filter-bar select:focus {
1688
- border: 1.5px solid #38bdf8;
1689
- }
1690
-
1691
- .filter-bar button {
1692
- padding: 6px 18px;
1693
- border-radius: 6px;
1694
- border: none;
1695
- font-size: 1em;
1696
- font-weight: 700;
1697
- cursor: pointer;
1698
- background: #2563eb;
1699
- color: #fff;
1700
- transition: background 0.15s;
1701
  }
1702
 
1703
- .filter-bar button:hover {
1704
- background: #0ea5e9;
1705
- }
1706
-
1707
- .filter-bar button:last-child {
1708
- background: #f87171;
1709
- color: #fff;
1710
- margin-left: 4px;
1711
- }
1712
-
1713
- .filter-bar button:last-child:hover {
1714
- background: #ef4444;
1715
- }
1716
-
1717
- .analytics-panel {
1718
- margin: 18px 16px 8px 16px;
1719
- border-radius: 8px;
1720
- overflow: hidden;
1721
- box-shadow: 0 4px 24px rgba(15,23,42,0.06);
1722
- }
1723
-
1724
- .analytics-header {
1725
- background: linear-gradient(90deg,#6b46ff,#7c3aed);
1726
- color: #fff;
1727
- padding: 16px 20px;
1728
- display: flex;
1729
- align-items: center;
1730
- justify-content: space-between;
1731
- }
1732
-
1733
- .analytics-title {
1734
- font-weight: 800;
1735
- font-size: 1.05rem;
1736
- }
1737
-
1738
- .create-project-btn {
1739
- background: #fff;
1740
- color: #111827;
1741
- border: none;
1742
- padding: 8px 12px;
1743
- border-radius: 6px;
1744
- font-weight: 700;
1745
- cursor: pointer;
1746
- }
1747
-
1748
- .analytics-cards {
1749
- display: flex;
1750
- gap: 5px;
1751
- width: 100%;
1752
- justify-content: stretch;
1753
- align-items: stretch;
1754
- }
1755
-
1756
- .summary-card {
1757
- flex: 110;
1758
- min-width: 0;
1759
- max-width: none;
1760
- /* keep all previous styles and animations */
1761
- border: none;
1762
- border-radius: var(--card-radius);
1763
- box-shadow: var(--card-shadow);
1764
- border-left: 5px solid var(--primary-accent-light);
1765
- transition: box-shadow 0.18s, border 0.18s, transform 0.18s;
1766
- background: #3f51b526;
1767
- padding: 10px 12px;
1768
- display: flex;
1769
- flex-direction: row;
1770
- justify-content: space-between;
1771
- align-items: center;
1772
- cursor: pointer;
1773
- }
1774
-
1775
- @media (max-width: 900px) {
1776
- .analytics-cards { gap: 6px; padding: 8px; }
1777
- .summary-card { flex: 11100%; min-width: 80px; max-width: 100%; padding: 6px 6px; flex-direction: row; }
1778
- }
1779
-
1780
- /* Footer */
1781
- footer {
1782
- background: linear-gradient(to right, #011022, #01030a);
1783
- color: #fff;
1784
- text-align: center;
1785
- padding: 10px 0px;
1786
- position: fixed;
1787
- left: 0;
1788
- bottom: 0;
1789
- width: 100%;
1790
- z-index: 100;
1791
- margin-top: 0;
1792
- }
1793
-
1794
- .back-btn {
1795
- font-family: 'Montserrat', 'Poppins', 'Arial Black', Arial, sans-serif;
1796
- font-size: 0.95rem;
1797
- font-weight: 700;
1798
- letter-spacing: 1px;
1799
- background: #fff;
1800
- color: #23272b;
1801
- border: 1px solid #d1d5db;
1802
- border-radius: 6px;
1803
- padding: 0.32rem 0.8rem;
1804
- margin: 0 0.3rem;
1805
- cursor: pointer;
1806
- transition: background 0.3s, color 0.2s, box-shadow 0.2s, transform 0.2s;
1807
- box-shadow: 0 2px 8px #d1d5db44;
1808
- display: inline-flex;
1809
- align-items: center;
1810
- gap: 6px;
1811
- }
1812
-
1813
- .back-btn:hover {
1814
- background: #f3f4f6;
1815
- color: #1976d2;
1816
- box-shadow: 0 2px 16px #bae6fd88;
1817
- transform: scale(1.04);
1818
- }
1819
-
1820
- .back-icon {
1821
- font-size: 1.1em;
1822
- margin-right: 4px;
1823
- }
1824
-
1825
- .back-btn,
1826
- .logout-btn {
1827
- font-size: 0.85rem;
1828
- padding: 0.18rem 0.7rem;
1829
- border-radius: 5px;
1830
- min-width: unset;
1831
- min-height: unset;
1832
- box-shadow: 0 1px 4px #d1d5db22;
1833
- display: inline-flex;
1834
- align-items: center;
1835
- gap: 6px;
1836
- }
1837
-
1838
- .analytics-blue {
1839
- height: var(--analytics-blue-height);
1840
- animation: analyticsBlueFadeIn0.7s cubic-bezier(0.4,0.2,0.2,1) both;
1841
- }
1842
-
1843
- @keyframes analyticsBlueFadeIn {
1844
- from {
1845
- opacity:0;
1846
- transform: translateY(-32px);
1847
- }
1848
- to {
1849
- opacity:1;
1850
- transform: translateY(0);
1851
  }
1852
  }
1853
-
1854
-
1855
- /* === Modern Additions for Record Page === */
1856
- :root {
1857
- --primary-accent: #2563eb;
1858
- --primary-accent-light: #38bdf8;
1859
- --primary-accent-dark: #1e40af;
1860
- --secondary-accent: #7c3aed;
1861
- --card-radius:14px;
1862
- --card-shadow:06px24px rgba(30,41,59,0.13);
1863
- --section-gap:32px;
1864
- }
1865
-
1866
- /* Modern gradient for analytics-blue */
1867
- .analytics-blue {
1868
- background: linear-gradient(90deg, var(--primary-accent)0%, var(--primary-accent-light)100%);
1869
- border-radius: var(--card-radius) var(--card-radius)00;
1870
- box-shadow: var(--card-shadow);
1871
- }
1872
-
1873
- /* Modern shadow and accent border for summary-card */
1874
- .summary-card {
1875
- border-radius: var(--card-radius);
1876
- box-shadow: var(--card-shadow);
1877
- border-left:5px solid var(--primary-accent-light);
1878
- transition: box-shadow 0.18s, border 0.18s;
1879
- }
1880
- .summary-card:hover {
1881
- box-shadow:0 10px 32px rgba(30,41,59,0.18);
1882
- border-left:5px solid var(--primary-accent);
1883
- }
1884
-
1885
- /* Modern color for summary-label, value, sub */
1886
- .summary-label {
1887
- color: var(--primary-accent);
1888
- font-weight:900;
1889
- font-size:1.25rem;
1890
- letter-spacing:1.5px;
1891
- text-transform: uppercase;
1892
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
1893
- margin-bottom:-11px;
1894
- opacity:0.95;
1895
- }
1896
- .summary-value {
1897
- font-size:3.0rem;
1898
- font-weight:500;
1899
- color: var(--primary-accent-light);
1900
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
1901
- letter-spacing:2px;
1902
- text-shadow:02px 8px #2563eb11;
1903
- margin-bottom:4px;
1904
- }
1905
- .summary-sub {
1906
- color: var(--secondary-accent);
1907
- font-size:1.18rem;
1908
- font-weight:700;
1909
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
1910
- opacity:0.88;
1911
- }
1912
- .summary-icon {
1913
- width:64px;
1914
- height:64px;
1915
- border-radius:12px;
1916
- display: flex;
1917
- align-items: center;
1918
- justify-content: center;
1919
- font-size:2.6rem;
1920
- margin-bottom:0;
1921
- margin-left:18px;
1922
- }
1923
-
1924
- /* Slide-in animation for table rows on load */
1925
- .record-table tbody tr {
1926
- animation: rowSlideIn0.7s cubic-bezier(0.4,0.2,0.2,1) both;
1927
- }
1928
- @keyframes rowSlideIn {
1929
- from { opacity:0; transform: translateX(-32px); }
1930
- to { opacity:1; transform: none; }
1931
- }
1932
-
1933
- /* Animated highlight (glow, no shadow) on row hover/focus */
1934
- .record-table tr:hover td,
1935
- .record-table tr:focus-within td {
1936
- background: #e0f2fe !important;
1937
- animation: rowGlow1.2s linear infinite alternate;
1938
- border-left:4px solid #38bdf8;
1939
- }
1940
- @keyframes rowGlow {
1941
- from { box-shadow:0000px #38bdf800; }
1942
- to { box-shadow:0004px #38bdf866; }
1943
- }
1944
-
1945
- /* Remove box-shadow on hover for table rows (override previous) */
1946
- .record-table tr:hover td {
1947
- box-shadow: none !important;
1948
- }
1949
-
1950
- /* Animated highlight for selected row (if you use .selected-row) */
1951
- .record-table tr.selected-row td {
1952
- background: #bae6fd !important;
1953
- animation: rowGlowSelected1.2s linear infinite alternate;
1954
- border-left:4px solid #38bdf8;
1955
- }
1956
- @keyframes rowGlowSelected {
1957
- from { box-shadow:0000px #38bdf800; }
1958
- to { box-shadow:0004px #38bdf866; }
1959
- }
1960
-
1961
- /* Remove all box-shadow from table rows and cells */
1962
- .record-table td, .record-table th {
1963
- box-shadow: none !important;
1964
- }
1965
-
1966
- /* === Summary Card Animations and Dynamic Styles === */
1967
- .summary-card {
1968
- transition: box-shadow 0.25s, border 0.18s, transform 0.18s;
1969
- cursor: pointer;
1970
- height: 100px;
1971
- }
1972
- .summary-card:hover {
1973
- transform: scale(1.045) rotate(0deg);
1974
- /* Default blue glow, overridden below for each type */
1975
- box-shadow:0 0 24px 0 #38bdf8cc,0 10px 32px rgba(30,41,59,0.18);
1976
- border-left:5px solid var(--primary-accent);
1977
- }
1978
-
1979
- /* Card type specific icon and glow */
1980
- .summary-card.total .summary-icon {
1981
- /* background: linear-gradient(135deg, #38bdf8 0%, #7c3aed 100%); */
1982
- color: #23272b;
1983
- box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); /* effectively no shadow */
1984
- }
1985
-
1986
- .summary-card.total:hover {
1987
- box-shadow: 0 0 32px 0 #38bdf8cc, 0 10px 32px #38bdf822;
1988
- border-left: 5px solid #38bdf8;
1989
- }
1990
-
1991
- .summary-card.closed .summary-icon {
1992
- /* background: linear-gradient(135deg, #ef4444 0%, #991b1b 100%); */
1993
- color: #ef4444;
1994
- }
1995
-
1996
- .summary-card.closed:hover {
1997
- box-shadow: 0 0 32px 0 #ef4444cc, 0 10px 32px #ef444422;
1998
- border-left: 5px solid #ef4444;
1999
- }
2000
-
2001
- .summary-card.open .summary-icon {
2002
- /* background: linear-gradient(135deg, #22c55e 0%, #2563eb 100%); */
2003
- color: #00ae0e; /* 6 digit hex, avoids linter warnings */
2004
- }
2005
-
2006
- .summary-card.open:hover {
2007
- box-shadow: 0 0 32px 0 #22c55ecc, 0 10px 32px #22c55e22;
2008
- border-left: 5px solid #22c55e;
2009
- }
2010
-
2011
- /* Subtle bounce animation for icon on hover */
2012
- .summary-card:hover .summary-icon {
2013
- animation: summary-bounce0.7s cubic-bezier(0.4,0.2,0.2,1) both;
2014
- }
2015
- @keyframes summary-bounce {
2016
- 0% { transform: scale(1) translateY(0); }
2017
- 30% { transform: scale(1.18) translateY(-6px); }
2018
- 60% { transform: scale(0.96) translateY(2px); }
2019
- 100% { transform: scale(1) translateY(0); }
2020
- }
2021
-
2022
- .summary-value.blue { color: #2563eb; }
2023
- .summary-value.green { color: #22c55e; }
2024
- .summary-value.red { color: #ef4444; }
2025
-
 
1
+ /* ===== CSS Variables & Reset ===== */
2
  :root {
3
+ --primary-blue: #1E3A8A;
4
+ --primary-blue-light: #2563eb;
5
+ --accent-green: #22c55e;
6
+ --accent-red: #ef4444;
7
+ --accent-purple: #7c3aed;
8
+ --text-dark: #1f2937;
9
+ --text-light: #6b7280;
10
+ --border-color: #e5e7eb;
11
+ --bg-light: #f8fafc;
12
+ --bg-white: #ffffff;
13
+ }
14
+
15
+ * {
16
+ margin: 0;
17
+ padding: 0;
18
+ box-sizing: border-box;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  }
20
 
21
+ html, body {
22
+ height: 100%;
23
+ width: 100%;
24
+ overflow: hidden;
25
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
26
+ background: #f4f6fa;
 
 
 
 
 
 
 
 
27
  }
28
 
29
+ /* ===== App Container ===== */
30
+ .app-container {
31
+ display: flex;
32
+ flex-direction: column;
33
+ height: 100vh;
34
+ width: 100%;
35
+ overflow: hidden;
36
  }
37
 
38
+ /* ===== Header Styles ===== */
39
+ .site-header {
40
+ background: linear-gradient(135deg, #011329 0%, #0a2540 100%);
41
+ box-shadow: 0 15px 20px -5px rgba(0, 0, 0, 0.1), 0 6px 8px -4px rgba(0, 0, 0, 0.1);
42
+ height: 60px;
43
+ flex-shrink: 0;
44
+ position: sticky;
45
+ top: 0;
46
+ z-index: 1000;
47
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
48
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
49
+ -webkit-backdrop-filter: blur(8px);
50
+ backdrop-filter: blur(8px);
51
+ background: rgba(1, 19, 41, 0.95);
 
 
 
 
 
 
 
52
  }
53
 
54
+ .site-header::before {
55
+ content: '';
56
+ position: absolute;
57
+ top: 0;
58
+ left: 0;
59
+ right: 0;
60
+ bottom: 0;
61
+ background: linear-gradient(135deg, rgba(30, 58, 138, 0.9) 0%, rgba(1, 19, 41, 0.95) 100%);
62
+ z-index: -1;
63
+ }
64
 
65
+ .site-header.scrolled {
66
+ height: 56px;
67
+ box-shadow: 0 8px 12px -3px rgba(0, 0, 0, 0.1), 0 3px 5px -3px rgba(0, 0, 0, 0.1);
68
+ }
 
 
 
 
69
 
70
+ .header-inner {
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  display: flex;
72
  align-items: center;
73
+ justify-content: space-between;
74
+ height: 100%;
75
+ padding: 0 32px;
76
+ max-width: 1400px;
77
+ margin: 0 auto;
78
+ width: 100%;
 
 
 
 
 
 
 
 
 
 
 
79
  }
80
 
81
+ .logo-cluster {
82
+ display: flex;
83
+ align-items: center;
84
+ gap: 12px;
85
+ margin-left: -252px;
 
 
 
86
  }
87
 
88
+ .logo-img-header {
89
+ width: 48px;
90
+ height: 48px;
 
 
 
 
 
 
 
 
 
91
  border-radius: 50%;
92
+ background: white;
93
+ padding: 6px;
94
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
95
+ cursor: pointer;
96
+ transition: transform 0.2s;
97
  }
98
 
99
+ .logo-img-header:hover {
100
+ transform: scale(1.05);
101
+ }
 
 
 
 
 
 
 
 
 
 
102
 
103
+ .py-detect-title-header {
104
+ font-size: 28px;
 
 
 
 
 
 
105
  font-weight: 900;
106
+ letter-spacing: 3px;
107
+ color: #38bdf8;
108
+ display: flex;
109
+ align-items: center;
110
+ gap: 2px;
 
111
  }
112
 
113
+ .py-detect-title-header .py-letter.p,
114
+ .py-detect-title-header .py-letter.d,
115
+ .py-detect-title-header .py-letter.t,
116
+ .py-detect-title-header .py-letter.c {
117
  color: #e3f6ff;
118
  text-shadow: 0 0 6px #38bdf8;
119
  }
120
 
121
+ .py-detect-title-header .py-letter.y,
122
+ .py-detect-title-header .py-letter.e,
123
+ .py-detect-title-header .py-letter.e2,
124
+ .py-detect-title-header .py-letter.t2 {
125
  color: #38bdf8;
126
  text-shadow: 0 0 6px #38bdf8;
127
  }
128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  .py-shape {
130
  display: inline-block;
131
+ width: 16px;
132
  height: 4px;
133
+ background: #e3f6ff;
134
+ margin: 0 6px;
 
135
  border-radius: 2px;
136
+ box-shadow: 0 0 6px #38bdf8;
 
137
  }
138
 
139
+ .header-actions-right {
 
 
 
 
 
140
  display: flex;
 
141
  align-items: center;
142
+ gap: 12px;
143
+ margin-right: -250px;
 
 
 
 
 
144
  }
145
 
146
+ .back-btn, .logout-btn {
147
+ font-weight: 600;
148
+ font-size: 11px;
149
+ padding: 6px 12px;
150
  border-radius: 6px;
151
+ border: none;
152
+ cursor: pointer;
 
 
 
 
 
 
 
153
  display: flex;
154
+ align-items: center;
155
+ gap: 6px;
156
+ transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
 
157
  position: relative;
158
+ overflow: hidden;
159
+ backdrop-filter: blur(8px);
160
  }
161
 
162
+ .back-btn {
163
+ background: white;
164
+ color: #374151;
165
+ border: 1px solid #d1d5db;
 
 
 
166
  }
167
 
168
+ .back-btn:hover {
169
+ background: #f9fafb;
170
+ transform: translateY(-1px);
171
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  }
173
 
174
+ .logout-btn {
175
+ background: linear-gradient(135deg, #ef4444, #dc2626);
176
+ color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  }
178
+
179
+ .logout-btn:hover {
180
+ background: linear-gradient(135deg, #dc2626, #ef4444);
181
+ transform: translateY(-1px);
182
+ box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
183
+ }
184
+
185
+ .back-icon, .logout-icon {
186
+ font-size: 16px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  }
188
 
189
+ /* ===== Main Content Area ===== */
190
+ .main-content {
191
+ flex: 1;
192
+ overflow: hidden;
193
+ display: flex;
194
+ flex-direction: column;
195
  }
196
 
197
+ .content-container {
198
+ flex: 1;
199
+ overflow: hidden;
200
+ padding: 20px;
201
+ max-width: 1882px;
 
 
 
202
  margin: 0 auto;
203
+ width: 100%;
204
  }
205
 
206
+ .record-card {
207
+ background: white;
208
+ border-radius: 12px;
209
+ box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px;
210
+ overflow: hidden;
211
+ display: flex;
212
+ flex-direction: column;
213
+ height: 90%;
214
+ min-height: 0;
215
  }
216
 
217
+ /* ===== Analytics Blue Section ===== */
218
+ .analytics-blue {
219
+ background: #1E3A8A;
220
+ padding: 20px 32px;
221
+ flex-shrink: 0;
222
+ }
223
+
224
+ .record-header {
225
+ display: flex;
226
+ justify-content: space-between;
227
+ align-items: center;
228
+ margin-bottom: 20px;
229
  }
230
 
231
+ .record-title-group {
232
+ display: flex;
233
+ align-items: center;
234
+ gap: 16px;
235
  }
236
 
237
+ .record-title {
238
+ font-size: 20px;
239
+ font-weight: 700;
240
+ color: white;
241
+ display: flex;
242
+ align-items: center;
243
+ gap: 10px;
244
  }
245
 
246
+ .record-title i {
247
+ font-size: 18px;
248
+ color: #38bdf8;
 
249
  }
250
 
251
+ .record-dropdown {
252
+ padding: 8px 16px;
253
+ border-radius: 6px;
254
+ border: 1px solid rgba(255, 255, 255, 0.3);
255
+ background: rgba(255, 255, 255, 0.1);
256
+ color: white;
257
+ font-size: 14px;
258
+ font-weight: 500;
259
+ cursor: pointer;
260
+ min-width: 160px;
261
  }
262
 
263
+ .record-dropdown:focus {
264
+ outline: none;
265
+ border-color: #38bdf8;
 
 
266
  }
267
 
268
+ option {
269
+ color: black;
270
+ }
271
 
272
+ .record-search-container {
273
+ position: relative;
274
+ }
 
275
 
276
+ .record-search {
277
+ width: 280px;
278
+ padding: 10px 16px 10px 42px;
279
+ border-radius: 6px;
280
+ border: 1px solid rgba(255, 255, 255, 0.3);
281
+ background: rgba(255, 255, 255, 0.1);
282
+ color: white;
283
+ font-size: 14px;
284
+ transition: all 0.3s;
285
+ }
286
 
287
+ .record-search:focus {
288
+ outline: none;
289
+ border-color: #38bdf8;
290
+ background: rgba(255, 255, 255, 0.15);
291
+ box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.1);
292
  }
293
 
294
+ .record-search::placeholder {
295
+ color: rgba(255, 255, 255, 0.7);
 
 
 
 
296
  }
 
 
 
 
 
 
 
 
 
297
 
298
+ .analytics-cards {
299
+ display: flex;
300
+ gap: 20px;
301
+ flex-shrink: 0;
302
  }
303
 
304
+ .summary-card {
305
+ flex: 1;
306
+ background: rgba(255, 255, 255, 0.95);
307
+ border-radius: 10px;
308
+ padding: 18px;
309
+ display: flex;
310
+ justify-content: space-between;
311
+ align-items: center;
312
+ transition: all 0.3s;
313
+ cursor: pointer;
314
+ border: 1px solid rgba(255, 255, 255, 0.2);
315
+ backdrop-filter: blur(10px);
316
+ min-height: 85px;
317
  }
318
 
319
+ .summary-card:hover {
320
+ transform: translateY(-2px);
321
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
322
+ }
 
323
 
324
+ .summary-left {
325
+ display: flex;
326
+ flex-direction: column;
 
 
 
 
 
 
 
 
 
 
 
327
  }
328
 
329
+ .summary-label {
330
+ font-size: 18px;
331
+ font-weight: 900;
332
+ color: #6b7280;
333
+ text-transform: uppercase;
334
+ letter-spacing: 1px;
335
+ margin-bottom: 6px;
336
  }
337
 
338
+ .summary-value {
339
+ font-size: 30px;
340
+ font-weight: 700;
341
+ font-family: 'Segoe UI', sans-serif;
342
+ line-height: 1;
343
  }
344
 
345
+ .summary-value.blue {
346
+ color: #2563eb;
347
+ }
348
 
349
+ .summary-value.green {
350
+ color: #22c55e;
351
  }
352
 
353
+ .summary-value.red {
354
+ color: #ef4444;
355
  }
356
 
357
+ .summary-icon {
358
+ width: 42px;
359
+ height: 42px;
360
+ border-radius: 8px;
361
+ display: flex;
362
+ align-items: center;
363
+ justify-content: center;
364
+ font-size: 18px;
365
+ color: white;
366
  }
367
 
368
+ .summary-card.total .summary-icon {
369
+ background: linear-gradient(135deg, #1E3A8A, #1e40af);
370
  }
371
 
372
+ .summary-card.open .summary-icon {
373
+ background: linear-gradient(135deg, #16a34a, #15803d);
374
  }
375
 
376
+ .summary-card.closed .summary-icon {
377
+ background: linear-gradient(135deg, #dc2626, #b91c1c);
378
  }
379
 
380
+ /* ===== Record Meta ===== */
381
+ .record-meta {
382
+ padding: 14px 32px;
383
+ background: #f8fafc;
384
+ border-bottom: 1px solid var(--border-color);
385
+ color: #6b7280;
386
+ font-size: 14px;
387
+ font-weight: 500;
388
+ flex-shrink: 0;
389
  }
390
 
391
+ /* ===== Filter Bar ===== */
392
+ .filter-bar {
393
+ display: flex;
394
+ align-items: center;
395
+ gap: 16px;
396
+ padding: 16px 32px;
397
+ background: #f8fafc;
398
+ border-bottom: 1px solid var(--border-color);
399
+ flex-shrink: 0;
400
+ flex-wrap: wrap;
401
  }
402
 
403
+ .filter-icon {
404
+ color: #6b7280;
405
+ font-size: 16px;
406
  }
407
 
408
+ .filter-select {
409
+ padding: 9px 16px;
410
+ border-radius: 6px;
411
+ border: 1px solid var(--border-color);
412
+ background: white;
413
+ color: #374151;
414
+ font-size: 14px;
415
+ font-weight: 500;
416
+ min-width: 150px;
417
+ cursor: pointer;
418
+ transition: all 0.2s;
419
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
420
  }
421
 
422
+ .filter-select:focus {
423
+ outline: none;
424
+ border-color: #2563eb;
425
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
426
  }
427
 
428
+ .filter-btn {
429
+ padding: 9px 18px;
430
+ border-radius: 6px;
431
+ border: none;
432
+ font-size: 14px;
433
+ font-weight: 600;
434
+ cursor: pointer;
435
+ display: flex;
436
+ align-items: center;
437
+ gap: 8px;
438
+ transition: all 0.2s;
439
+ }
440
 
441
+ .filter-btn.apply {
442
+ background: #2563eb;
443
+ color: white;
444
  }
445
 
446
+ .filter-btn.apply:hover {
447
+ background: #1d4ed8;
448
+ transform: translateY(-1px);
449
+ box-shadow: 0 2px 8px rgba(37, 99, 235, 0.3);
450
+ }
 
 
 
 
 
 
 
 
 
 
 
 
451
 
452
+ .filter-btn.reset {
453
+ background: #E5E7EB;
454
+ color: #374151;
455
+ border: 1px solid #D1D5DB;
456
+ }
457
 
458
+ .filter-btn.reset:hover {
459
+ background: #D1D5DB;
460
+ transform: translateY(-1px);
461
+ box-shadow: 0 2px 8px rgba(107, 114, 128, 0.2);
462
+ }
463
 
464
+ .filter-btn.reset i {
465
+ color: #6B7280;
466
+ }
467
 
468
+ /* ===== Table Container ===== */
469
+ .table-container {
470
+ flex: 1;
471
  overflow: hidden;
 
 
 
 
 
 
472
  display: flex;
473
+ flex-direction: column;
474
+ min-height: 0;
475
  }
476
 
477
+ .table-wrapper {
478
+ flex: 1;
479
+ overflow: auto;
480
+ padding: 0 32px;
481
+ background: white;
 
482
  }
483
 
484
+ .table-wrapper::-webkit-scrollbar {
485
+ width: 8px;
486
+ height: 8px;
 
487
  }
488
 
489
+ .table-wrapper::-webkit-scrollbar-track {
490
+ background: #f1f1f1;
491
+ border-radius: 4px;
 
492
  }
493
 
494
+ .table-wrapper::-webkit-scrollbar-thumb {
495
+ background: #c1c1c1;
496
+ border-radius: 4px;
 
 
 
497
  }
498
 
499
+ .table-wrapper::-webkit-scrollbar-thumb:hover {
500
+ background: #a8a8a8;
 
501
  }
502
 
503
+ .record-table {
504
+ width: 100%;
505
+ border-collapse: separate;
506
+ border-spacing: 0;
507
+ background: white;
508
+ min-height: 118px;
 
 
 
 
 
 
 
 
 
509
  }
510
 
511
+ .record-table thead {
512
+ position: sticky;
513
+ top: 0;
514
+ z-index: 10;
515
+ background: #f8fafc;
 
 
 
 
 
 
516
  }
517
 
518
+ .record-table th {
519
+ padding: 18px 16px;
520
+ text-align: left;
521
+ font-weight: 600;
522
+ color: #374151;
523
+ font-size: 13px;
524
+ border-bottom: 2px solid var(--border-color);
525
+ white-space: nowrap;
526
+ position: relative;
527
  }
528
 
529
+ .record-table th i {
530
+ color: #64748B;
531
+ margin-right: 8px;
532
+ font-size: 13px;
533
+ }
534
 
535
+ .record-table th.sortable {
536
+ cursor: pointer;
537
+ user-select: none;
538
+ }
539
 
540
+ .record-table th.sortable:hover {
541
+ background: #f1f5f9;
542
+ }
 
543
 
544
+ .sort {
545
+ position: absolute;
546
+ right: 16px;
547
+ top: 50%;
548
+ transform: translateY(-50%);
549
+ width: 0;
550
+ height: 0;
551
+ border-left: 5px solid transparent;
552
+ border-right: 5px solid transparent;
553
+ opacity: 0.5;
554
+ }
555
 
556
+ .sort.asc {
557
+ border-bottom: 8px solid #374151;
 
558
  }
559
 
560
+ .sort.desc {
561
+ border-top: 8px solid #374151;
562
  }
563
 
564
+ .sort.neutral {
565
+ border-top: 8px solid #9ca3af;
566
+ }
 
567
 
568
+ .record-table td {
569
+ padding: 16px 16px;
570
+ border-bottom: 1px solid var(--border-color);
571
+ color: #374151;
572
+ font-size: 14px;
573
+ white-space: nowrap;
574
+ box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
575
  }
576
 
577
+ .record-table tbody tr {
578
+ transition: background 0.2s;
 
 
 
579
  }
580
 
581
+ .record-table tbody tr:hover {
582
+ background: #f8fafc;
583
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
584
 
585
+ .sl-no-col {
586
+ width: 80px;
587
+ text-align: center;
 
 
 
588
  }
589
 
590
+ .case-id-link {
591
+ color: #2563eb;
592
+ text-decoration: none;
593
+ font-weight: 600;
594
+ cursor: pointer;
595
+ transition: color 0.2s;
 
596
  }
597
 
598
+ .case-id-link:hover {
599
+ color: #1d4ed8;
600
+ text-decoration: underline;
601
+ }
602
+
603
+ /* Status Badges */
604
+ .status-badge {
605
+ display: inline-flex;
606
+ align-items: center;
607
+ gap: 8px;
608
+ padding: 6px 14px;
609
+ border-radius: 20px;
610
+ font-size: 13px;
611
+ font-weight: 600;
612
+ min-width: 110px;
613
+ justify-content: center;
614
  }
615
 
616
+ .status-badge.status-open {
617
+ background: #dcfce7;
618
+ color: #166534;
619
  }
620
 
621
+ .status-badge.status-open .status-dot {
622
+ background: #166534;
623
+ }
 
 
624
 
625
+ .status-badge.status-under {
626
+ background: #fef3c7;
627
+ color: #92400e;
 
628
  }
629
 
630
+ .status-badge.status-under .status-dot {
631
+ background: #92400e;
632
+ }
 
633
 
634
+ .status-badge.status-closed {
635
+ background: #fee2e2;
636
+ color: #991b1b;
 
 
637
  }
638
 
639
+ .status-badge.status-closed .status-dot {
640
+ background: #991b1b;
641
+ }
 
 
642
 
643
+ .status-dot {
644
+ width: 8px;
645
+ height: 8px;
646
+ border-radius: 50%;
647
+ display: inline-block;
648
  }
649
 
650
+ /* Empty values */
651
+ .empty-value {
652
+ color: #9CA3AF;
653
+ font-style: italic;
 
 
 
 
 
654
  }
655
 
656
+ /* Action Buttons */
657
+ .actions-col {
658
+ width: 160px;
659
+ }
660
 
661
+ .action-buttons {
662
  display: flex;
663
+ gap: 8px;
 
 
664
  }
665
 
666
+ .action-btn {
667
+ width: 34px;
668
+ height: 34px;
669
+ border-radius: 6px;
670
+ border: 1px solid var(--border-color);
671
+ background: white;
672
  display: flex;
 
673
  align-items: center;
674
  justify-content: center;
675
+ cursor: pointer;
676
+ transition: all 0.2s;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677
  position: relative;
 
678
  }
679
 
680
+ .action-btn::after {
681
+ content: attr(title);
682
+ position: absolute;
683
+ bottom: -30px;
684
+ left: 50%;
685
+ transform: translateX(-50%);
686
+ background: #374151;
687
+ color: white;
688
+ padding: 4px 8px;
689
+ border-radius: 4px;
690
+ font-size: 12px;
691
+ white-space: nowrap;
692
+ opacity: 0;
693
+ pointer-events: none;
694
+ transition: opacity 0.2s;
695
+ z-index: 100;
696
+ }
697
+
698
+ .action-btn:hover::after {
699
+ opacity: 1;
700
+ }
701
 
702
+ .action-btn.view {
703
+ color: #2563eb;
704
+ border-color: #dbeafe;
705
  }
706
 
707
+ .action-btn.view:hover {
708
+ background: #dbeafe;
709
+ border-color: #2563eb;
710
+ }
711
+
712
+ .action-btn.edit {
713
+ color: #7c3aed;
714
+ border-color: #ede9fe;
715
  }
716
 
717
+ .action-btn.edit:hover {
718
+ background: #ede9fe;
719
+ border-color: #7c3aed;
720
+ }
721
+
722
+ .action-btn.delete {
723
+ color: #ef4444;
724
+ border-color: #fee2e2;
725
  }
 
726
 
727
+ .action-btn.delete:hover {
728
+ background: #fee2e2;
729
+ border-color: #ef4444;
730
+ }
 
 
 
731
 
732
+ /* Empty State */
733
+ .empty {
734
+ text-align: center;
735
+ padding: 60px 20px;
736
+ color: #9ca3af;
737
+ font-size: 16px;
738
+ background: white;
739
  }
740
 
741
+ .empty i {
742
+ font-size: 32px;
743
+ margin-bottom: 16px;
744
+ display: block;
745
+ opacity: 0.5;
746
+ }
747
+
748
+ /* ===== Bottom Controls ===== */
749
+ .bottom-controls {
750
+ display: flex;
751
+ justify-content: space-between;
752
+ align-items: center;
753
+ padding: 16px 32px;
754
+ background: #f8fafc;
755
+ border-top: 1px solid var(--border-color);
756
+ flex-shrink: 0;
757
  }
758
 
759
+ .results-summary {
760
+ display: flex;
761
+ align-items: center;
762
+ gap: 20px;
763
  }
764
 
765
+ .results-info {
 
 
766
  display: flex;
767
+ align-items: center;
768
+ gap: 8px;
769
+ color: #6b7280;
770
+ font-size: 14px;
771
+ font-weight: 500;
772
  }
773
 
774
+ .results-info i {
775
+ color: #2563eb;
 
 
776
  }
777
 
778
+ .page-size-selector {
779
  display: flex;
780
  align-items: center;
781
+ gap: 8px;
782
+ color: #6b7280;
783
+ font-size: 14px;
 
 
 
 
784
  }
785
 
786
+ .page-size-selector select {
787
+ padding: 6px 12px;
788
+ border-radius: 4px;
789
+ border: 1px solid var(--border-color);
790
+ background: white;
791
+ color: #374151;
792
+ font-size: 14px;
793
+ cursor: pointer;
794
  }
795
 
796
+ .pagination-controls {
797
  display: flex;
798
  align-items: center;
799
+ gap: 6px;
800
  }
801
 
802
+ .page-btn {
803
+ width: 34px;
804
+ height: 34px;
805
+ border-radius: 6px;
806
+ border: 1px solid var(--border-color);
807
+ background: white;
808
+ display: flex;
809
+ align-items: center;
810
+ justify-content: center;
811
+ cursor: pointer;
812
+ transition: all 0.2s;
813
+ color: #374151;
814
  }
815
 
816
+ .page-btn:hover:not(:disabled) {
817
+ background: #f3f4f6;
818
+ border-color: #9ca3af;
 
819
  }
820
 
821
+ .page-btn:disabled {
822
+ opacity: 0.5;
823
+ cursor: not-allowed;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
824
  }
825
 
826
+ .page-numbers {
827
+ display: flex;
828
+ align-items: center;
829
+ gap: 4px;
830
  }
831
 
832
+ .page-number {
833
+ min-width: 32px;
834
+ height: 32px;
835
+ padding: 0 8px;
836
+ border-radius: 6px;
837
+ border: 1px solid var(--border-color);
838
+ background: white;
839
+ color: #374151;
840
+ font-size: 14px;
841
+ font-weight: 500;
842
+ cursor: pointer;
843
+ transition: all 0.2s;
844
  }
845
 
846
+ .page-number:hover {
847
+ background: #f3f4f6;
848
+ }
849
+
850
+ .page-number.active {
851
+ background: #2563eb;
852
+ color: white;
853
+ border-color: #2563eb;
854
  }
855
 
856
+ .page-ellipsis {
857
+ padding: 0 8px;
858
+ color: #9ca3af;
859
+ font-size: 14px;
860
  }
861
 
862
+ /* ===== FOOTER ===== */
863
+ footer {
864
+ background: linear-gradient(135deg, rgba(30, 58, 138, 0.9) 0%, rgba(1, 19, 41, 0.95) 100%);
865
  color: #fff;
866
+ text-align: center;
867
+ padding: 12px 0;
868
+ position: fixed;
869
+ bottom: 0;
870
+ left: 0;
871
+ width: 100%;
872
+ z-index: 1000;
 
 
 
873
  }
874
 
875
+ /* ===== Responsive Design ===== */
876
+ @media (max-width: 1200px) {
877
+ .header-inner {
878
+ padding: 0 24px;
879
  }
880
 
881
+ .content-container {
882
+ padding: 16px;
883
+ }
884
 
885
+ .analytics-blue,
886
+ .record-meta,
887
+ .filter-bar,
888
+ .table-wrapper,
889
+ .bottom-controls {
890
+ padding-left: 24px;
891
+ padding-right: 24px;
892
+ }
 
 
 
 
 
 
893
  }
894
 
895
+ @media (max-width: 992px) {
896
+ .analytics-cards {
897
+ flex-direction: column;
898
+ gap: 16px;
899
+ }
900
+
901
+ .record-header {
902
+ flex-direction: column;
903
+ align-items: stretch;
904
+ gap: 16px;
905
+ }
906
 
907
+ .record-title-group {
908
+ flex-direction: column;
909
+ align-items: stretch;
910
+ gap: 12px;
 
 
911
  }
912
 
913
+ .record-dropdown,
914
+ .record-search {
915
  width: 100%;
 
916
  }
 
917
 
918
+ .filter-bar {
919
+ gap: 12px;
920
+ }
 
 
 
 
 
 
921
 
922
+ .filter-select {
923
+ flex: 1;
924
+ min-width: 120px;
925
+ }
926
+
927
+ .table-wrapper {
928
+ padding: 0 16px;
929
+ overflow-x: auto;
930
+ }
931
+
932
+ .record-table {
933
+ min-width: 800px;
934
+ }
935
  }
936
 
937
+ @media (max-width: 768px) {
938
+ .header-inner {
939
+ padding: 0 16px;
940
+ flex-direction: column;
941
+ height: auto;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
942
  padding: 12px 16px;
943
+ gap: 12px;
 
 
 
 
 
 
 
944
  }
945
 
946
+ .py-detect-title-header {
947
+ font-size: 22px;
948
+ letter-spacing: 2px;
949
  }
950
 
951
+ .header-actions-right {
952
+ width: 100%;
953
+ justify-content: space-between;
954
  }
955
 
956
+ .back-btn,
957
+ .logout-btn {
958
+ flex: 1;
959
+ justify-content: center;
 
 
960
  }
961
 
962
+ .analytics-blue,
963
+ .record-meta,
964
+ .filter-bar,
965
+ .table-wrapper,
966
+ .bottom-controls {
967
+ padding-left: 16px;
968
+ padding-right: 16px;
969
  }
970
 
971
+ .summary-value {
972
+ font-size: 22px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
973
  }
974
 
975
+ .summary-icon {
976
+ width: 40px;
977
+ height: 40px;
978
+ font-size: 18px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
979
  }
980
 
981
+ .bottom-controls {
982
+ flex-direction: column;
983
+ gap: 16px;
984
+ align-items: stretch;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
985
  }
986
 
987
+ .results-summary {
988
+ width: 100%;
989
+ justify-content: space-between;
 
 
 
 
 
 
990
  }
991
 
992
+ .pagination-controls {
993
+ width: 100%;
994
+ justify-content: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
995
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
996
  }
997
 
998
+ @media (max-width: 480px) {
999
+ .py-detect-title-header {
1000
+ font-size: 18px;
1001
+ letter-spacing: 1px;
1002
  }
1003
 
1004
+ .header-actions-right {
1005
+ flex-direction: column;
1006
+ gap: 8px;
1007
  }
1008
 
1009
+ .summary-card {
1010
+ padding: 16px;
1011
  }
 
1012
 
1013
+ .summary-value {
1014
+ font-size: 20px;
 
1015
  }
1016
 
1017
+ .summary-icon {
1018
+ width: 36px;
1019
+ height: 36px;
1020
+ font-size: 16px;
1021
  }
1022
 
1023
+ .filter-bar {
1024
+ flex-direction: column;
1025
+ align-items: stretch;
 
 
 
 
1026
  }
 
1027
 
1028
+ .filter-select,
1029
+ .filter-btn {
1030
+ width: 100%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1031
  }
1032
 
1033
+ .action-buttons {
1034
+ flex-direction: column;
1035
+ gap: 4px;
 
 
 
 
 
 
 
 
 
 
 
1036
  }
1037
 
1038
+ .action-btn {
1039
+ width: 32px;
1040
+ height: 32px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1041
  }
1042
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/recordpage/recordpage.component.html CHANGED
@@ -1,257 +1,246 @@
1
- <!-- Modern UI header with logo and PyDetect title -->
2
- <div class="site-header">
3
- <div class="header-inner">
4
- <div class="logo-cluster">
5
- <span (click)="navigateHome()" style="cursor:pointer;display:flex;align-items:center;">
6
- <img src="/assets/pykara-logo.png" alt="PyDetect Logo" class="logo-img-header" />
7
- </span>
8
- <div class="py-detect-title-header">
9
- <span class="py-letter p">P</span>
10
- <span class="py-letter y">Y</span>
11
- <span class="py-shape"></span>
12
- <span class="py-letter d">D</span>
13
- <span class="py-letter e">E</span>
14
- <span class="py-letter t">T</span>
15
- <span class="py-letter e2">E</span>
16
- <span class="py-letter c">C</span>
17
- <span class="py-letter t2">T</span>
18
- </div>
19
- </div>
20
- <div class="header-actions-right">
21
- <button class="back-btn" (click)="navigateBackToInfoPage()">
22
- <span class="back-icon">←</span> Back to Info Page
23
- </button>
24
- <button class="logout-btn" (click)="logout()">
25
- <span class="logout-icon">⎋</span> Logout
26
- </button>
27
- </div>
28
- </div>
29
- </div>
30
-
31
- <!-- Salesforce-style card/table content below the header -->
32
- <div class="record-card">
33
- <!-- Analytics summary panel -->
34
- <div class="analytics-panel">
35
- <div class="analytics-blue">
36
-
37
- <div class="record-header">
38
- <div class="record-title-group">
39
- <span class="record-title"><i class="fas fa-database"></i> Police Investigation Records</span>
40
- <select class="record-dropdown">
41
- <option>Recently Viewed</option>
42
- <option>All Records</option>
43
- </select>
44
- </div>
45
- <div class="record-header-actions">
46
- <span style="position:relative;">
47
- <i class="fas fa-search" style="position:absolute;left:10px;top:50%;transform:translateY(-50%);color:#b0b0b0;"></i>
48
- <input class="record-search" type="text" [(ngModel)]="q" (ngModelChange)="applyFilters()" placeholder="Search this list..." style="padding-left:32px;" />
49
- </span>
50
  </div>
51
  </div>
 
 
 
 
 
 
 
 
 
 
52
 
53
- <div class="analytics-cards">
54
- <!-- Total Cases -->
55
- <div class="summary-card total">
56
- <div class="summary-left">
57
- <div class="summary-label">Total Cases</div>
58
- <div class="summary-value blue">{{ totalCases }}</div>
59
- <div class="summary-sub">&nbsp;</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  </div>
61
- <div class="summary-icon icon-indigo"><i class="fas fa-folder-open fa-bounce"></i></div>
62
- </div>
63
- <!-- Open Cases -->
64
- <div class="summary-card open">
65
- <div class="summary-left">
66
- <div class="summary-label">Open</div>
67
- <div class="summary-value green">{{ openCases }}</div>
68
- <div class="summary-sub">&nbsp;</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  </div>
70
- <div class="summary-icon icon-blue"><i class="fas fa-exclamation-circle fa-beat"></i></div>
71
  </div>
72
- <!-- Closed Cases -->
73
- <div class="summary-card closed">
74
- <div class="summary-left">
75
- <div class="summary-label">Closed</div>
76
- <div class="summary-value red">{{ closedCases }}</div>
77
- <div class="summary-sub">&nbsp;</div>
78
- </div>
79
- <div class="summary-icon icon-green"><i class="fas fa-check-circle fa-spin"></i></div>
80
  </div>
81
- </div>
82
- </div>
83
 
84
- <div class="record-meta" style="padding:8px 24px 0 24px; color: #6b7280; font-size:0.98em;">
85
- {{ rows.length }} items • Updated a few seconds ago
86
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
- <!-- Filter bar above the table -->
89
- <div class="filter-bar">
90
- <span style="margin-right:8px;"><i class="fas fa-filter"></i></span>
91
- <select [(ngModel)]="filterCrimeType">
92
- <option value="">Crime Type</option>
93
- <option *ngFor="let type of crimeTypes">{{ type }}</option>
94
- </select>
95
- <select [(ngModel)]="filterStatus">
96
- <option value="">Select Status</option>
97
- <option *ngFor="let status of statusTypes">{{ status }}</option>
98
- </select>
99
- <select [(ngModel)]="filterLocation">
100
- <option value="">Location</option>
101
- <option *ngFor="let loc of locations">{{ loc }}</option>
102
- </select>
103
- <select [(ngModel)]="filterOfficer">
104
- <option value="">Officer</option>
105
- <option *ngFor="let officer of officers">{{ officer }}</option>
106
- </select>
107
- <button (click)="applyFilters()"><i class="fas fa-check"></i> Apply</button>
108
- <button (click)="resetFilters()"><i class="fas fa-undo"></i> Reset</button>
109
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
- <table class="record-table">
112
- <thead>
113
- <tr>
114
- <th class="select-col"><i class="fas fa-list-ol"></i> Sl. No</th>
115
- <th (click)="setSort('caseId')" [attr.aria-sort]="ariaSort('caseId')" style="cursor:pointer;">
116
- <i class="fas fa-id-badge"></i> Case ID
117
- <span class="sort" [ngClass]="{'asc': isAsc('caseId'), 'desc': isDesc('caseId'), 'neutral': !isAsc('caseId') && !isDesc('caseId')}"></span>
118
- </th>
119
- <th (click)="setSort('status')" [attr.aria-sort]="ariaSort('status')" style="cursor:pointer;">
120
- <i class="fas fa-info-circle"></i> Status
121
- <span class="sort" [ngClass]="{'asc': isAsc('status'), 'desc': isDesc('status'), 'neutral': !isAsc('status') && !isDesc('status')}"></span>
122
- </th>
123
- <th (click)="setSort('crime')" [attr.aria-sort]="ariaSort('crime')" style="cursor:pointer;">
124
- <i class="fas fa-gavel"></i> Crime Type
125
- <span class="sort" [ngClass]="{'asc': isAsc('crime'), 'desc': isDesc('crime'), 'neutral': !isAsc('crime') && !isDesc('crime')}"></span>
126
- </th>
127
- <th (click)="setSort('Investigation Officer')" [attr.aria-sort]="ariaSort('Investigation Officer')" style="cursor:pointer;">
128
- <i class="fas fa-user-tie"></i> Investigator
129
- <span class="sort" [ngClass]="{'asc': isAsc('Investigation Officer'), 'desc': isDesc('Investigation Officer'), 'neutral': !isAsc('Investigation Officer') && !isDesc('Investigation Officer')}"></span>
130
- </th>
131
- <th (click)="setSort('dateTime')" [attr.aria-sort]="ariaSort('dateTime')" style="cursor:pointer;">
132
- <i class="fas fa-calendar-alt"></i> Date &amp; Time
133
- <span class="sort" [ngClass]="{'asc': isAsc('dateTime'), 'desc': isDesc('dateTime'), 'neutral': !isAsc('dateTime') && !isDesc('dateTime')}"></span>
134
- </th>
135
- <th class="actions-col" style="text-align:left;"><i class="fas fa-cogs"></i> Actions</th>
136
- </tr>
137
- </thead>
138
- <tbody>
139
- <tr *ngFor="let c of rows, let i = index">
140
- <td class="select-col">{{ (currentPage -1) * pageSize + i +1 }}</td>
141
- <td><a (click)="navigateToCaseDetails(c)" style="cursor:pointer">{{ c.caseId || '—' }}</a></td>
142
- <td>
143
- <span class="status-label"
144
- [ngClass]="{
145
- 'status-open': c.status === 'Open',
146
- 'status-under': c.status === 'Under Investigation',
147
- 'status-closed': c.status === 'Closed'
148
- }">
149
- <span class="status-dot"></span>{{ c.status || '—' }}
150
- </span>
151
- </td>
152
- <td>{{ c.crime || '—' }}</td>
153
- <td>{{ c.police?.name || '—' }}</td>
154
- <td>{{ c.dateTime ? (c.dateTime | date:'M/d/yyyy HH:mm') : '—' }}</td>
155
- <td class="actions-col" style="text-align:left;">
156
- <button class="icon-btn view" (click)="navigateToCaseDetails(c)" title="View">
157
- <i class="fas fa-eye"></i>
158
- </button>
159
- <button class="icon-btn edit" (click)="editCase(c, i)" title="Edit">
160
- <i class="fas fa-edit"></i>
161
  </button>
162
- <button class="icon-btn delete" (click)="deleteCase(i)" title="Delete">
163
- <i class="fas fa-trash"></i>
 
 
 
 
 
 
 
 
 
 
 
164
  </button>
165
- </td>
166
- </tr>
167
- <tr *ngIf="rows.length ===0">
168
- <td colspan="7" class="empty">No records found.</td>
169
- </tr>
170
- </tbody>
171
- </table>
172
-
173
- <!-- Pagination Controls -->
174
- <div class="pagination-controls" style="display:flex;justify-content:center;align-items:center;margin:5px 0;gap:10px;">
175
- <style>
176
- .pagination-controls button {
177
- border: none;
178
- background: #f3f4f6;
179
- color: #333;
180
- border-radius: 8px;
181
- padding: 016px;
182
- min-width: 40px;
183
- min-height: 40px;
184
- font-size: 1.1em;
185
- font-weight: 500;
186
- box-shadow: 0 2px 8px rgba(0,0,0,0.04);
187
- transition: background 0.2s, color 0.2s, transform 0.2s;
188
- cursor: pointer;
189
- outline: none;
190
- }
191
-
192
- .pagination-controls button:hover:not(:disabled),
193
- .pagination-controls button:focus:not(:disabled) {
194
- background: #e3eafe;
195
- color: #1976d2;
196
- transform: scale(1.08);
197
- }
198
-
199
- .pagination-controls button.active {
200
- background: #1976d2;
201
- color: #fff;
202
- font-weight: bold;
203
- box-shadow: 0002px #90caf9;
204
- animation: pulseActive1s infinite;
205
- }
206
-
207
- @keyframes pulseActive {
208
- 0% {
209
- box-shadow: 0002px #90caf9;
210
- }
211
-
212
- 50% {
213
- box-shadow: 0006px #90caf9;
214
- }
215
-
216
- 100% {
217
- box-shadow: 0002px #90caf9;
218
- }
219
- }
220
-
221
- .pagination-controls span {
222
- font-size: 1.2em;
223
- color: #888;
224
- padding: 08px;
225
- }
226
- </style>
227
- <button (click)="prevPage()" [disabled]="currentPage ===1">«</button>
228
- <ng-container *ngFor="let page of getPagination()">
229
- <button *ngIf="page !== '...'" (click)="goToPage(page)" [class.active]="currentPage === page">{{ page }}</button>
230
- <span *ngIf="page === '...'">...</span>
231
- </ng-container>
232
- <button (click)="nextPage()" [disabled]="currentPage === totalPages">»</button>
233
  </div>
234
  </div>
235
 
236
- <!-- Results summary and page size selector -->
237
- <div style="display:flex;align-items:center;justify-content:flex-start;gap:24px;margin-bottom:12px;">
238
- <span style="font-size:1.1em;"><i class="fas fa-list-ol"></i> Results: {{ resultsStart }} - {{ resultsEnd }} of {{ resultsTotal }}</span>
239
- <select [(ngModel)]="pageSize" (change)="onPageSizeChange(pageSize)" style="padding:4px 12px;border-radius:8px;font-size:1em;">
240
- <option *ngFor="let size of pageSizeOptions" [value]="size">{{ size }}</option>
241
- </select>
242
- </div>
243
 
244
  <!-- Modal -->
245
- <!-- Modal white blur overlay for background -->
246
  <div class="modal-blur-overlay" *ngIf="showDetails"></div>
247
-
248
- <!-- Modal backdrop and modal as before -->
249
  <div class="modal-backdrop" *ngIf="showDetails" (click)="closeDetails()"></div>
250
  <div class="modal" *ngIf="showDetails" role="dialog" aria-modal="true" aria-labelledby="detailsTitle">
251
  <div class="modal-header">
252
  <h2 id="detailsTitle"><i class="fas fa-info-circle"></i> Case Details</h2>
 
253
  </div>
254
- <!-- View Modal: Show all subgroup pills and fields for each section -->
255
  <div class="modal-body" *ngIf="selectedCase as sc">
256
  <div class="modal-sections-grid">
257
  <ng-container *ngFor="let sectionKey of ['crime', 'suspect', 'notes']">
@@ -263,39 +252,35 @@
263
  {{ sectionKey === 'crime' ? 'Crime Details' : sectionKey === 'suspect' ? 'Suspect Details' : 'Evidence and Documents' }}
264
  </div>
265
  <ng-container *ngFor="let subgroup of getSubgroups(sectionKey)">
266
- <div class="subgroup-title" style="margin:10px 0 4px 0;font-weight:600;color:#1976d2;">
267
  <i class="fas fa-folder"></i> {{ subgroup }}
268
  </div>
269
  <div class="fields-grid">
270
  <ng-container *ngFor="let field of getFieldsForSubgroup(sectionKey, subgroup)">
271
- <div class="field-card" style="display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid #f3f4f6;">
272
- <span style="font-weight:500;color:#333;"><i class="fas fa-tag"></i> {{ field }}</span>
273
- <span style="color:#444;">{{ getFieldValue(sc, sectionKey, field) }}</span>
274
  </div>
275
  </ng-container>
276
  </div>
277
  </ng-container>
278
  </div>
279
  </ng-container>
280
- <!-- Show all other fields from formData not in the above structure -->
281
  <div class="section-block">
282
  <div class="section-title"><i class="fas fa-list"></i> All Entered Information</div>
283
  <div class="fields-grid">
284
- <div class="field-card" *ngFor="let key of objectKeys(sc)" style="display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid #f3f4f6;">
285
- <span style="font-weight:500;color:#333;"><i class="fas fa-tag"></i> {{ key }}</span>
286
- <span style="color:#444;">{{ getValue(sc, key) }}</span>
287
  </div>
288
  </div>
289
  </div>
290
  </div>
291
  </div>
 
292
  <div class="modal-footer">
293
- <button type="button" class="btn" (click)="closeDetails()"><i class="fas fa-times"></i> Close</button>
294
  </div>
295
  </div>
296
-
297
- <footer>
298
- <p>© 2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
299
- </footer>
300
-
301
-
 
1
+ <div class="app-container">
2
+ <!-- Modern UI header with logo and PyDetect title -->
3
+ <div class="site-header">
4
+ <div class="header-inner">
5
+ <div class="logo-cluster">
6
+ <span (click)="navigateHome()" style="cursor:pointer;display:flex;align-items:center;">
7
+ <img src="/assets/pykara-logo.png" alt="PyDetect Logo" class="logo-img-header" />
8
+ </span>
9
+ <div class="py-detect-title-header">
10
+ <span class="py-letter p">P</span>
11
+ <span class="py-letter y">Y</span>
12
+ <span class="py-shape"></span>
13
+ <span class="py-letter d">D</span>
14
+ <span class="py-letter e">E</span>
15
+ <span class="py-letter t">T</span>
16
+ <span class="py-letter e2">E</span>
17
+ <span class="py-letter c">C</span>
18
+ <span class="py-letter t2">T</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </div>
20
  </div>
21
+ <div class="header-actions-right">
22
+ <button class="back-btn" (click)="navigateBackToInfoPage()">
23
+ <span class="back-icon">←</span> Back
24
+ </button>
25
+ <button class="logout-btn" (click)="logout()">
26
+ <span class="logout-icon">⎋</span> Logout
27
+ </button>
28
+ </div>
29
+ </div>
30
+ </div>
31
 
32
+ <!-- Main Content Area -->
33
+ <div class="main-content">
34
+ <div class="content-container">
35
+ <!-- Record Card -->
36
+ <div class="record-card">
37
+ <!-- Analytics Section -->
38
+ <div class="analytics-blue">
39
+ <div class="record-header">
40
+ <div class="record-title-group">
41
+ <span class="record-title"><i class="fas fa-database"></i> Police Investigation Records</span>
42
+ <select class="record-dropdown">
43
+ <option>Recently Viewed</option>
44
+ <option>All Records</option>
45
+ </select>
46
+ </div>
47
+ <div class="record-search-container">
48
+ <span style="position:relative;">
49
+ <i class="fas fa-search" style="position:absolute;left:16px;top:50%;transform:translateY(-50%);color:rgba(255,255,255,0.7);"></i>
50
+ <input class="record-search" type="text" [(ngModel)]="q" (ngModelChange)="applyFilters()" placeholder="Search this list..." />
51
+ </span>
52
+ </div>
53
  </div>
54
+
55
+ <div class="analytics-cards">
56
+ <!-- Total Cases -->
57
+ <div class="summary-card total">
58
+ <div class="summary-left">
59
+ <div class="summary-label">Total Cases</div>
60
+ <div class="summary-value blue">{{ totalCases }}</div>
61
+ </div>
62
+ <div class="summary-icon"><i class="fas fa-folder-open"></i></div>
63
+ </div>
64
+ <!-- Open Cases -->
65
+ <div class="summary-card open">
66
+ <div class="summary-left">
67
+ <div class="summary-label">Open</div>
68
+ <div class="summary-value green">{{ openCases }}</div>
69
+ </div>
70
+ <div class="summary-icon"><i class="fas fa-exclamation-circle"></i></div>
71
+ </div>
72
+ <!-- Closed Cases -->
73
+ <div class="summary-card closed">
74
+ <div class="summary-left">
75
+ <div class="summary-label">Closed</div>
76
+ <div class="summary-value red">{{ closedCases }}</div>
77
+ </div>
78
+ <div class="summary-icon"><i class="fas fa-check-circle"></i></div>
79
+ </div>
80
  </div>
 
81
  </div>
82
+
83
+ <div class="record-meta">
84
+ {{ rows.length }} items • Updated a few seconds ago
 
 
 
 
 
85
  </div>
 
 
86
 
87
+ <!-- Filter bar -->
88
+ <div class="filter-bar">
89
+ <span class="filter-icon"><i class="fas fa-filter"></i></span>
90
+ <select [(ngModel)]="filterCrimeType" class="filter-select" (change)="applyFilters()">
91
+ <option value="">Crime Type</option>
92
+ <option *ngFor="let type of crimeTypes">{{ type }}</option>
93
+ </select>
94
+ <select [(ngModel)]="filterStatus" class="filter-select" (change)="applyFilters()">
95
+ <option value="">Select Status</option>
96
+ <option *ngFor="let status of statusTypes">{{ status }}</option>
97
+ </select>
98
+ <select [(ngModel)]="filterLocation" class="filter-select" (change)="applyFilters()">
99
+ <option value="">Location</option>
100
+ <option *ngFor="let loc of locations">{{ loc }}</option>
101
+ </select>
102
+ <select [(ngModel)]="filterOfficer" class="filter-select" (change)="applyFilters()">
103
+ <option value="">Officer</option>
104
+ <option *ngFor="let officer of officers">{{ officer }}</option>
105
+ </select>
106
+ <button class="filter-btn apply" (click)="applyFilters()">
107
+ <i class="fas fa-check"></i> Apply
108
+ </button>
109
+ <button class="filter-btn reset" (click)="resetFilters()">
110
+ <i class="fas fa-undo"></i> Reset
111
+ </button>
112
+ </div>
113
 
114
+ <!-- Table Container -->
115
+ <div class="table-container">
116
+ <div class="table-wrapper">
117
+ <table class="record-table">
118
+ <thead>
119
+ <tr>
120
+ <th class="sl-no-col">
121
+ <i class="fas fa-list-ol"></i> Sl. No
122
+ </th>
123
+ <th (click)="setSort('caseId')" [attr.aria-sort]="ariaSort('caseId')" class="sortable">
124
+ <i class="fas fa-id-badge"></i> Case ID
125
+ <span class="sort" [ngClass]="{'asc': isAsc('caseId'), 'desc': isDesc('caseId'), 'neutral': !isAsc('caseId') && !isDesc('caseId')}"></span>
126
+ </th>
127
+ <th (click)="setSort('status')" [attr.aria-sort]="ariaSort('status')" class="sortable">
128
+ <i class="fas fa-info-circle"></i> Status
129
+ <span class="sort" [ngClass]="{'asc': isAsc('status'), 'desc': isDesc('status'), 'neutral': !isAsc('status') && !isDesc('status')}"></span>
130
+ </th>
131
+ <th (click)="setSort('crime')" [attr.aria-sort]="ariaSort('crime')" class="sortable">
132
+ <i class="fas fa-gavel"></i> Crime Type
133
+ <span class="sort" [ngClass]="{'asc': isAsc('crime'), 'desc': isDesc('crime'), 'neutral': !isAsc('crime') && !isDesc('crime')}"></span>
134
+ </th>
135
+ <th (click)="setSort('Investigation Officer')" [attr.aria-sort]="ariaSort('Investigation Officer')" class="sortable">
136
+ <i class="fas fa-user-tie"></i> Investigator
137
+ <span class="sort" [ngClass]="{'asc': isAsc('Investigation Officer'), 'desc': isDesc('Investigation Officer'), 'neutral': !isAsc('Investigation Officer') && !isDesc('Investigation Officer')}"></span>
138
+ </th>
139
+ <th (click)="setSort('dateTime')" [attr.aria-sort]="ariaSort('dateTime')" class="sortable">
140
+ <i class="fas fa-calendar-alt"></i> Date & Time
141
+ <span class="sort" [ngClass]="{'asc': isAsc('dateTime'), 'desc': isDesc('dateTime'), 'neutral': !isAsc('dateTime') && !isDesc('dateTime')}"></span>
142
+ </th>
143
+ <th class="actions-col">
144
+ <i class="fas fa-cogs"></i> Actions
145
+ </th>
146
+ </tr>
147
+ </thead>
148
+ <tbody>
149
+ <tr *ngFor="let c of rows; let i = index">
150
+ <td class="sl-no-col">{{ (currentPage - 1) * pageSize + i + 1 }}</td>
151
+ <td>
152
+ <a (click)="navigateToCaseDetails(c)" class="case-id-link">{{ c.caseId || 'Not Assigned' }}</a>
153
+ </td>
154
+ <td>
155
+ <span class="status-badge" [ngClass]="{
156
+ 'status-open': c.status === 'Open',
157
+ 'status-under': c.status === 'Under Investigation',
158
+ 'status-closed': c.status === 'Closed'
159
+ }">
160
+ <span class="status-dot"></span>
161
+ {{ c.status || 'Not Assigned' }}
162
+ </span>
163
+ </td>
164
+ <td [class.empty-value]="!c.crime">{{ c.crime || 'Not Assigned' }}</td>
165
+ <td [class.empty-value]="!c.police?.name">{{ c.police?.name || 'Not Assigned' }}</td>
166
+ <td [class.empty-value]="!c.dateTime">{{ c.dateTime ? (c.dateTime | date:'M/d/yyyy HH:mm') : 'Not Assigned' }}</td>
167
+ <td class="actions-col">
168
+ <div class="action-buttons">
169
+ <button class="action-btn view" (click)="navigateToCaseDetails(c)" title="View Case">
170
+ <i class="fas fa-eye"></i>
171
+ </button>
172
+ <button class="action-btn edit" (click)="editCase(c, i)" title="Edit Case">
173
+ <i class="fas fa-edit"></i>
174
+ </button>
175
+ <button class="action-btn delete" (click)="deleteCase(i)" title="Delete Case">
176
+ <i class="fas fa-trash"></i>
177
+ </button>
178
+ </div>
179
+ </td>
180
+ </tr>
181
+ <tr *ngIf="rows.length === 0">
182
+ <td colspan="7" class="empty">
183
+ <i class="fas fa-inbox"></i> No records found.
184
+ </td>
185
+ </tr>
186
+ </tbody>
187
+ </table>
188
+ </div>
189
+ </div>
190
 
191
+ <!-- Bottom Controls -->
192
+ <div class="bottom-controls">
193
+ <div class="results-summary">
194
+ <div class="results-info">
195
+ <i class="fas fa-list-ol"></i>
196
+ <span>Results: {{ resultsStart }} - {{ resultsEnd }} of {{ resultsTotal }}</span>
197
+ </div>
198
+ <div class="page-size-selector">
199
+ <span>Show:</span>
200
+ <select [(ngModel)]="pageSize" (change)="onPageSizeChange(pageSize)">
201
+ <option *ngFor="let size of pageSizeOptions" [value]="size">{{ size }}</option>
202
+ </select>
203
+ </div>
204
+ </div>
205
+
206
+ <div class="pagination-controls">
207
+ <button class="page-btn prev" (click)="prevPage()" [disabled]="currentPage === 1">
208
+ <i class="fas fa-chevron-left"></i>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  </button>
210
+ <div class="page-numbers">
211
+ <ng-container *ngFor="let page of getPagination()">
212
+ <button *ngIf="page !== '...'"
213
+ class="page-number"
214
+ [class.active]="currentPage === page"
215
+ (click)="goToPage(page)">
216
+ {{ page }}
217
+ </button>
218
+ <span *ngIf="page === '...'" class="page-ellipsis">...</span>
219
+ </ng-container>
220
+ </div>
221
+ <button class="page-btn next" (click)="nextPage()" [disabled]="currentPage === totalPages">
222
+ <i class="fas fa-chevron-right"></i>
223
  </button>
224
+ </div>
225
+ </div>
226
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  </div>
228
  </div>
229
 
230
+ <!-- Footer from provided design -->
231
+ <footer>
232
+ <p>©2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
233
+ </footer>
 
 
 
234
 
235
  <!-- Modal -->
 
236
  <div class="modal-blur-overlay" *ngIf="showDetails"></div>
 
 
237
  <div class="modal-backdrop" *ngIf="showDetails" (click)="closeDetails()"></div>
238
  <div class="modal" *ngIf="showDetails" role="dialog" aria-modal="true" aria-labelledby="detailsTitle">
239
  <div class="modal-header">
240
  <h2 id="detailsTitle"><i class="fas fa-info-circle"></i> Case Details</h2>
241
+ <button class="modal-close" (click)="closeDetails()"><i class="fas fa-times"></i></button>
242
  </div>
243
+
244
  <div class="modal-body" *ngIf="selectedCase as sc">
245
  <div class="modal-sections-grid">
246
  <ng-container *ngFor="let sectionKey of ['crime', 'suspect', 'notes']">
 
252
  {{ sectionKey === 'crime' ? 'Crime Details' : sectionKey === 'suspect' ? 'Suspect Details' : 'Evidence and Documents' }}
253
  </div>
254
  <ng-container *ngFor="let subgroup of getSubgroups(sectionKey)">
255
+ <div class="subgroup-title">
256
  <i class="fas fa-folder"></i> {{ subgroup }}
257
  </div>
258
  <div class="fields-grid">
259
  <ng-container *ngFor="let field of getFieldsForSubgroup(sectionKey, subgroup)">
260
+ <div class="field-card">
261
+ <span class="field-label"><i class="fas fa-tag"></i> {{ field }}</span>
262
+ <span class="field-value">{{ getFieldValue(sc, sectionKey, field) }}</span>
263
  </div>
264
  </ng-container>
265
  </div>
266
  </ng-container>
267
  </div>
268
  </ng-container>
269
+
270
  <div class="section-block">
271
  <div class="section-title"><i class="fas fa-list"></i> All Entered Information</div>
272
  <div class="fields-grid">
273
+ <div class="field-card" *ngFor="let key of objectKeys(sc)">
274
+ <span class="field-label"><i class="fas fa-tag"></i> {{ key }}</span>
275
+ <span class="field-value">{{ getValue(sc, key) }}</span>
276
  </div>
277
  </div>
278
  </div>
279
  </div>
280
  </div>
281
+
282
  <div class="modal-footer">
283
+ <button type="button" class="btn btn-close" (click)="closeDetails()"><i class="fas fa-times"></i> Close</button>
284
  </div>
285
  </div>
286
+ </div>
 
 
 
 
 
src/app/recordpage/recordpage.component.ts CHANGED
@@ -1,7 +1,7 @@
1
  import { Component, OnInit, OnDestroy } from '@angular/core';
2
  import { Router } from '@angular/router';
3
  import { CaseStoreService, PoliceCase } from '../shared/case-store.service';
4
- import { InfopageComponent } from '../infopage/infopage.component';
5
  import { Subscription } from 'rxjs';
6
 
7
  @Component({
@@ -19,289 +19,30 @@ export class RecordpageComponent implements OnInit, OnDestroy {
19
 
20
  // Pagination
21
  currentPage: number = 1;
22
- pageSize: number = 5; // Change to 20 if you want 20 per page
 
 
23
  get totalPages(): number {
24
  return Math.ceil(this.filteredCases.length / this.pageSize) || 1;
25
  }
 
26
  get pagedCases(): PoliceCase[] {
27
  const start = (this.currentPage - 1) * this.pageSize;
28
  return this.filteredCases.slice(start, start + this.pageSize);
29
  }
30
- setPage(page: number) {
31
- if (page < 1 || page > this.totalPages) return;
32
- this.currentPage = page;
33
- }
34
- nextPage() { this.setPage(this.currentPage + 1); }
35
- prevPage() { this.setPage(this.currentPage - 1); }
36
 
37
- // modal
38
  showDetails = false;
39
  selectedCase: PoliceCase | null = null;
40
  selectedIndex = -1;
41
 
42
- // quick filter
43
  q = '';
44
 
45
- // simple sort state
46
- sortKey: 'caseId' | 'crime' | 'dateTime' | 'location' | 'status' | 'Investigation Officer' = 'dateTime';
47
  sortDir: 'asc' | 'desc' = 'desc';
48
 
49
- objectKeys = Object.keys;
50
-
51
- // For modal subgroup pills
52
- selectedSubgroup: any = {
53
- crime: 'Identification & Timing',
54
- suspect: 'Identity',
55
- notes: 'Investigation Notes'
56
- };
57
-
58
- // Reference infopage section/subgroup/fields structure
59
- sections = new InfopageComponent(null as any, null as any).sections;
60
-
61
- getSubgroups(sectionKey: string): string[] {
62
- return Object.keys(this.sections[sectionKey].subgroups);
63
- }
64
-
65
- getFieldsForSubgroup(sectionKey: string, subgroup: string): string[] {
66
- return this.sections[sectionKey].subgroups[subgroup] || [];
67
- }
68
-
69
- selectSubgroup(sectionKey: string, subgroup: string) {
70
- this.selectedSubgroup[sectionKey] = subgroup;
71
- }
72
-
73
- getFieldValue(sc: any, sectionKey: string, field: string): any {
74
- // Map field labels to PoliceCase property paths
75
- const fieldMap: { [key: string]: string | string[] } = {
76
- // Crime Details
77
- 'Case ID': 'caseId',
78
- 'FIR / Ref #': 'firRef',
79
- 'Crime Type': 'crime',
80
- 'Case Category': 'caseCategory',
81
- 'Date & Time (Entry)': 'dateTime',
82
- 'Occurred From': 'occurredFrom',
83
- 'Occurred To': 'occurredTo',
84
- 'Time Reported': 'timeReported',
85
- 'Time Discovered': 'timeDiscovered',
86
- 'Country': 'country',
87
- 'State': 'state',
88
- 'District': 'district',
89
- 'Number of Victims': 'numberOfVictims',
90
- 'Brief Description': 'briefDescription',
91
- 'Location': ['police', 'address'],
92
- 'Jurisdiction / PS': 'jurisdiction',
93
- 'Scene Type': 'sceneType',
94
- 'Reported By': 'reportedBy',
95
- 'Reported Contact': 'reportedContact',
96
- 'Witness Count': 'witnessCount',
97
- 'Victim Name': 'victimName',
98
- 'Victim Contact': 'victimContact',
99
- 'Victim Summary': 'victimSummary',
100
- 'Suspected Offender Known?': 'suspectedOffenderKnown',
101
- 'Suspect Link': 'suspectLink',
102
- 'Legal Sections / Charges': 'legalSections',
103
- 'Offence Category': 'offenceCategory',
104
- 'Offence Description': 'offenceDescription',
105
- 'Suspected Motive': 'suspectedMotive',
106
- 'Confirmed Motive': 'confirmedMotive',
107
- 'Weapon Involved': 'weaponInvolved',
108
- 'Property Loss / Damage': 'propertyLoss',
109
- 'Evidence Collected': 'evidenceCollected',
110
- 'Forensic Tests Required': 'forensicTestsRequired',
111
- 'Scene Condition': 'sceneCondition',
112
- 'Photos / Video?': 'photosVideo',
113
- 'CCTV Present?': 'cctvPresent',
114
- 'CCTV Sources / IDs': 'cctvSources',
115
- 'Physical Evidence (list)': 'physicalEvidence',
116
- 'Chain of Custody?': 'chainOfCustody',
117
- 'Digital Evidence': 'digitalEvidence',
118
- 'Evidence Storage Reference': 'evidenceStorageReference',
119
- 'Investigating Officer': ['police', 'name'],
120
- 'Duty Person': ['police', 'dutyPerson'],
121
- 'Supervising Officer': ['police', 'supervisingOfficer'],
122
- 'Patrol Notes': ['police', 'patrolNotes'],
123
- 'Arrest Made': 'arrestMade',
124
- 'Arrest Location': 'arrestLocation',
125
- 'Initial Actions Taken': 'initialActionsTaken',
126
- 'riskLevel': 'riskLevel',
127
- 'Confidentiality': 'confidentiality',
128
- 'Biometric / Forensic IDs': 'biometricIds',
129
- 'DNA Ref ID': 'dnaRefId',
130
- 'Fingerprint ID': 'fingerprintId',
131
- 'Case Status': 'status',
132
- 'Linked Cases': 'linkedCases',
133
- 'arrestCount': 'arrestCount',
134
- 'Case Priority': 'casePriority',
135
- 'Follow-up Date': 'followUpDate',
136
- 'Court Case ID': 'courtCaseId',
137
- 'Next Hearing Date': 'nextHearingDate',
138
- 'Final Summary': 'finalSummary',
139
- 'Remark': 'remark',
140
- // Suspect Details
141
- 'Suspect ID': ['accused', 'suspectId'],
142
- 'Suspect Name': ['accused', 'name'],
143
- 'Alias / Nickname': ['accused', 'alias'],
144
- 'Age': ['accused', 'age'],
145
- 'Gender': ['accused', 'gender'],
146
- 'Nationality': ['accused', 'nationality'],
147
- 'Nationality ID / Passport Number': ['accused', 'passportNumber'],
148
- 'Languages': ['accused', 'languages'],
149
- 'Address': ['accused', 'address'],
150
- 'Known Aliases': ['accused', 'knownAliases'],
151
- 'Government ID': ['accused', 'governmentId'],
152
- 'Height (cm)': ['accused', 'height'],
153
- 'Weight (kg)': ['accused', 'weight'],
154
- 'Build': ['accused', 'build'],
155
- 'Hair Color': ['accused', 'hairColor'],
156
- 'Eye Color': ['accused', 'eyeColor'],
157
- 'Distinguishing Marks': ['accused', 'distinguishingMarks'],
158
- 'Tattoo Details': ['accused', 'tattooDetails'],
159
- 'Scar Details': ['accused', 'scarDetails'],
160
- 'Photo Upload': ['accused', 'photoUpload'],
161
- 'Employment': ['accused', 'employment'],
162
- 'Education': ['accused', 'education'],
163
- 'Occupation': ['accused', 'occupation'],
164
- 'Company': ['accused', 'company'],
165
- 'Workplace Address': ['accused', 'workplaceAddress'],
166
- 'Marital Status': ['accused', 'maritalStatus'],
167
- 'Known Habits': ['accused', 'knownHabits'],
168
- 'Known Financial Details': ['accused', 'knownFinancialDetails'],
169
- 'Associate Names': ['accused', 'associateNames'],
170
- 'Gang Affiliation': ['accused', 'gangAffiliation'],
171
- 'Family Connections': ['accused', 'familyConnections'],
172
- 'Social Media Handles': ['accused', 'socialMediaHandles'],
173
- 'Criminal History': ['accused', 'criminalHistory'],
174
- 'Prior Arrests': ['accused', 'priorArrests'],
175
- 'Probation/Parole Status': ['accused', 'probationStatus'],
176
- // Notes/Evidence
177
- 'Initial Findings': ['police', 'information'],
178
- 'Detailed Notes': ['notes', 'detailedNotes'],
179
- 'Status': 'status',
180
- 'Version History / Updates': ['notes', 'versionHistory'],
181
- 'Evidence Photos': ['legal', 'evidencePhotos'],
182
- 'Evidence Videos': ['legal', 'evidenceVideos'],
183
- 'Evidence Documents': ['legal', 'evidenceDocuments'],
184
- 'Links to Evidence': ['legal', 'linksToEvidence'],
185
- 'Final Recommendations': ['legal', 'finalRecommendations'],
186
- 'Witness Statements': ['legal', 'witnessStatements'],
187
- 'Confessions': ['legal', 'confessions'],
188
- // Audit Fields
189
- 'Created By': 'createdBy',
190
- 'Creation Date': 'creationDate',
191
- 'Last Updated': 'lastUpdated',
192
- 'Verified By': 'verifiedBy'
193
- };
194
-
195
- const path = fieldMap[field] || field;
196
- let value: any = undefined;
197
- if (Array.isArray(path)) {
198
- let v = sc;
199
- for (const p of path) {
200
- if (v && v[p] !== undefined) v = v[p];
201
- else { v = undefined; break; }
202
- }
203
- value = v;
204
- } else {
205
- value = sc && sc[path] !== undefined ? sc[path] : undefined;
206
- }
207
-
208
- // Fallback: try raw formData (array or object) saved with the case
209
- if (value === null || value === undefined || value === '') {
210
- try {
211
- const fd = this.getFormDataArray(sc);
212
- const norm = (s: any) => {
213
- if (s === null || s === undefined) return '';
214
- let t = String(s).toLowerCase();
215
- t = t.replace(/&/g, '');
216
- t = t.replace(/and/g, '');
217
- t = t.replace(/entry/g, '');
218
- t = t.replace(/\s+/g, '');
219
- return t.replace(/[^a-z0-9]/g, '');
220
- };
221
-
222
- if (fd && fd.length) {
223
- let kv = fd.find(k => k && String(k.key).toLowerCase() === String(field).toLowerCase());
224
- if (kv) value = kv.value;
225
- if (value === null || value === undefined || value === '') {
226
- const fieldNorm = norm(field);
227
- const pathName = Array.isArray(path) ? path[path.length -1] : String(path);
228
- const pathNorm = norm(pathName);
229
- kv = fd.find(k => k && (norm(k.key) === fieldNorm || norm(k.key) === pathNorm || norm(k.key).includes(fieldNorm) || fieldNorm.includes(norm(k.key))));
230
- if (kv) value = kv.value;
231
- }
232
- }
233
-
234
- if ((value === null || value === undefined || value === '') && sc && sc.formData && typeof sc.formData === 'object') {
235
- if (sc.formData[field] !== undefined) value = sc.formData[field];
236
- else {
237
- const fieldNorm = (s: any) => s === null || s === undefined ? '' : String(s).toLowerCase().replace(/[^a-z0-9]/g, '');
238
- const target = fieldNorm(field);
239
- for (const k of Object.keys(sc.formData)) {
240
- if (fieldNorm(k) === target || k.toLowerCase() === field.toLowerCase() || k.toLowerCase().includes(field.toLowerCase())) {
241
- value = sc.formData[k];
242
- break;
243
- }
244
- }
245
- }
246
- }
247
- } catch (e) {
248
- // ignore
249
- }
250
- }
251
-
252
- // Normalize and format
253
- if (value === null || value === undefined || value === '') return '—';
254
-
255
- // Date formatting
256
- if ((this.dateTimeFields && this.dateTimeFields.has(field)) || (this.dateFields && this.dateFields.has(field))) {
257
- const d = new Date(value);
258
- if (!isNaN(d.getTime())) {
259
- if (this.dateFields && this.dateFields.has(field)) return d.toISOString().slice(0,10);
260
- return d.toLocaleString();
261
- }
262
- }
263
-
264
- if (typeof value === 'object') return this.formatFormValue(value);
265
- return value;
266
- }
267
-
268
- getValue(obj: any, key: string): any {
269
- const v = obj && obj[key] !== undefined ? obj[key] : undefined;
270
- if (v === null || v === undefined || v === '') return '—';
271
- if (typeof v === 'object') return this.formatFormValue(v);
272
- if ((this.dateTimeFields && this.dateTimeFields.has(key)) || (this.dateFields && this.dateFields.has(key))) {
273
- const d = new Date(v);
274
- if (!isNaN(d.getTime())) {
275
- if (this.dateFields && this.dateFields.has(key)) return d.toISOString().slice(0,10);
276
- return d.toLocaleString();
277
- }
278
- }
279
- return v;
280
- }
281
-
282
- // Helpers for handling stored formData
283
- isFormDataArray(fd: any): boolean {
284
- return Array.isArray(fd) && fd.length >0 && fd.every((item: any) => item && Object.prototype.hasOwnProperty.call(item, 'key'));
285
- }
286
-
287
- getFormDataArray(caseObj: any): Array<{ key: string; value: any }> {
288
- if (!caseObj || !caseObj.formData) return [];
289
- const fd = caseObj.formData;
290
- if (this.isFormDataArray(fd)) return fd as Array<{ key: string; value: any }>;
291
- if (typeof fd === 'object') return Object.keys(fd).map(k => ({ key: k, value: fd[k] }));
292
- return [{ key: 'value', value: fd }];
293
- }
294
-
295
- formatFormValue(value: any): string {
296
- if (value === null || value === undefined || value === '') return '—';
297
- if (typeof value === 'object') {
298
- try { return JSON.stringify(value, null,2); } catch { return String(value); }
299
- }
300
- return String(value);
301
- }
302
-
303
- constructor(private caseStore: CaseStoreService, private router: Router) { }
304
-
305
  // Filter state
306
  filterCrimeType: string = '';
307
  filterStatus: string = '';
@@ -315,11 +56,22 @@ export class RecordpageComponent implements OnInit, OnDestroy {
315
 
316
  filteredCases: PoliceCase[] = [];
317
 
318
- // selection state
319
  allSelected: boolean = false;
320
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  ngOnInit(): void {
322
- // Subscribe to case store so UI updates automatically when new cases are added/updated
323
  if (typeof this.caseStore.getCases$ === 'function') {
324
  this.casesSub = this.caseStore.getCases$().subscribe((cases: PoliceCase[]) => {
325
  this.cases = cases || [];
@@ -329,7 +81,6 @@ export class RecordpageComponent implements OnInit, OnDestroy {
329
  this.applySort();
330
  });
331
  } else {
332
- // Fallback for older API
333
  this.load();
334
  this.populateFilterOptions();
335
  this.applyFilters();
@@ -341,17 +92,72 @@ export class RecordpageComponent implements OnInit, OnDestroy {
341
  if (this.casesSub) this.casesSub.unsubscribe();
342
  }
343
 
344
- load(): void {
345
- this.cases = this.caseStore.getPoliceCases();
346
- this.cases.forEach((c: any) => { if (c.selected === undefined) c.selected = false; });
347
- this.populateFilterOptions();
348
- this.applyFilters();
349
  }
350
 
351
- toggleSelectAll(event: Event): void {
352
- const checked = (event.target as HTMLInputElement).checked;
353
- this.allSelected = checked;
354
- this.rows.forEach((c: any) => c.selected = checked);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  }
356
 
357
  populateFilterOptions() {
@@ -362,14 +168,15 @@ export class RecordpageComponent implements OnInit, OnDestroy {
362
  }
363
 
364
  applyFilters() {
365
- // First, filter by dropdowns
366
  let filtered = this.cases.filter(c =>
367
  (!this.filterCrimeType || c.crime === this.filterCrimeType) &&
368
  (!this.filterStatus || c.status === this.filterStatus) &&
369
  (!this.filterLocation || c.police?.address === this.filterLocation) &&
370
  (!this.filterOfficer || c.police?.name === this.filterOfficer)
371
  );
372
- // Then, filter by search query
 
373
  const s = (this.q || '').toLowerCase();
374
  if (s) {
375
  filtered = filtered.filter(c =>
@@ -380,8 +187,9 @@ export class RecordpageComponent implements OnInit, OnDestroy {
380
  (c.police?.name || '').toLowerCase().includes(s)
381
  );
382
  }
 
383
  this.filteredCases = filtered;
384
- this.currentPage =1; // Reset to first page on filter
385
  this.applySort();
386
  }
387
 
@@ -390,23 +198,11 @@ export class RecordpageComponent implements OnInit, OnDestroy {
390
  this.filterStatus = '';
391
  this.filterLocation = '';
392
  this.filterOfficer = '';
 
393
  this.applyFilters();
394
  }
395
 
396
- // search over visible columns (no accused)
397
- get filtered(): PoliceCase[] {
398
- const s = (this.q || '').toLowerCase();
399
- if (!s) return this.cases;
400
- return this.cases.filter(c =>
401
- (c.caseId || '').toString().toLowerCase().includes(s) ||
402
- (c.crime || '').toLowerCase().includes(s) ||
403
- (c.police?.address || '').toLowerCase().includes(s) ||
404
- (c.status || '').toLowerCase().includes(s) ||
405
- (c.police?.name || '').toLowerCase().includes(s)
406
- );
407
- }
408
-
409
- // sorting helpers
410
  setSort(key: typeof this.sortKey) {
411
  if (this.sortKey === key) {
412
  this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc';
@@ -416,12 +212,15 @@ export class RecordpageComponent implements OnInit, OnDestroy {
416
  }
417
  this.applySort();
418
  }
 
419
  isAsc(key: typeof this.sortKey) {
420
  return this.sortKey === key && this.sortDir === 'asc';
421
  }
 
422
  isDesc(key: typeof this.sortKey) {
423
  return this.sortKey === key && this.sortDir === 'desc';
424
  }
 
425
  ariaSort(key: typeof this.sortKey) {
426
  return this.sortKey === key ? (this.sortDir === 'asc' ? 'ascending' : 'descending') : 'none';
427
  }
@@ -429,8 +228,10 @@ export class RecordpageComponent implements OnInit, OnDestroy {
429
  applySort() {
430
  const key = this.sortKey;
431
  const dir = this.sortDir;
 
432
  this.filteredCases.sort((a, b) => {
433
  let aVal: any, bVal: any;
 
434
  switch (key) {
435
  case 'caseId':
436
  aVal = a.caseId || '';
@@ -441,12 +242,8 @@ export class RecordpageComponent implements OnInit, OnDestroy {
441
  bVal = b.crime || '';
442
  break;
443
  case 'dateTime':
444
- aVal = a.dateTime ? new Date(a.dateTime).getTime() :0;
445
- bVal = b.dateTime ? new Date(b.dateTime).getTime() :0;
446
- break;
447
- case 'location':
448
- aVal = a.police?.address || '';
449
- bVal = b.police?.address || '';
450
  break;
451
  case 'status':
452
  aVal = a.status || '';
@@ -460,175 +257,378 @@ export class RecordpageComponent implements OnInit, OnDestroy {
460
  aVal = '';
461
  bVal = '';
462
  }
463
- if (aVal < bVal) return dir === 'asc' ? -1 :1;
464
- if (aVal > bVal) return dir === 'asc' ?1 : -1;
 
465
  return 0;
466
  });
467
  }
468
 
469
- // Update your table to use filteredCases instead of rows
470
- get rows(): PoliceCase[] {
471
- return this.pagedCases;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
472
  }
473
 
 
 
 
 
 
 
 
 
 
 
 
 
474
  openDetails(c: PoliceCase, i: number): void {
475
  this.selectedCase = c;
476
  this.selectedIndex = i;
477
  this.showDetails = true;
478
- document.body.classList.add('modal-open');
479
  }
480
 
481
  closeDetails(): void {
482
  this.showDetails = false;
483
  this.selectedCase = null;
484
  this.selectedIndex = -1;
485
- document.body.classList.remove('modal-open');
486
  }
487
 
 
488
  editCase(c: PoliceCase, i: number): void {
489
- // Navigate to /infopage/:id for editing and pass prefill data in navigation state
490
  const prefill: Record<string, any> = {};
 
491
  try {
492
- // If case.formData is stored as array of {key,value}
493
  const fd = (c as any).formData;
494
  if (fd && Array.isArray(fd)) {
495
  (fd as Array<any>).forEach(kv => { if (kv && kv.key) prefill[kv.key] = kv.value; });
496
  } else if (fd && typeof fd === 'object') {
497
  Object.assign(prefill, fd as Record<string, any>);
498
  }
499
- // Also include mapped top-level properties for convenience
 
500
  if (c.caseId) prefill['Case ID'] = c.caseId;
501
  if (c.crime) prefill['Crime Type'] = c.crime;
502
  if (c.dateTime) prefill['Date & Time (Entry)'] = c.dateTime;
503
- if (c.police && c.police.address) prefill['Location'] = c.police.address;
504
- if (c.police && c.police.name) prefill['Investigating Officer'] = c.police.name;
505
- if (c.accused && c.accused.name) prefill['Suspect Name'] = c.accused.name;
506
  } catch (e) {
507
- // ignore
508
  }
509
 
510
- this.router.navigate(['/infopage', c.caseId], { state: { from: 'record', returnId: c.caseId, prefillFormData: prefill, case: c } });
 
 
 
 
 
 
 
511
  }
512
 
513
- // resolve origin dynamically from current router url
514
- private resolveOrigin(): string {
515
- try {
516
- const cur = this.router.url || '';
517
- if (cur.includes('/case-details')) return 'case-details';
518
- if (cur.includes('/record')) return 'record';
519
- } catch {}
520
- return 'record';
521
  }
522
 
523
  navigateToCaseDetails(c: PoliceCase): void {
524
  if (!c || !c.caseId) return;
525
- const origin = this.resolveOrigin();
526
- // navigate to new summary page — include source so summary can navigate back
527
- const from = origin;
528
- this.router.navigate(['/case-details-summary-page', c.caseId], { queryParams: { from, returnId: c.caseId }, state: { case: c, from, returnId: c.caseId } });
529
- }
530
 
531
- viewSummary(caseId: string) {
532
- const origin = this.resolveOrigin();
533
- this.router.navigate(['/case-details-summary-page', caseId], { queryParams: { from: origin, returnId: caseId }, state: { from: origin, returnId: caseId } });
 
 
 
 
 
 
 
 
 
 
534
  }
535
 
536
- onModernSearch() {
537
- // Optionally, you can trigger filtering or any other logic here
538
- // For now, just prevent form submission from reloading the page
539
- return false;
540
- }
541
 
542
- navigateHome(): void {
543
- this.router.navigate(['/']);
544
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
545
 
546
- onNewRecord(): void {
547
- // Placeholder: implement record creation logic or modal here
548
- alert('New record functionality coming soon!');
549
- }
550
 
551
- deleteCase(index: number): void {
552
- if (confirm('Are you sure you want to delete this case?')) {
553
- this.caseStore.deletePoliceCaseAt(index);
554
- this.load();
 
 
 
 
 
555
  }
556
- }
557
 
558
- verifyCase(index: number): void {
559
- const adminName = 'Admin'; // Replace with actual admin name if available
560
- const updated = { ...this.cases[index], verifiedBy: adminName, lastUpdated: new Date().toISOString() };
561
- this.caseStore.updatePoliceCaseAt(index, updated);
562
- this.load();
563
- }
 
 
 
 
 
 
 
564
 
565
- get totalCases(): number {
566
- return this.cases.length;
567
- }
568
- get openCases(): number {
569
- return this.cases.filter(c => c.status === 'Open').length;
570
- }
571
- get closedCases(): number {
572
- return this.cases.filter(c => c.status === 'Closed').length;
573
- }
574
 
575
- getPagination(): (number | string)[] {
576
- const pages: (number | string)[] = [];
577
- const total = this.totalPages;
578
- if (total <= 7) {
579
- for (let i = 1; i <= total; i++) pages.push(i);
580
- } else {
581
- if (this.currentPage <= 4) {
582
- for (let i = 1; i <= 5; i++) pages.push(i);
583
- pages.push('...');
584
- pages.push(total);
585
- } else if (this.currentPage >= total - 3) {
586
- pages.push(1);
587
- pages.push('...');
588
- for (let i = total - 4; i <= total; i++) pages.push(i);
589
- } else {
590
- pages.push(1);
591
- pages.push('...');
592
- for (let i = this.currentPage - 1; i <= this.currentPage + 1; i++) pages.push(i);
593
- pages.push('...');
594
- pages.push(total);
595
  }
596
  }
597
- return pages;
598
- }
599
 
600
- goToPage(page: string | number) {
601
- if (typeof page === 'number') {
602
- this.setPage(page);
 
 
 
 
 
 
 
603
  }
 
 
 
604
  }
605
 
606
- pageSizeOptions: number[] = [5, 10, 20, 50];
607
- onPageSizeChange(size: number) {
608
- this.pageSize = size;
609
- this.currentPage = 1;
 
 
 
 
 
 
 
 
610
  }
611
- get resultsStart(): number {
612
- return this.filteredCases.length === 0 ? 0 : (this.currentPage - 1) * this.pageSize + 1;
 
 
613
  }
614
- get resultsEnd(): number {
615
- return Math.min(this.currentPage * this.pageSize, this.filteredCases.length);
 
 
 
 
 
616
  }
617
- get resultsTotal(): number {
618
- return this.filteredCases.length;
 
 
 
 
 
 
 
 
 
619
  }
620
 
621
- goToDetect(caseId: string): void {
622
- this.router.navigate(['/py-detect'], { state: { caseId } });
 
 
 
623
  }
624
 
625
- navigateBackToInfoPage(): void {
626
- this.router.navigate(['/infopage']);
627
  }
628
 
629
- logout(): void {
630
- // Implement your logout logic here (clear session, etc.)
631
- // For now, just redirect to home/login
632
- window.location.href = '/';
633
  }
634
  }
 
1
  import { Component, OnInit, OnDestroy } from '@angular/core';
2
  import { Router } from '@angular/router';
3
  import { CaseStoreService, PoliceCase } from '../shared/case-store.service';
4
+ import { INFOPAGE_SECTIONS } from '../shared/infopage-sections';
5
  import { Subscription } from 'rxjs';
6
 
7
  @Component({
 
19
 
20
  // Pagination
21
  currentPage: number = 1;
22
+ pageSize: number = 5;
23
+ pageSizeOptions: number[] = [5, 10, 20, 50];
24
+
25
  get totalPages(): number {
26
  return Math.ceil(this.filteredCases.length / this.pageSize) || 1;
27
  }
28
+
29
  get pagedCases(): PoliceCase[] {
30
  const start = (this.currentPage - 1) * this.pageSize;
31
  return this.filteredCases.slice(start, start + this.pageSize);
32
  }
 
 
 
 
 
 
33
 
34
+ // Modal state
35
  showDetails = false;
36
  selectedCase: PoliceCase | null = null;
37
  selectedIndex = -1;
38
 
39
+ // Search query
40
  q = '';
41
 
42
+ // Sorting
43
+ sortKey: 'caseId' | 'crime' | 'dateTime' | 'status' | 'Investigation Officer' = 'dateTime';
44
  sortDir: 'asc' | 'desc' = 'desc';
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  // Filter state
47
  filterCrimeType: string = '';
48
  filterStatus: string = '';
 
56
 
57
  filteredCases: PoliceCase[] = [];
58
 
59
+ // Selection state
60
  allSelected: boolean = false;
61
 
62
+ // For modal subgroup pills
63
+ selectedSubgroup: any = {
64
+ crime: 'Identification & Timing',
65
+ suspect: 'Identity',
66
+ notes: 'Investigation Notes'
67
+ };
68
+
69
+ // Reference infopage section/subgroup/fields structure
70
+ sections = INFOPAGE_SECTIONS;
71
+
72
+ constructor(private caseStore: CaseStoreService, private router: Router) { }
73
+
74
  ngOnInit(): void {
 
75
  if (typeof this.caseStore.getCases$ === 'function') {
76
  this.casesSub = this.caseStore.getCases$().subscribe((cases: PoliceCase[]) => {
77
  this.cases = cases || [];
 
81
  this.applySort();
82
  });
83
  } else {
 
84
  this.load();
85
  this.populateFilterOptions();
86
  this.applyFilters();
 
92
  if (this.casesSub) this.casesSub.unsubscribe();
93
  }
94
 
95
+ // Helper methods
96
+ getSubgroups(sectionKey: string): string[] {
97
+ return Object.keys(this.sections[sectionKey]?.subgroups || {});
 
 
98
  }
99
 
100
+ getFieldsForSubgroup(sectionKey: string, subgroup: string): string[] {
101
+ return this.sections[sectionKey]?.subgroups[subgroup] || [];
102
+ }
103
+
104
+ selectSubgroup(sectionKey: string, subgroup: string) {
105
+ this.selectedSubgroup[sectionKey] = subgroup;
106
+ }
107
+
108
+ // Case statistics
109
+ get totalCases(): number {
110
+ return this.cases.length;
111
+ }
112
+
113
+ get openCases(): number {
114
+ return this.cases.filter(c => c.status === 'Open').length;
115
+ }
116
+
117
+ get closedCases(): number {
118
+ return this.cases.filter(c => c.status === 'Closed').length;
119
+ }
120
+
121
+ // Table rows getter
122
+ get rows(): PoliceCase[] {
123
+ return this.pagedCases;
124
+ }
125
+
126
+ // Pagination results
127
+ get resultsStart(): number {
128
+ return this.filteredCases.length === 0 ? 0 : (this.currentPage - 1) * this.pageSize + 1;
129
+ }
130
+
131
+ get resultsEnd(): number {
132
+ return Math.min(this.currentPage * this.pageSize, this.filteredCases.length);
133
+ }
134
+
135
+ get resultsTotal(): number {
136
+ return this.filteredCases.length;
137
+ }
138
+
139
+ // Navigation methods
140
+ navigateHome(): void {
141
+ this.router.navigate(['/']);
142
+ }
143
+
144
+ navigateBackToInfoPage(): void {
145
+ this.router.navigate(['/infopage']);
146
+ }
147
+
148
+ logout(): void {
149
+ // Implement proper logout logic
150
+ localStorage.clear();
151
+ sessionStorage.clear();
152
+ this.router.navigate(['/login']);
153
+ }
154
+
155
+ // Data loading and filtering
156
+ load(): void {
157
+ this.cases = this.caseStore.getPoliceCases();
158
+ this.cases.forEach((c: any) => { if (c.selected === undefined) c.selected = false; });
159
+ this.populateFilterOptions();
160
+ this.applyFilters();
161
  }
162
 
163
  populateFilterOptions() {
 
168
  }
169
 
170
  applyFilters() {
171
+ // Filter by dropdowns
172
  let filtered = this.cases.filter(c =>
173
  (!this.filterCrimeType || c.crime === this.filterCrimeType) &&
174
  (!this.filterStatus || c.status === this.filterStatus) &&
175
  (!this.filterLocation || c.police?.address === this.filterLocation) &&
176
  (!this.filterOfficer || c.police?.name === this.filterOfficer)
177
  );
178
+
179
+ // Filter by search query
180
  const s = (this.q || '').toLowerCase();
181
  if (s) {
182
  filtered = filtered.filter(c =>
 
187
  (c.police?.name || '').toLowerCase().includes(s)
188
  );
189
  }
190
+
191
  this.filteredCases = filtered;
192
+ this.currentPage = 1;
193
  this.applySort();
194
  }
195
 
 
198
  this.filterStatus = '';
199
  this.filterLocation = '';
200
  this.filterOfficer = '';
201
+ this.q = '';
202
  this.applyFilters();
203
  }
204
 
205
+ // Sorting methods
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  setSort(key: typeof this.sortKey) {
207
  if (this.sortKey === key) {
208
  this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc';
 
212
  }
213
  this.applySort();
214
  }
215
+
216
  isAsc(key: typeof this.sortKey) {
217
  return this.sortKey === key && this.sortDir === 'asc';
218
  }
219
+
220
  isDesc(key: typeof this.sortKey) {
221
  return this.sortKey === key && this.sortDir === 'desc';
222
  }
223
+
224
  ariaSort(key: typeof this.sortKey) {
225
  return this.sortKey === key ? (this.sortDir === 'asc' ? 'ascending' : 'descending') : 'none';
226
  }
 
228
  applySort() {
229
  const key = this.sortKey;
230
  const dir = this.sortDir;
231
+
232
  this.filteredCases.sort((a, b) => {
233
  let aVal: any, bVal: any;
234
+
235
  switch (key) {
236
  case 'caseId':
237
  aVal = a.caseId || '';
 
242
  bVal = b.crime || '';
243
  break;
244
  case 'dateTime':
245
+ aVal = a.dateTime ? new Date(a.dateTime).getTime() : 0;
246
+ bVal = b.dateTime ? new Date(b.dateTime).getTime() : 0;
 
 
 
 
247
  break;
248
  case 'status':
249
  aVal = a.status || '';
 
257
  aVal = '';
258
  bVal = '';
259
  }
260
+
261
+ if (aVal < bVal) return dir === 'asc' ? -1 : 1;
262
+ if (aVal > bVal) return dir === 'asc' ? 1 : -1;
263
  return 0;
264
  });
265
  }
266
 
267
+ // Pagination methods
268
+ setPage(page: number) {
269
+ if (page < 1 || page > this.totalPages) return;
270
+ this.currentPage = page;
271
+ }
272
+
273
+ nextPage() {
274
+ this.setPage(this.currentPage + 1);
275
+ }
276
+
277
+ prevPage() {
278
+ this.setPage(this.currentPage - 1);
279
+ }
280
+
281
+ getPagination(): (number | string)[] {
282
+ const pages: (number | string)[] = [];
283
+ const total = this.totalPages;
284
+
285
+ if (total <= 7) {
286
+ for (let i = 1; i <= total; i++) pages.push(i);
287
+ } else {
288
+ if (this.currentPage <= 4) {
289
+ for (let i = 1; i <= 5; i++) pages.push(i);
290
+ pages.push('...');
291
+ pages.push(total);
292
+ } else if (this.currentPage >= total - 3) {
293
+ pages.push(1);
294
+ pages.push('...');
295
+ for (let i = total - 4; i <= total; i++) pages.push(i);
296
+ } else {
297
+ pages.push(1);
298
+ pages.push('...');
299
+ for (let i = this.currentPage - 1; i <= this.currentPage + 1; i++) pages.push(i);
300
+ pages.push('...');
301
+ pages.push(total);
302
+ }
303
+ }
304
+
305
+ return pages;
306
  }
307
 
308
+ goToPage(page: string | number) {
309
+ if (typeof page === 'number') {
310
+ this.setPage(page);
311
+ }
312
+ }
313
+
314
+ onPageSizeChange(size: number) {
315
+ this.pageSize = size;
316
+ this.currentPage = 1;
317
+ }
318
+
319
+ // Modal methods
320
  openDetails(c: PoliceCase, i: number): void {
321
  this.selectedCase = c;
322
  this.selectedIndex = i;
323
  this.showDetails = true;
324
+ document.body.style.overflow = 'hidden';
325
  }
326
 
327
  closeDetails(): void {
328
  this.showDetails = false;
329
  this.selectedCase = null;
330
  this.selectedIndex = -1;
331
+ document.body.style.overflow = 'auto';
332
  }
333
 
334
+ // Case actions
335
  editCase(c: PoliceCase, i: number): void {
 
336
  const prefill: Record<string, any> = {};
337
+
338
  try {
 
339
  const fd = (c as any).formData;
340
  if (fd && Array.isArray(fd)) {
341
  (fd as Array<any>).forEach(kv => { if (kv && kv.key) prefill[kv.key] = kv.value; });
342
  } else if (fd && typeof fd === 'object') {
343
  Object.assign(prefill, fd as Record<string, any>);
344
  }
345
+
346
+ // Include mapped top-level properties
347
  if (c.caseId) prefill['Case ID'] = c.caseId;
348
  if (c.crime) prefill['Crime Type'] = c.crime;
349
  if (c.dateTime) prefill['Date & Time (Entry)'] = c.dateTime;
350
+ if (c.police?.address) prefill['Location'] = c.police.address;
351
+ if (c.police?.name) prefill['Investigating Officer'] = c.police.name;
352
+ if (c.accused?.name) prefill['Suspect Name'] = c.accused.name;
353
  } catch (e) {
354
+ console.error('Error preparing edit data:', e);
355
  }
356
 
357
+ this.router.navigate(['/infopage', c.caseId], {
358
+ state: {
359
+ from: 'record',
360
+ returnId: c.caseId,
361
+ prefillFormData: prefill,
362
+ case: c
363
+ }
364
+ });
365
  }
366
 
367
+ deleteCase(index: number): void {
368
+ if (confirm('Are you sure you want to delete this case?')) {
369
+ this.caseStore.deletePoliceCaseAt(index);
370
+ this.load();
371
+ }
 
 
 
372
  }
373
 
374
  navigateToCaseDetails(c: PoliceCase): void {
375
  if (!c || !c.caseId) return;
 
 
 
 
 
376
 
377
+ const origin = this.router.url.includes('/case-details') ? 'case-details' : 'record';
378
+
379
+ this.router.navigate(['/case-details-summary-page', c.caseId], {
380
+ queryParams: {
381
+ from: origin,
382
+ returnId: c.caseId
383
+ },
384
+ state: {
385
+ case: c,
386
+ from: origin,
387
+ returnId: c.caseId
388
+ }
389
+ });
390
  }
391
 
392
+ // Field value getters for modal
393
+ objectKeys = Object.keys;
 
 
 
394
 
395
+ getFieldValue(sc: any, sectionKey: string, field: string): any {
396
+ const fieldMap: { [key: string]: string | string[] } = {
397
+ // Crime Details
398
+ 'Case ID': 'caseId',
399
+ 'FIR / Ref #': 'firRef',
400
+ 'Crime Type': 'crime',
401
+ 'Case Category': 'caseCategory',
402
+ 'Date & Time (Entry)': 'dateTime',
403
+ 'Occurred From': 'occurredFrom',
404
+ 'Occurred To': 'occurredTo',
405
+ 'Time Reported': 'timeReported',
406
+ 'Time Discovered': 'timeDiscovered',
407
+ 'Country': 'country',
408
+ 'State': 'state',
409
+ 'District': 'district',
410
+ 'Number of Victims': 'numberOfVictims',
411
+ 'Brief Description': 'briefDescription',
412
+ 'Location': ['police', 'address'],
413
+ 'Jurisdiction / PS': 'jurisdiction',
414
+ 'Scene Type': 'sceneType',
415
+ 'Reported By': 'reportedBy',
416
+ 'Reported Contact': 'reportedContact',
417
+ 'Witness Count': 'witnessCount',
418
+ 'Victim Name': 'victimName',
419
+ 'Victim Contact': 'victimContact',
420
+ 'Victim Summary': 'victimSummary',
421
+ 'Suspected Offender Known?': 'suspectedOffenderKnown',
422
+ 'Suspect Link': 'suspectLink',
423
+ 'Legal Sections / Charges': 'legalSections',
424
+ 'Offence Category': 'offenceCategory',
425
+ 'Offence Description': 'offenceDescription',
426
+ 'Suspected Motive': 'suspectedMotive',
427
+ 'Confirmed Motive': 'confirmedMotive',
428
+ 'Weapon Involved': 'weaponInvolved',
429
+ 'Property Loss / Damage': 'propertyLoss',
430
+ 'Evidence Collected': 'evidenceCollected',
431
+ 'Forensic Tests Required': 'forensicTestsRequired',
432
+ 'Scene Condition': 'sceneCondition',
433
+ 'Photos / Video?': 'photosVideo',
434
+ 'CCTV Present?': 'cctvPresent',
435
+ 'CCTV Sources / IDs': 'cctvSources',
436
+ 'Physical Evidence (list)': 'physicalEvidence',
437
+ 'Chain of Custody?': 'chainOfCustody',
438
+ 'Digital Evidence': 'digitalEvidence',
439
+ 'Evidence Storage Reference': 'evidenceStorageReference',
440
+ 'Investigating Officer': ['police', 'name'],
441
+ 'Duty Person': ['police', 'dutyPerson'],
442
+ 'Supervising Officer': ['police', 'supervisingOfficer'],
443
+ 'Patrol Notes': ['police', 'patrolNotes'],
444
+ 'Arrest Made': 'arrestMade',
445
+ 'Arrest Location': 'arrestLocation',
446
+ 'Initial Actions Taken': 'initialActionsTaken',
447
+ 'riskLevel': 'riskLevel',
448
+ 'Confidentiality': 'confidentiality',
449
+ 'Biometric / Forensic IDs': 'biometricIds',
450
+ 'DNA Ref ID': 'dnaRefId',
451
+ 'Fingerprint ID': 'fingerprintId',
452
+ 'Case Status': 'status',
453
+ 'Linked Cases': 'linkedCases',
454
+ 'arrestCount': 'arrestCount',
455
+ 'Case Priority': 'casePriority',
456
+ 'Follow-up Date': 'followUpDate',
457
+ 'Court Case ID': 'courtCaseId',
458
+ 'Next Hearing Date': 'nextHearingDate',
459
+ 'Final Summary': 'finalSummary',
460
+ 'Remark': 'remark',
461
+ // Suspect Details
462
+ 'Suspect ID': ['accused', 'suspectId'],
463
+ 'Suspect Name': ['accused', 'name'],
464
+ 'Alias / Nickname': ['accused', 'alias'],
465
+ 'Age': ['accused', 'age'],
466
+ 'Gender': ['accused', 'gender'],
467
+ 'Nationality': ['accused', 'nationality'],
468
+ 'Nationality ID / Passport Number': ['accused', 'passportNumber'],
469
+ 'Languages': ['accused', 'languages'],
470
+ 'Address': ['accused', 'address'],
471
+ 'Known Aliases': ['accused', 'knownAliases'],
472
+ 'Government ID': ['accused', 'governmentId'],
473
+ 'Height (cm)': ['accused', 'height'],
474
+ 'Weight (kg)': ['accused', 'weight'],
475
+ 'Build': ['accused', 'build'],
476
+ 'Hair Color': ['accused', 'hairColor'],
477
+ 'Eye Color': ['accused', 'eyeColor'],
478
+ 'Distinguishing Marks': ['accused', 'distinguishingMarks'],
479
+ 'Tattoo Details': ['accused', 'tattooDetails'],
480
+ 'Scar Details': ['accused', 'scarDetails'],
481
+ 'Photo Upload': ['accused', 'photoUpload'],
482
+ 'Employment': ['accused', 'employment'],
483
+ 'Education': ['accused', 'education'],
484
+ 'Occupation': ['accused', 'occupation'],
485
+ 'Company': ['accused', 'company'],
486
+ 'Workplace Address': ['accused', 'workplaceAddress'],
487
+ 'Marital Status': ['accused', 'maritalStatus'],
488
+ 'Known Habits': ['accused', 'knownHabits'],
489
+ 'Known Financial Details': ['accused', 'knownFinancialDetails'],
490
+ 'Associate Names': ['accused', 'associateNames'],
491
+ 'Gang Affiliation': ['accused', 'gangAffiliation'],
492
+ 'Family Connections': ['accused', 'familyConnections'],
493
+ 'Social Media Handles': ['accused', 'socialMediaHandles'],
494
+ 'Criminal History': ['accused', 'criminalHistory'],
495
+ 'Prior Arrests': ['accused', 'priorArrests'],
496
+ 'Probation/Parole Status': ['accused', 'probationStatus'],
497
+ // Notes/Evidence
498
+ 'Initial Findings': ['police', 'information'],
499
+ 'Detailed Notes': ['notes', 'detailedNotes'],
500
+ 'Status': 'status',
501
+ 'Version History / Updates': ['notes', 'versionHistory'],
502
+ 'Evidence Photos': ['legal', 'evidencePhotos'],
503
+ 'Evidence Videos': ['legal', 'evidenceVideos'],
504
+ 'Evidence Documents': ['legal', 'evidenceDocuments'],
505
+ 'Links to Evidence': ['legal', 'linksToEvidence'],
506
+ 'Final Recommendations': ['legal', 'finalRecommendations'],
507
+ 'Witness Statements': ['legal', 'witnessStatements'],
508
+ 'Confessions': ['legal', 'confessions'],
509
+ // Audit Fields
510
+ 'Created By': 'createdBy',
511
+ 'Creation Date': 'creationDate',
512
+ 'Last Updated': 'lastUpdated',
513
+ 'Verified By': 'verifiedBy'
514
+ };
515
 
516
+ const path = fieldMap[field] || field;
517
+ let value: any = undefined;
 
 
518
 
519
+ if (Array.isArray(path)) {
520
+ let v = sc;
521
+ for (const p of path) {
522
+ if (v && v[p] !== undefined) v = v[p];
523
+ else { v = undefined; break; }
524
+ }
525
+ value = v;
526
+ } else {
527
+ value = sc && sc[path] !== undefined ? sc[path] : undefined;
528
  }
 
529
 
530
+ // Try to find in formData
531
+ if (value === null || value === undefined || value === '') {
532
+ try {
533
+ const fd = this.getFormDataArray(sc);
534
+ const norm = (s: any) => {
535
+ if (s === null || s === undefined) return '';
536
+ let t = String(s).toLowerCase();
537
+ t = t.replace(/&/g, '');
538
+ t = t.replace(/and/g, '');
539
+ t = t.replace(/entry/g, '');
540
+ t = t.replace(/\s+/g, '');
541
+ return t.replace(/[^a-z0-9]/g, '');
542
+ };
543
 
544
+ if (fd && fd.length) {
545
+ let kv = fd.find(k => k && String(k.key).toLowerCase() === String(field).toLowerCase());
546
+ if (kv) value = kv.value;
 
 
 
 
 
 
547
 
548
+ if (value === null || value === undefined || value === '') {
549
+ const fieldNorm = norm(field);
550
+ const pathName = Array.isArray(path) ? path[path.length - 1] : String(path);
551
+ const pathNorm = norm(pathName);
552
+ kv = fd.find(k => k && (norm(k.key) === fieldNorm || norm(k.key) === pathNorm));
553
+ if (kv) value = kv.value;
554
+ }
555
+ }
556
+
557
+ if ((value === null || value === undefined || value === '') && sc && sc.formData && typeof sc.formData === 'object') {
558
+ if (sc.formData[field] !== undefined) value = sc.formData[field];
559
+ }
560
+ } catch (e) {
561
+ // ignore
 
 
 
 
 
 
562
  }
563
  }
 
 
564
 
565
+ // Normalize and format
566
+ if (value === null || value === undefined || value === '') return 'Not Assigned';
567
+
568
+ // Date formatting
569
+ if (this.dateTimeFields.has(field) || this.dateFields.has(field)) {
570
+ const d = new Date(value);
571
+ if (!isNaN(d.getTime())) {
572
+ if (this.dateFields.has(field)) return d.toISOString().slice(0, 10);
573
+ return d.toLocaleString();
574
+ }
575
  }
576
+
577
+ if (typeof value === 'object') return this.formatFormValue(value);
578
+ return value;
579
  }
580
 
581
+ getValue(obj: any, key: string): any {
582
+ const v = obj && obj[key] !== undefined ? obj[key] : undefined;
583
+ if (v === null || v === undefined || v === '') return 'Not Assigned';
584
+ if (typeof v === 'object') return this.formatFormValue(v);
585
+ if (this.dateTimeFields.has(key) || this.dateFields.has(key)) {
586
+ const d = new Date(v);
587
+ if (!isNaN(d.getTime())) {
588
+ if (this.dateFields.has(key)) return d.toISOString().slice(0, 10);
589
+ return d.toLocaleString();
590
+ }
591
+ }
592
+ return v;
593
  }
594
+
595
+ // Form data helpers
596
+ isFormDataArray(fd: any): boolean {
597
+ return Array.isArray(fd) && fd.length > 0 && fd.every((item: any) => item && Object.prototype.hasOwnProperty.call(item, 'key'));
598
  }
599
+
600
+ getFormDataArray(caseObj: any): Array<{ key: string; value: any }> {
601
+ if (!caseObj || !caseObj.formData) return [];
602
+ const fd = caseObj.formData;
603
+ if (this.isFormDataArray(fd)) return fd as Array<{ key: string; value: any }>;
604
+ if (typeof fd === 'object') return Object.keys(fd).map(k => ({ key: k, value: fd[k] }));
605
+ return [{ key: 'value', value: fd }];
606
  }
607
+
608
+ formatFormValue(value: any): string {
609
+ if (value === null || value === undefined || value === '') return 'Not Assigned';
610
+ if (typeof value === 'object') {
611
+ try {
612
+ return JSON.stringify(value, null, 2);
613
+ } catch {
614
+ return String(value);
615
+ }
616
+ }
617
+ return String(value);
618
  }
619
 
620
+ // Other methods
621
+ toggleSelectAll(event: Event): void {
622
+ const checked = (event.target as HTMLInputElement).checked;
623
+ this.allSelected = checked;
624
+ this.rows.forEach((c: any) => c.selected = checked);
625
  }
626
 
627
+ onModernSearch() {
628
+ return false;
629
  }
630
 
631
+ goToDetect(caseId: string): void {
632
+ this.router.navigate(['/py-detect'], { state: { caseId } });
 
 
633
  }
634
  }
src/app/shared/case-store.service.ts CHANGED
@@ -162,6 +162,8 @@ export class CaseStoreService {
162
  const mapped: PoliceCase = {
163
  caseId: crime.caseId || (formValue && formValue['Case ID']) || '',
164
  dateTime: crime.dateTime || '' ,
 
 
165
  status: notes.status || 'Open',
166
  crime: crime.crimeType || 'Unknown',
167
  police: {
@@ -199,6 +201,8 @@ export class CaseStoreService {
199
  if (idx !== -1) {
200
  // Merge existing object to preserve other metadata where possible
201
  this.cases[idx] = { ...this.cases[idx], ...mapped };
 
 
202
  this.save();
203
  this.debugLogCasesOperation('updatePoliceCase', mapped);
204
  this.casesSubject.next(this.cases.slice());
 
162
  const mapped: PoliceCase = {
163
  caseId: crime.caseId || (formValue && formValue['Case ID']) || '',
164
  dateTime: crime.dateTime || '' ,
165
+ // mark when infopage submission occurred
166
+ lastUpdated: new Date().toISOString(),
167
  status: notes.status || 'Open',
168
  crime: crime.crimeType || 'Unknown',
169
  police: {
 
201
  if (idx !== -1) {
202
  // Merge existing object to preserve other metadata where possible
203
  this.cases[idx] = { ...this.cases[idx], ...mapped };
204
+ // Ensure lastUpdated reflects this infopage update
205
+ this.cases[idx].lastUpdated = mapped.lastUpdated || new Date().toISOString();
206
  this.save();
207
  this.debugLogCasesOperation('updatePoliceCase', mapped);
208
  this.casesSubject.next(this.cases.slice());
src/app/shared/infopage-sections.ts ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const INFOPAGE_SECTIONS: any = {
2
+ crime: {
3
+ title: 'Crime Details',
4
+ subgroups: {
5
+ 'Identification & Timing': ['Case ID', 'FIR / Ref #', 'Crime Type', 'Case Category', 'Date & Time (Entry)', 'Occurred From', 'Occurred To', 'Time Reported', 'Time Discovered', 'Country', 'State', 'District', 'Number of Victims', 'Brief Description'],
6
+ 'Location & Involved Persons': ['Location', 'Jurisdiction / PS', 'Scene Type', 'Reported By', 'Reporter Phone', 'Reporter Email', 'Witness Count', 'Witness Phone', 'Witness Email', 'Victim Name', 'Victim Phone', 'Victim Email', 'Victim Summary', 'Suspected Offender Known?', 'Suspect Link'],
7
+ 'Offence & Context': ['Legal Sections / Charges', 'Offence Category', 'Offence Description', 'Suspected Motive', 'Confirmed Motive', 'Weapon Involved', 'Property Loss / Damage'],
8
+ 'Evidence & Scene': ['Evidence Collected', 'Physical Evidence List', 'Evidence Storage Reference', 'Evidence Collected By', 'Evidence Bag / Seal Number', 'Scene Secured Time', 'Photos / Video?', 'CCTV Present?', 'CCTV Sources / IDs', 'Forensic Tests Required', 'Chain of Custody?', 'Scene Condition'],
9
+ 'Operational Notes': ['Investigating Officer', 'Duty Person', 'Supervising Officer', 'Patrol Notes', 'Arrest Made', 'Arrest Location', 'Initial Actions Taken', 'risk Level', 'Confidentiality'],
10
+ 'Status & Linkage': ['Biometric / Forensic IDs', 'DNA Ref ID', 'Fingerprint ID', 'Investigative Status', 'Linked Cases', 'Arrest Count', 'Case Priority', 'Follow-up Date', 'Court Case ID', 'Next Hearing Date', 'Final Summary'],
11
+ 'Remark': ['Remark']
12
+ }
13
+ },
14
+ suspect: {
15
+ title: 'Suspect Details',
16
+ subgroups: {
17
+ 'Identity': ['Suspect ID', 'Suspect Name', 'Suspect Email', 'Suspect Phone', 'Alias / Nickname', 'Age', 'Gender', 'Nationality', 'Nationality ID / Passport Number', 'Languages', 'Address', 'Known Aliases', 'Government ID'],
18
+ 'Physical Description': ['Height (cm)', 'Weight (kg)', 'Build', 'Skin Tone', 'Hair Color', 'Eye Color', 'Facial Hair', 'Handedness', 'Tattoo Details', 'Scar Details', 'Distinguishing Marks', 'Voice Characteristics', 'Photo Upload'],
19
+ 'Background': ['Employment', 'Education', 'Occupation', 'Company', 'Workplace Address', 'Marital Status', 'Known Habits', 'Known Financial Details'],
20
+ 'Known Associates': ['Associate Names', 'Gang Affiliation', 'Family Connections', 'Social Media Handles'],
21
+ 'Prior Records': ['Criminal History', 'Prior Arrests', 'Probation/Parole Status'],
22
+ 'Remark': ['Remark']
23
+ }
24
+ },
25
+ notes: {
26
+ title: 'Evidence and Documents',
27
+ subgroups: {
28
+ 'Investigation Notes': ['Initial Findings', 'Detailed Notes', 'Status', 'Version History / Updates'],
29
+ 'Evidence Files': ['Evidence Photos', 'Evidence Videos', 'Evidence Documents', 'Digital Evidence'],
30
+ 'Links and Recommendation': ['Links to Evidence', 'Final Recommendations'],
31
+ 'Remark': ['Remark']
32
+ }
33
+ }
34
+ };
src/assets/1-old.JPG DELETED

Git LFS Details

  • SHA256: 9f1b4dfea042dab76916683485000d63160b27d683654731fd72e72788170b5d
  • Pointer size: 131 Bytes
  • Size of remote file: 123 kB
src/assets/9.JPG DELETED

Git LFS Details

  • SHA256: b38734f3ad661443854decea4a3af3344c6132182b6eef6b785708608b9a1157
  • Pointer size: 131 Bytes
  • Size of remote file: 176 kB
src/assets/background-2.jpg DELETED

Git LFS Details

  • SHA256: edb3629b8b1cc4f1d22cdc586b1c704e82686ec5b8772d23448245038aaab6be
  • Pointer size: 132 Bytes
  • Size of remote file: 4.07 MB
src/assets/background-21.jpg DELETED

Git LFS Details

  • SHA256: edb3629b8b1cc4f1d22cdc586b1c704e82686ec5b8772d23448245038aaab6be
  • Pointer size: 132 Bytes
  • Size of remote file: 4.07 MB
src/assets/background.jpg DELETED

Git LFS Details

  • SHA256: edb3629b8b1cc4f1d22cdc586b1c704e82686ec5b8772d23448245038aaab6be
  • Pointer size: 132 Bytes
  • Size of remote file: 4.07 MB
src/assets/background1.jpg DELETED

Git LFS Details

  • SHA256: edb3629b8b1cc4f1d22cdc586b1c704e82686ec5b8772d23448245038aaab6be
  • Pointer size: 132 Bytes
  • Size of remote file: 4.07 MB
src/assets/background1234.jpg DELETED

Git LFS Details

  • SHA256: 92f913300dfd5ef30b16121cf273e13d43f96cbdb442d79ac450281e643456b9
  • Pointer size: 131 Bytes
  • Size of remote file: 587 kB
src/{app/homepage/sign-up-1.zip → assets/coverimage.png} RENAMED
File without changes
src/assets/hero-jpg.jpg DELETED

Git LFS Details

  • SHA256: 3a8f6e92b08b67f73540db6b998140eb666b2734668adb05612479748b46c79c
  • Pointer size: 131 Bytes
  • Size of remote file: 121 kB
src/assets/{hero.png → hero1.png} RENAMED
File without changes
src/assets/{home.png → home1.png} RENAMED
File without changes
src/assets/side-2scale-right.jpg DELETED

Git LFS Details

  • SHA256: 5348ab8d6addd57a8647fdb78ba2abac73d55c975394dd19e2c910051d161f3d
  • Pointer size: 130 Bytes
  • Size of remote file: 30 kB
src/assets/signin.png DELETED

Git LFS Details

  • SHA256: 79201537ae5b1ca1f2cb1902cca71fe8040ed3383ee1d19e008fd0ac320fb627
  • Pointer size: 131 Bytes
  • Size of remote file: 337 kB
src/assets/signup.png DELETED

Git LFS Details

  • SHA256: 79201537ae5b1ca1f2cb1902cca71fe8040ed3383ee1d19e008fd0ac320fb627
  • Pointer size: 131 Bytes
  • Size of remote file: 337 kB
src/styles.css CHANGED
@@ -21,7 +21,7 @@ body.light-theme {
21
 
22
  /* Homepage specific background */
23
  body.homepage-bg {
24
- background: url('/assets/hero.png') no-repeat center center fixed;
25
  background-size: cover;
26
  }
27
 
@@ -92,7 +92,7 @@ html {
92
  /* Firefox Scrollbar */
93
  html {
94
  scrollbar-width: thin;
95
- scrollbar-color: #00d4ff rgba(0, 0, 0, 0.1);
96
  }
97
 
98
  /* Material Design Compatibility */
 
21
 
22
  /* Homepage specific background */
23
  body.homepage-bg {
24
+ background: url('/assets/coverimage.png') no-repeat center center fixed;
25
  background-size: cover;
26
  }
27
 
 
92
  /* Firefox Scrollbar */
93
  html {
94
  scrollbar-width: thin;
95
+ scrollbar-color: #1e3a8a rgba(0, 0, 0, 0.1);
96
  }
97
 
98
  /* Material Design Compatibility */