Oviya commited on
Commit
0dcd2ca
·
1 Parent(s): 9b2360d

record page updated

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. obj/Debug/Py-Detect.esproj.FileListAbsolute.txt +1 -0
  2. obj/Debug/package.g.props +1 -0
  3. src/app/app-routing.module.ts +9 -4
  4. src/app/app.module.ts +6 -4
  5. src/app/auth.service.spec.ts +16 -0
  6. src/app/auth.service.ts +50 -0
  7. src/app/case-details-page/case-details-page.component.css +600 -60
  8. src/app/case-details-page/case-details-page.component.html +223 -96
  9. src/app/case-details-page/case-details-page.component.ts +342 -0
  10. src/app/case-store.service.ts +59 -3
  11. src/app/homepage/homepage.component.css +308 -450
  12. src/app/homepage/homepage.component.html +208 -65
  13. src/app/homepage/sign-in/sign-in.component.css +180 -197
  14. src/app/homepage/sign-in/sign-in.component.html +37 -38
  15. src/app/homepage/sign-in/sign-in.component.ts +22 -2
  16. src/app/homepage/sign-in/sign-in.service.ts +1 -6
  17. src/app/homepage/sign-up/sign-up.component.css +257 -69
  18. src/app/homepage/sign-up/sign-up.component.html +68 -80
  19. src/app/homepage/sign-up/sign-up.component.ts +21 -9
  20. src/app/homepage/sign-up/sign-up.service.ts +2 -6
  21. src/app/infopage/infopage.component.css +2045 -254
  22. src/app/infopage/infopage.component.html +269 -218
  23. src/app/infopage/infopage.component.ts +1120 -198
  24. src/app/recordpage/recordpage.component.css +873 -80
  25. src/app/recordpage/recordpage.component.html +166 -104
  26. src/app/recordpage/recordpage.component.ts +238 -8
  27. src/app/shared/case-store.service.ts +3 -0
  28. src/app/shared/truncate.pipe.ts +0 -0
  29. src/assets/1-old.JPG +3 -0
  30. src/assets/2.JPG +3 -0
  31. src/assets/3.JPG +3 -0
  32. src/assets/4.JPG +3 -0
  33. src/assets/5.JPG +3 -0
  34. src/assets/6.JPG +3 -0
  35. src/assets/7.JPG +3 -0
  36. src/assets/8.JPG +3 -0
  37. src/assets/9.JPG +3 -0
  38. src/assets/background-2.jpg +3 -0
  39. src/assets/background.jpg +2 -2
  40. src/assets/background1234.jpg +3 -0
  41. src/assets/font/EBGaramond-Medium.ttf +3 -0
  42. src/assets/font/EBGaramond-Regular.ttf +3 -0
  43. src/assets/font/Roliana-Regular.otf +3 -0
  44. src/assets/hero-jpg.jpg +3 -0
  45. src/assets/hero-old.png +3 -0
  46. src/assets/hero.png +3 -0
  47. src/assets/home.png +3 -0
  48. src/favicon.ico +0 -0
  49. src/index.html +4 -2
  50. src/pykara-favicon.ico +0 -0
obj/Debug/Py-Detect.esproj.FileListAbsolute.txt CHANGED
@@ -1 +1,2 @@
1
  C:\Users\Admin\Desktop\Py-Detect\obj\Debug\Py-Detect.esproj.CoreCompileInputs.cache
 
 
1
  C:\Users\Admin\Desktop\Py-Detect\obj\Debug\Py-Detect.esproj.CoreCompileInputs.cache
2
+ C:\Users\Admin\Desktop\deployment-pydetect\Py-detect\obj\Debug\Py-Detect.esproj.CoreCompileInputs.cache
obj/Debug/package.g.props CHANGED
@@ -17,6 +17,7 @@
17
  <PackageJsonDependenciesAngularPlatformBrowser Condition="$(PackageJsonDependenciesAngularPlatformBrowser) == ''">^16.1.0</PackageJsonDependenciesAngularPlatformBrowser>
18
  <PackageJsonDependenciesAngularPlatformBrowserDynamic Condition="$(PackageJsonDependenciesAngularPlatformBrowserDynamic) == ''">^16.1.0</PackageJsonDependenciesAngularPlatformBrowserDynamic>
19
  <PackageJsonDependenciesAngularRouter Condition="$(PackageJsonDependenciesAngularRouter) == ''">^16.1.0</PackageJsonDependenciesAngularRouter>
 
20
  <PackageJsonDependenciesJestEditorSupport Condition="$(PackageJsonDependenciesJestEditorSupport) == ''">*</PackageJsonDependenciesJestEditorSupport>
21
  <PackageJsonDependenciesRxjs Condition="$(PackageJsonDependenciesRxjs) == ''">~7.8.0</PackageJsonDependenciesRxjs>
22
  <PackageJsonDependenciesTslib Condition="$(PackageJsonDependenciesTslib) == ''">^2.3.0</PackageJsonDependenciesTslib>
 
17
  <PackageJsonDependenciesAngularPlatformBrowser Condition="$(PackageJsonDependenciesAngularPlatformBrowser) == ''">^16.1.0</PackageJsonDependenciesAngularPlatformBrowser>
18
  <PackageJsonDependenciesAngularPlatformBrowserDynamic Condition="$(PackageJsonDependenciesAngularPlatformBrowserDynamic) == ''">^16.1.0</PackageJsonDependenciesAngularPlatformBrowserDynamic>
19
  <PackageJsonDependenciesAngularRouter Condition="$(PackageJsonDependenciesAngularRouter) == ''">^16.1.0</PackageJsonDependenciesAngularRouter>
20
+ <PackageJsonDependenciesForce Condition="$(PackageJsonDependenciesForce) == ''">^0.0.3</PackageJsonDependenciesForce>
21
  <PackageJsonDependenciesJestEditorSupport Condition="$(PackageJsonDependenciesJestEditorSupport) == ''">*</PackageJsonDependenciesJestEditorSupport>
22
  <PackageJsonDependenciesRxjs Condition="$(PackageJsonDependenciesRxjs) == ''">~7.8.0</PackageJsonDependenciesRxjs>
23
  <PackageJsonDependenciesTslib Condition="$(PackageJsonDependenciesTslib) == ''">^2.3.0</PackageJsonDependenciesTslib>
src/app/app-routing.module.ts CHANGED
@@ -1,21 +1,26 @@
1
  import { NgModule } from '@angular/core';
2
  import { RouterModule, Routes } from '@angular/router';
3
- import { HomepageComponent } from './homepage/homepage.component';
4
- import { PyDetectComponent } from './py-detect/py-detect.component';
5
  import { InfopageComponent } from './infopage/infopage.component';
6
  import { ValidationpageComponent } from './validationpage/validationpage.component';
7
  import { RecordpageComponent } from './recordpage/recordpage.component';
8
  import { CaseDetailsPageComponent } from './case-details-page/case-details-page.component';
9
 
10
  const routes: Routes = [
11
- { path: '', component: HomepageComponent },
 
 
 
12
  { path: 'infopage', component: InfopageComponent },
13
  { path: 'infopage/:id', component: InfopageComponent },
14
- { path: 'py-detect', component: PyDetectComponent },
 
 
 
15
  { path: 'validationpage', component: ValidationpageComponent },
16
  { path: 'record', component: RecordpageComponent },
17
  { path: 'case-details', component: CaseDetailsPageComponent },
18
  { path: 'case-details/:id', component: CaseDetailsPageComponent },
 
19
 
20
  {
21
  path: 'auth/signin', loadComponent: () =>
 
1
  import { NgModule } from '@angular/core';
2
  import { RouterModule, Routes } from '@angular/router';
 
 
3
  import { InfopageComponent } from './infopage/infopage.component';
4
  import { ValidationpageComponent } from './validationpage/validationpage.component';
5
  import { RecordpageComponent } from './recordpage/recordpage.component';
6
  import { CaseDetailsPageComponent } from './case-details-page/case-details-page.component';
7
 
8
  const routes: Routes = [
9
+ {
10
+ path: '',
11
+ loadComponent: () => import('./homepage/homepage.component').then(m => m.HomepageComponent)
12
+ },
13
  { path: 'infopage', component: InfopageComponent },
14
  { path: 'infopage/:id', component: InfopageComponent },
15
+ {
16
+ path: 'py-detect',
17
+ loadComponent: () => import('./py-detect/py-detect.component').then(m => m.PyDetectComponent)
18
+ },
19
  { path: 'validationpage', component: ValidationpageComponent },
20
  { path: 'record', component: RecordpageComponent },
21
  { path: 'case-details', component: CaseDetailsPageComponent },
22
  { path: 'case-details/:id', component: CaseDetailsPageComponent },
23
+ { path: '', redirectTo: '/case-detail', pathMatch: 'full' },
24
 
25
  {
26
  path: 'auth/signin', loadComponent: () =>
src/app/app.module.ts CHANGED
@@ -1,8 +1,10 @@
1
  import { NgModule } from '@angular/core';
2
  import { BrowserModule } from '@angular/platform-browser';
 
3
  import { ReactiveFormsModule, FormsModule } from '@angular/forms';
4
  import { CommonModule } from '@angular/common';
5
  import { HttpClientModule } from '@angular/common/http';
 
6
  import { AppComponent } from './app.component';
7
  import { InfopageComponent } from './infopage/infopage.component';
8
  import { AppRoutingModule } from './app-routing.module';
@@ -10,8 +12,8 @@ import { ValidationpageComponent } from './validationpage/validationpage.compone
10
  import { SignInComponent } from './homepage/sign-in/sign-in.component';
11
  import { SignUpComponent } from './homepage/sign-up/sign-up.component';
12
  import { RecordpageComponent } from './recordpage/recordpage.component';
13
- import { HomepageComponent } from './homepage/homepage.component';
14
  import { CaseDetailsPageComponent } from './case-details-page/case-details-page.component';
 
15
 
16
  @NgModule({
17
  declarations: [
@@ -23,14 +25,14 @@ import { CaseDetailsPageComponent } from './case-details-page/case-details-page.
23
  ],
24
  imports: [
25
  BrowserModule,
 
26
  ReactiveFormsModule,
27
  FormsModule,
28
  CommonModule,
29
  AppRoutingModule,
30
  HttpClientModule,
31
- HomepageComponent,
32
- SignInComponent,
33
- SignUpComponent
34
  ],
35
  providers: [],
36
  bootstrap: [AppComponent]
 
1
  import { NgModule } from '@angular/core';
2
  import { BrowserModule } from '@angular/platform-browser';
3
+ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
4
  import { ReactiveFormsModule, FormsModule } from '@angular/forms';
5
  import { CommonModule } from '@angular/common';
6
  import { HttpClientModule } from '@angular/common/http';
7
+ import { RouterModule } from '@angular/router';
8
  import { AppComponent } from './app.component';
9
  import { InfopageComponent } from './infopage/infopage.component';
10
  import { AppRoutingModule } from './app-routing.module';
 
12
  import { SignInComponent } from './homepage/sign-in/sign-in.component';
13
  import { SignUpComponent } from './homepage/sign-up/sign-up.component';
14
  import { RecordpageComponent } from './recordpage/recordpage.component';
 
15
  import { CaseDetailsPageComponent } from './case-details-page/case-details-page.component';
16
+ import { MatCardModule } from '@angular/material/card';
17
 
18
  @NgModule({
19
  declarations: [
 
25
  ],
26
  imports: [
27
  BrowserModule,
28
+ BrowserAnimationsModule,
29
  ReactiveFormsModule,
30
  FormsModule,
31
  CommonModule,
32
  AppRoutingModule,
33
  HttpClientModule,
34
+ RouterModule,
35
+ MatCardModule
 
36
  ],
37
  providers: [],
38
  bootstrap: [AppComponent]
src/app/auth.service.spec.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { TestBed } from '@angular/core/testing';
2
+
3
+ import { AuthService } from './auth.service';
4
+
5
+ describe('AuthService', () => {
6
+ let service: AuthService;
7
+
8
+ beforeEach(() => {
9
+ TestBed.configureTestingModule({});
10
+ service = TestBed.inject(AuthService);
11
+ });
12
+
13
+ it('should be created', () => {
14
+ expect(service).toBeTruthy();
15
+ });
16
+ });
src/app/auth.service.ts ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // src/app/auth.service.ts
2
+ import { Injectable } from '@angular/core';
3
+
4
+ @Injectable({
5
+ providedIn: 'root' // This makes the service available globally in the app
6
+ })
7
+ export class AuthService {
8
+
9
+ constructor() { }
10
+
11
+ // Sign-up method to handle user registration
12
+ signUp(name: string, role: string, email: string, password: string): Promise<any> {
13
+ return new Promise((resolve) => {
14
+ // Simulating storing user data in localStorage
15
+ const user = { name, role, email, password };
16
+ localStorage.setItem('user', JSON.stringify(user)); // Save user data in localStorage
17
+ localStorage.setItem('userRole', role); // Save only the role in localStorage (for later use)
18
+ resolve('User signed up');
19
+ });
20
+ }
21
+
22
+ // Sign-in method to handle user login
23
+ signIn(email: string, password: string): Promise<any> {
24
+ return new Promise((resolve, reject) => {
25
+ const user = JSON.parse(localStorage.getItem('user') || '{}');
26
+ // Check if email and password match the stored data
27
+ if (user.email === email && user.password === password) {
28
+ resolve('User signed in');
29
+ } else {
30
+ reject('Invalid credentials');
31
+ }
32
+ });
33
+ }
34
+
35
+ // Helper methods to manage user session and check if the user is logged in
36
+ isLoggedIn(): boolean {
37
+ return !!localStorage.getItem('user');
38
+ }
39
+
40
+ // Get the user's role from localStorage
41
+ getUserRole(): string | null {
42
+ return localStorage.getItem('userRole');
43
+ }
44
+
45
+ // Logout function to clear the session
46
+ logout(): void {
47
+ localStorage.removeItem('user');
48
+ localStorage.removeItem('userRole');
49
+ }
50
+ }
src/app/case-details-page/case-details-page.component.css CHANGED
@@ -1,5 +1,119 @@
1
  @import '../recordpage/recordpage.component.css';
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  .case-details-list {
4
  width: 100%;
5
  max-width: 1700px;
@@ -143,13 +257,23 @@ hr {
143
  }
144
 
145
  .detail-row span {
146
- color: #64748b;
147
- font-weight: 500;
 
 
 
 
 
148
  }
149
 
150
  .detail-row b {
151
- color: #23272b;
152
- font-weight: 500;
 
 
 
 
 
153
  }
154
 
155
  .bold-value {
@@ -172,18 +296,19 @@ hr {
172
  /* Modal for case details (match record page style) */
173
  .modal-blur-overlay {
174
  position: fixed;
175
- top: 0;
176
- left: 0;
177
- width: 100%;
178
- height: 100%;
179
- background: rgba(0, 0, 0, 0.5);
180
- z-index: 1000;
181
  }
182
 
183
  .modal {
184
- background: #fefefe;
185
- border-radius: 8px;
186
- box-shadow: 0 4px 32px rgba(30, 41, 59, 0.12);
 
187
  overflow: hidden;
188
  position: fixed;
189
  top: 50%;
@@ -198,8 +323,9 @@ hr {
198
  }
199
 
200
  .modal-header {
201
- background: #f5fafd;
202
- border-bottom: 1px solid #e2e8f0;
 
203
  padding: 16px;
204
  display: flex;
205
  justify-content: space-between;
@@ -218,11 +344,13 @@ hr {
218
  background: #fff;
219
  flex: 1;
220
  overflow-y: auto;
 
221
  }
222
 
223
  .modal-footer {
224
- background: #f5fafd;
225
- border-top: 1px solid #e2e8f0;
 
226
  padding: 12px;
227
  text-align: right;
228
  }
@@ -242,47 +370,47 @@ hr {
242
  background: #34b3e0;
243
  }
244
 
245
- @keyframes fadeIn {
246
- from {
247
- opacity: 0;
248
- transform: translate(-50%, -48%);
249
- }
250
- to {
251
- opacity: 1;
252
- transform: translate(-50%, -50%);
253
- }
254
- }
255
-
256
- .active-cases-label {
257
- font-size: 1.8rem;
258
- font-weight: 800;
259
- color: #38bdf8;
260
- margin-bottom: 16px;
261
- position: relative;
262
- text-align: left;
263
- }
264
-
265
  .actions {
266
  display: flex;
267
  flex-direction: row;
268
  justify-content: center;
269
  align-items: center;
270
- gap: 16px;
271
  flex-wrap: nowrap;
272
  width: 100%;
273
  }
274
 
275
- .actions .btn {
276
- min-width: 150px;
277
- max-width: 200px;
278
- padding: 12px 20px;
 
 
279
  font-size: 1.08rem;
280
- white-space: nowrap;
281
- border-radius: 8px;
282
- box-sizing: border-box;
283
- overflow: visible;
284
- text-overflow: unset;
285
- margin: 0 2px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
  }
287
 
288
  .actions-col {
@@ -292,11 +420,20 @@ hr {
292
 
293
  .records th.actions-col,
294
  .records td.actions {
295
- width: 28% !important;
296
- min-width: 260px;
297
  max-width: 320px;
298
  }
299
 
 
 
 
 
 
 
 
 
 
300
  @media (max-width: 700px) {
301
  .actions .btn {
302
  min-width: 120px;
@@ -312,19 +449,422 @@ hr {
312
  }
313
  }
314
 
315
- /* Make all table columns equal width */
316
- .records {
317
- table-layout: fixed;
 
 
 
 
 
 
 
 
 
 
 
 
318
  width: 100%;
 
 
 
 
319
  }
320
- .records th,
321
- .records td {
322
- width: 16.66%; /* 6 columns, 100/6 = 16.66% */
323
- text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  vertical-align: middle;
325
- white-space: nowrap;
326
- overflow: hidden;
327
- text-overflow: ellipsis;
328
  }
329
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
 
 
1
  @import '../recordpage/recordpage.component.css';
2
 
3
+ body, html {
4
+ overflow: auto !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: flex-start;
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;
 
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 {
 
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%;
 
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;
 
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
  }
 
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 {
 
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;
 
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
+ /* table layout for case details page */
466
+ .record-table {
467
  width: 100%;
468
+ table-layout: auto;
469
+ border-collapse: collapse;
470
+ overflow-x: hidden;
471
+ box-sizing: border-box;
472
  }
473
+
474
+ .record-table th, .record-table td {
475
+ padding-left: 12px;
476
+ padding-right: 12px;
477
+ word-break: break-word;
478
+ white-space: normal;
479
+ box-sizing: border-box;
480
+ border-bottom: 1px solid #e5e7eb; /* consistent row border */
481
+ }
482
+
483
+ .record-table tr:last-child td {
484
+ border-bottom: none;
485
+ }
486
+
487
+ th.actions, td.actions {
488
+ text-align: left;
489
+ padding-left: 0;
490
+ padding-right: 0;
491
+ }
492
+
493
+ td.actions {
494
  vertical-align: middle;
 
 
 
495
  }
496
 
497
+ .icon-btn.view {
498
+ margin: 0;
499
+ padding: 0;
500
+ background: none;
501
+ border: none;
502
+ cursor: pointer;
503
+ color: #2563eb;
504
+ font-size: 1.4em;
505
+ display: flex;
506
+ align-items: center;
507
+ justify-content: flex-start;
508
+ }
509
+
510
+ .custom-active-cases-label {
511
+ font-weight: 800;
512
+ color: #2196f3;
513
+ font-size: 2rem;
514
+ margin-bottom: 18px;
515
+ margin-top: 0;
516
+ text-align: left;
517
+ position: relative;
518
+ right: 32vw;
519
+ }
520
+
521
+ .filter-bar select,
522
+ .filter-bar .filter-date {
523
+ padding: 6px 18px;
524
+ border-radius: 6px;
525
+ border: 1.5px solid #cbd5e1;
526
+ font-size: 1em;
527
+ color: #2563eb;
528
+ background: #fff;
529
+ font-weight: 600;
530
+ outline: none;
531
+ transition: border 0.15s;
532
+ min-width: 160px;
533
+ margin-right: 4px;
534
+ }
535
+ .filter-bar select:focus,
536
+ .filter-bar .filter-date:focus {
537
+ border: 1.5px solid #38bdf8;
538
+ }
539
+ .date-group {
540
+ display: inline-flex;
541
+ align-items: center;
542
+ font-family: inherit;
543
+ color: #a86a00;
544
+ font-size: 1.08em;
545
+ font-weight: 500;
546
+ margin-right: 8px;
547
+ gap: 2px;
548
+ }
549
+ .date-label {
550
+ margin: 0 4px 0 4px;
551
+ display: inline-flex;
552
+ align-items: center;
553
+ gap: 2px;
554
+ }
555
+ .calendar-ico {
556
+ font-size: 1.1em;
557
+ margin-left: 2px;
558
+ vertical-align: middle;
559
+ }
560
+ .filter-date {
561
+ border: none;
562
+ background: transparent;
563
+ color: #a86a00;
564
+ font-size: 1em;
565
+ font-weight: 600;
566
+ outline: none;
567
+ min-width: 90px;
568
+ margin-left: 2px;
569
+ }
570
+ .filter-date::-webkit-input-placeholder { color: #a86a00; }
571
+ .filter-date:-moz-placeholder { color: #a86a00; }
572
+ .filter-date::-moz-placeholder { color: #a86a00; }
573
+ .filter-date:-ms-input-placeholder { color: #a86a00; }
574
+
575
+ .fullpage-details {
576
+ width: 100vw;
577
+ min-height: 100vh;
578
+ background: #f8fafc;
579
+ padding: 32px 0 32px 0;
580
+ position: relative;
581
+ z-index: 1;
582
+ }
583
+ .details-content {
584
+ max-width: 1200px;
585
+ margin: 0 auto;
586
+ background: #fff;
587
+ border-radius: 18px;
588
+ box-shadow: 0 8px 32px rgba(30,41,59,0.12);
589
+ padding: 32px 48px;
590
+ }
591
+ .btn.back-btn {
592
+ margin: 0 0 24px 0;
593
+ background: #64748b;
594
+ color: #fff;
595
+ border-radius: 8px;
596
+ font-weight: 600;
597
+ font-size: 1.1rem;
598
+ padding: 10px 28px;
599
+ border: none;
600
+ cursor: pointer;
601
+ }
602
+
603
+ .fullpage-popup-overlay {
604
+ position: fixed;
605
+ top: 0; left: 0; right: 0; bottom: 0;
606
+ width: 100vw;
607
+ height: 100vh;
608
+ background: #f8fafc;
609
+ z-index: 9999;
610
+ display: flex;
611
+ align-items: flex-start;
612
+ justify-content: center;
613
+ overflow-y: auto;
614
+ overscroll-behavior: contain;
615
+ }
616
+ body.popup-open {
617
+ overflow: hidden !important;
618
+ }
619
+
620
+ .fullpage-popup-content {
621
+ background: #fff;
622
+ border-radius: 18px;
623
+ box-shadow: 0 8px 32px rgba(30,41,59,0.18);
624
+ margin: 40px 0;
625
+ max-width: 1200px;
626
+ width: 90vw;
627
+ min-height: 80vh;
628
+ padding: 32px 48px;
629
+ position: relative;
630
+ }
631
+
632
+ /* --- Section Card Styles --- */
633
+ .details-section-card {
634
+ background: #fff;
635
+ border-radius: 22px;
636
+ box-shadow: 0 4px 24px rgba(30,41,59,0.10);
637
+ border-left: 6px solid #38bdf8;
638
+ border-right: 2px solid #e0f2fe;
639
+ padding: 32px 36px 32px 36px;
640
+ margin-bottom: 0;
641
+ margin-top: 32px;
642
+ animation: fadeInUp 0.6s cubic-bezier(.23,1.01,.32,1) both;
643
+ }
644
+ @keyframes fadeInUp {
645
+ from { opacity: 0; transform: translateY(32px); }
646
+ to { opacity: 1; transform: translateY(0); }
647
+ }
648
+
649
+ .section-title {
650
+ font-size: 1.5rem;
651
+ font-weight: 700;
652
+ color: #2563eb;
653
+ margin-bottom: 18px;
654
+ letter-spacing: 0.5px;
655
+ }
656
+
657
+ .subgroup-pills {
658
+ display: flex;
659
+ gap: 18px;
660
+ margin-bottom: 28px;
661
+ flex-wrap: wrap;
662
+ }
663
+ .subgroup-pills button {
664
+ background: linear-gradient(90deg, #2563eb 0%, #38bdf8 100%);
665
+ color: #fff;
666
+ font-weight: 700;
667
+ border: none;
668
+ border-radius: 22px;
669
+ padding: 10px 32px;
670
+ font-size: 1.08em;
671
+ box-shadow: 0 2px 12px rgba(56,189,248,0.13);
672
+ cursor: pointer;
673
+ transition: background 0.18s, box-shadow 0.18s, transform 0.18s;
674
+ outline: none;
675
+ margin-bottom: 4px;
676
+ }
677
+ .subgroup-pills button.active,
678
+ .subgroup-pills button:focus {
679
+ background: linear-gradient(90deg, #38bdf8 0%, #2563eb 100%);
680
+ box-shadow: 0 4px 16px rgba(56,189,248,0.18);
681
+ transform: translateY(-2px) scale(1.04);
682
+ }
683
+
684
+ .fields-table-2col {
685
+ display: flex;
686
+ flex-direction: row;
687
+ gap: 0;
688
+ margin-top: 8px;
689
+ margin-bottom: 8px;
690
+ }
691
+ .fields-col {
692
+ display: flex;
693
+ flex-direction: column;
694
+ gap: 6px;
695
+ }
696
+ .fields-col-labels {
697
+ min-width: 260px;
698
+ text-align: left;
699
+ }
700
+ .fields-col-values {
701
+ min-width: 220px;
702
+ text-align: right;
703
+ }
704
+ .field-label {
705
+ color: #22223b;
706
+ font-weight: 500;
707
+ font-size: 1.08em;
708
+ padding: 2px 0;
709
+ }
710
+ .field-value {
711
+ color: #22223b;
712
+ font-weight: 700;
713
+ font-size: 1.08em;
714
+ padding: 2px 0;
715
+ letter-spacing: 0.5px;
716
+ }
717
+
718
+ /* --- Card Container and Title --- */
719
+ .case-details-title {
720
+ font-size: 2.2rem;
721
+ font-weight: 700;
722
+ color: #22223b;
723
+ margin-bottom: 32px;
724
+ margin-left: 8px;
725
+ display: flex;
726
+ align-items: center;
727
+ justify-content: space-between;
728
+ position: relative;
729
+ }
730
+
731
+ .btn.close-btn {
732
+ background: none;
733
+ color: #64748b;
734
+ border: none;
735
+ font-size: 2.2rem;
736
+ font-weight: 700;
737
+ cursor: pointer;
738
+ position: absolute;
739
+ right: 0;
740
+ top: 0;
741
+ line-height: 1;
742
+ padding: 0 16px;
743
+ transition: color 0.15s;
744
+ }
745
+ .btn.close-btn:hover {
746
+ color: #2563eb;
747
+ }
748
+
749
+ .btn.close-btn-bottom {
750
+ position: absolute;
751
+ right: 32px;
752
+ top: 32px;
753
+ background: #2563eb;
754
+ color: #fff;
755
+ border: none;
756
+ border-radius: 50%;
757
+ width: 48px;
758
+ height: 48px;
759
+ font-size: 2.2rem;
760
+ font-weight: 700;
761
+ box-shadow: 0 4px 16px rgba(56,189,248,0.18);
762
+ cursor: pointer;
763
+ z-index: 10001;
764
+ display: flex;
765
+ align-items: center;
766
+ justify-content: center;
767
+ transition: background 0.18s, color 0.18s, box-shadow 0.18s;
768
+ }
769
+ .btn.close-btn-bottom:hover {
770
+ background: #38bdf8;
771
+ color: #22223b;
772
+ }
773
+
774
+ .progress-col {
775
+ text-align: left;
776
+ min-width: 90px;
777
+ padding-left: 0;
778
+ padding-right: 0;
779
+ }
780
+
781
+ .progress-dot {
782
+ width: 18px;
783
+ height: 18px;
784
+ border-radius: 50%;
785
+ display: inline-block;
786
+ margin-right: 6px;
787
+ vertical-align: middle;
788
+ box-shadow: 0 2px 8px rgba(56,189,248,0.10);
789
+ }
790
+ .progress-dot.blue {
791
+ background: linear-gradient(135deg, #2563eb 0%, #38bdf8 100%);
792
+ }
793
+ .progress-dot.green {
794
+ background: linear-gradient(135deg, #34d399 0%, #6ee7b7 100%);
795
+ }
796
+ .progress-check {
797
+ font-size: 1.2em;
798
+ color: #34d399;
799
+ background: #d1fae5;
800
+ border-radius: 4px;
801
+ padding: 2px 4px;
802
+ display: inline-block;
803
+ margin-right: 6px;
804
+ vertical-align: middle;
805
+ }
806
+ .progress-value {
807
+ font-weight: 700;
808
+ font-size: 1em;
809
+ color: #22223b;
810
+ vertical-align: middle;
811
+ }
812
+
813
+ .priority-pill {
814
+ display: inline-flex;
815
+ align-items: center;
816
+ font-weight: bold;
817
+ font-size: 0.98em;
818
+ padding: 2px 12px 2px 8px;
819
+ border-radius: 16px;
820
+ margin-right: 2px;
821
+ letter-spacing: 0.02em;
822
+ }
823
+ .priority-high {
824
+ background: #fee2e2;
825
+ color: #b91c1c;
826
+ }
827
+ .priority-medium {
828
+ background: #fef9c3;
829
+ color: #ca8a04;
830
+ }
831
+ .priority-low {
832
+ background: #dcfce7;
833
+ color: #15803d;
834
+ }
835
+
836
+ .evidence-upload-section {
837
+ margin-top: 32px;
838
+ padding: 16px;
839
+ background: #f3f4f6;
840
+ border-radius: 12px;
841
+ box-shadow: 0 1px 4px rgba(0,0,0,0.04);
842
+ }
843
+ .evidence-upload-section h3 {
844
+ margin-bottom: 12px;
845
+ font-size: 1.1em;
846
+ color: #2563eb;
847
+ }
848
+ .evidence-list {
849
+ margin-top: 10px;
850
+ }
851
+ .evidence-file {
852
+ display: flex;
853
+ align-items: center;
854
+ font-size: 0.98em;
855
+ margin-bottom: 6px;
856
+ color: #374151;
857
+ }
858
+ .evidence-file i {
859
+ margin-right: 8px;
860
+ color: #2563eb;
861
+ }
862
+
863
+
864
+
865
+
866
+
867
+
868
+
869
+
870
 
src/app/case-details-page/case-details-page.component.html CHANGED
@@ -1,107 +1,234 @@
1
- <div class="searchbar-topright">
2
- <form class="modern-searchbar-form compact white-bg">
3
- <span class="modern-searchbar-icon">
4
- <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#64748b" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
5
- <circle cx="11" cy="11" r="8"/>
6
- <line x1="21" y1="21" x2="16.65" y2="16.65"/>
7
- </svg>
8
- </span>
9
- <input type="text" class="modern-searchbar-input white-bg" placeholder="Search... (not active)" autocomplete="off" />
10
- </form>
11
- </div>
12
-
13
- <div class="logo-title-row">
14
- <img src="/assets/pykara-logo.png" alt="PyDetect Logo" class="logo-img" style="cursor:pointer;" (click)="navigateHome()" />
15
- <div class="py-detect-title">
16
- <span class="py-letter p">P</span>
17
- <span class="py-letter y">Y</span>
18
- <span class="py-shape"></span>
19
- <span class="py-letter d">D</span>
20
- <span class="py-letter e">E</span>
21
- <span class="py-letter t">T</span>
22
- <span class="py-letter e2">E</span>
23
- <span class="py-letter c">C</span>
24
- <span class="py-letter t2">T</span>
25
  </div>
26
  </div>
27
 
28
- <div style="height:128px;"></div>
 
 
 
 
 
 
 
 
29
 
30
- <main class="content" role="main">
31
- <div class="wrapper records-center">
32
- <div class="welcome-left">Welcome User 👮</div>
33
- <div *ngIf="username" class="welcome-user">Welcome, {{ username }}👮</div>
34
- <div class="active-cases-label left-align"><b>Your Active Cases</b></div>
35
- <div class="table-wrap padded-table-wrap">
36
- <table class="records">
37
- <thead>
38
- <tr>
39
- <th>Case ID</th>
40
- <th>Crime</th>
41
- <th>Date &amp; Time</th>
42
- <th>Location</th>
43
- <th>Status</th>
44
- <th class="actions-col">Actions</th>
45
- </tr>
46
- </thead>
47
- <tbody>
48
- <tr *ngFor="let c of cases">
49
- <td class="mono">{{ c.caseId || '—' }}</td>
50
- <td>{{ c.crime || '—' }}</td>
51
- <td>{{ c.dateTime ? (c.dateTime | date:'yyyy-MM-dd HH:mm') : '—' }}</td>
52
- <td class="ellipsis" [title]="c.police.address || ''">{{ c.police.address || '—' }}</td>
53
- <td>
54
- <span class="status"
55
- [class.open]="c.status==='Open'"
56
- [class.under]="c.status==='Under Investigation'"
57
- [class.closed]="c.status==='Closed'">
58
- {{ c.status || '—' }}
59
- </span>
60
- </td>
61
- <td class="actions">
62
- <button type="button" class="btn view" (click)="openDetails(c)">View Details</button>
63
- <button type="button" class="btn edit" (click)="goToDetect()">Go Detect</button>
64
- </td>
65
- </tr>
66
- <tr *ngIf="cases.length === 0">
67
- <td colspan="6" class="empty">No records found.</td>
68
- </tr>
69
- </tbody>
70
- </table>
71
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
- <!-- Modal for case details -->
74
- <!-- ...existing code above... -->
75
- <!-- Modal for case details -->
76
- <!-- ...existing code above... -->
77
- <!-- Modal for case details (match record page style) -->
78
- <div class="modal-blur-overlay" *ngIf="showDetails"></div>
79
- <div class="modal" *ngIf="showDetails" role="dialog" aria-modal="true" aria-labelledby="detailsTitle">
80
- <div class="modal-header">
81
- <h2 id="detailsTitle">Case Details</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  </div>
83
- <div class="modal-body" *ngIf="selectedCase as sc">
84
- <div class="detail-row"><span>Case ID</span><b>{{ sc.caseId || '—' }}</b></div>
85
- <div class="detail-row"><span>Crime</span><b>{{ sc.crime || '—' }}</b></div>
86
- <div class="detail-row"><span>Date & Time</span><b>{{ sc.dateTime ? (sc.dateTime | date:'yyyy-MM-dd HH:mm') : '—' }}</b></div>
87
- <div class="detail-row"><span>Location</span><b>{{ sc.police.address || '—' }}</b></div>
88
- <hr />
89
- <div class="detail-row"><span>Investigation Officer</span><b>{{ sc.police.name || '—' }}</b></div>
90
- <div class="detail-row"><span>Duty Person</span><b>{{ sc.police.dutyPerson || '—' }}</b></div>
91
- <div class="detail-row"><span>Mode of Crime</span><b>{{ sc.police.modeOfCrime || '—' }}</b></div>
92
- <hr />
93
- <div class="detail-row"><span>Accused Name</span><b>{{ sc.accused.name || '—' }}</b></div>
94
- <div class="detail-row"><span>Accused Gender</span><b>{{ sc.accused.gender || '—' }}</b></div>
95
- <div class="detail-row"><span>Accused Age</span><b>{{ sc.accused.age || '' }}</b></div>
96
- <div class="detail-row"><span>Accused Address</span><b>{{ sc.accused.address || '' }}</b></div>
97
- <hr />
98
- <div class="detail-block">
99
- <div class="label">Notes / Explanation</div>
100
- <p class="explain">{{ sc.police.information || '—' }}</p>
101
- </div>
 
 
 
 
 
 
 
 
 
102
  </div>
103
- <div class="modal-footer">
104
- <button type="button" class="btn" (click)="closeDetails()">Close</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  </div>
 
106
  </div>
 
 
 
107
 
 
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>
21
  </div>
22
 
23
+ <div class="record-card">
24
+ <div class="record-header">
25
+ <div class="record-title-group">
26
+ <span class="record-title">Your Assigned Cases</span>
27
+ </div>
28
+ <div class="record-header-actions">
29
+ <input class="record-search" type="text" [(ngModel)]="q" placeholder="Search your cases..." />
30
+ </div>
31
+ </div>
32
 
33
+ <div class="analytics-summary">
34
+ <div class="summary-card total">
35
+ <div class="summary-label">Total Cases</div>
36
+ <div class="summary-value">{{ totalCases }}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  </div>
38
+ <div class="summary-card open">
39
+ <div class="summary-label">Open</div>
40
+ <div class="summary-value">{{ openCases }}</div>
41
+ </div>
42
+ <div class="summary-card closed">
43
+ <div class="summary-label">Closed</div>
44
+ <div class="summary-value">{{ closedCases }}</div>
45
+ </div>
46
+ <div class="summary-card review">
47
+ <div class="summary-label">Pending Review</div>
48
+ <div class="summary-value">{{ reviewCases }}</div>
49
+ </div>
50
+ </div>
51
 
52
+ <div class="record-meta" style="padding: 8px 24px 0 24px; color: #6b7280; font-size: 0.98em;">
53
+ {{ filteredCases.length }} items • Updated a few seconds ago
54
+ </div>
55
+
56
+ <div class="filter-bar">
57
+ <select [(ngModel)]="filterStatus">
58
+ <option value="">Status</option>
59
+ <option *ngFor="let status of statusTypes">{{ status }}</option>
60
+ </select>
61
+ <select [(ngModel)]="filterCrimeType">
62
+ <option value="">Crime Type</option>
63
+ <option *ngFor="let type of crimeTypes">{{ type }}</option>
64
+ </select>
65
+ <button (click)="applyFilters()">Apply</button>
66
+ <button (click)="resetFilters()">Reset</button>
67
+ </div>
68
+
69
+ <!-- Table/list view always visible -->
70
+ <table class="record-table">
71
+ <thead>
72
+ <tr>
73
+ <th>#</th>
74
+ <th>Case ID</th>
75
+ <th>Priority</th>
76
+ <th>Status</th>
77
+ <th>Crime Type</th>
78
+ <th>Date &amp; Time</th>
79
+ <th>Location</th>
80
+ <th>Suspect Name</th>
81
+ <th>Last Updated</th>
82
+ <th class="progress-col">Progress</th>
83
+ <th class="next-action-col">Next Action</th>
84
+ <th class="actions">Actions</th>
85
+ </tr>
86
+ </thead>
87
+ <tbody>
88
+ <tr *ngFor="let c of filteredCases; let i = index">
89
+ <td>{{ i + 1 }}</td>
90
+ <td class="mono">
91
+ <a (click)="openDetails(c)" style="cursor:pointer; color:#2563eb; text-decoration:underline;">
92
+ {{ c.caseId || '—' }}
93
+ </a>
94
+ </td>
95
+ <td>
96
+ <ng-container [ngSwitch]="getCasePriority(c)">
97
+ <span *ngSwitchCase="'High'" class="priority-pill priority-high" title="High Priority">
98
+ 🔴 High
99
+ </span>
100
+ <span *ngSwitchCase="'Medium'" class="priority-pill priority-medium" title="Medium Priority">
101
+ 🟡 Medium
102
+ </span>
103
+ <span *ngSwitchCase="'Low'" class="priority-pill priority-low" title="Low Priority">
104
+ 🟢 Low
105
+ </span>
106
+ <span *ngSwitchDefault style="color:#6b7280;">—</span>
107
+ </ng-container>
108
+ </td>
109
+ <td>
110
+ <span class="status-label"
111
+ [ngClass]="{
112
+ 'status-open': c.status === 'Open',
113
+ 'status-under': c.status === 'Under Investigation',
114
+ 'status-closed': c.status === 'Closed'
115
+ }">
116
+ {{ c.status || '—' }}
117
+ </span>
118
+ </td>
119
+ <td>{{ c.crime || '—' }}</td>
120
+ <td>{{ c.dateTime ? (c.dateTime | date:'M/d/yyyy HH:mm') : '—' }}</td>
121
+ <td>{{ c.police?.address || '—' }}</td>
122
+ <td>{{ c.accused?.name || '—' }}</td>
123
+ <td>{{ c.lastUpdated ? (c.lastUpdated | date:'M/d/yyyy HH:mm') : '—' }}</td>
124
+ <td class="progress-col">
125
+ <ng-container [ngSwitch]="getProgressValue(c)">
126
+ <ng-container *ngSwitchCase="100">
127
+ <span class="progress-check">&#x2714;</span>
128
+ <span class="progress-value">100%</span>
129
+ </ng-container>
130
+ <ng-container *ngSwitchCase="75">
131
+ <span class="progress-dot green"></span>
132
+ <span class="progress-value">75%</span>
133
+ </ng-container>
134
+ <ng-container *ngSwitchDefault>
135
+ <span class="progress-dot blue"></span>
136
+ <span class="progress-value">{{ getProgressValue(c) }}%</span>
137
+ </ng-container>
138
+ </ng-container>
139
+ </td>
140
+ <td class="next-action-col">
141
+ {{ getNextActionMessage(c) }}
142
+ </td>
143
+ <td class="actions">
144
+ <button type="button" class="icon-btn view" (click)="openDetails(c)" title="View Case Details" aria-label="View Case Details">
145
+ <i class="fas fa-eye"></i>
146
+ </button>
147
+ <button *ngIf="isInvestigator()" type="button" class="icon-btn upload" (click)="showEvidencePanel(c)" title="Upload Evidence" aria-label="Upload Evidence">
148
+ <i class="fas fa-upload"></i>
149
+ </button>
150
+ </td>
151
+ </tr>
152
+ <tr *ngIf="filteredCases.length === 0">
153
+ <td colspan="12" class="empty">No records found.</td>
154
+ </tr>
155
+ </tbody>
156
+ </table>
157
+
158
+ <!-- Evidence Upload Section for Investigators only, below the table -->
159
+ <div *ngIf="isInvestigator() && selectedCase" class="evidence-upload-section">
160
+ <h3>Upload Evidence for Case: {{ selectedCase.caseId }}</h3>
161
+ <input type="file" multiple (change)="onEvidenceUpload($event)" />
162
+ <div class="evidence-list" *ngIf="uploadedEvidence.length">
163
+ <div *ngFor="let file of uploadedEvidence" class="evidence-file">
164
+ <i class="fas fa-file-upload"></i> {{ file.name }}
165
  </div>
166
+ </div>
167
+ </div>
168
+
169
+ <!-- Evidence Upload Panel for selected case -->
170
+ <div *ngIf="isInvestigator() && evidencePanelCase && evidencePanelCase.caseId" class="evidence-upload-section">
171
+ <hr class="evidence-hr" />
172
+ <div class="evidence-title">
173
+ <i class="fas fa-folder-open evidence-folder"></i>
174
+ Evidence Upload for Case: {{ evidencePanelCase.caseId }}
175
+ </div>
176
+ <hr class="evidence-hr" />
177
+ <div class="evidence-type-tabs">
178
+ <button [class.active]="evidenceType === 'Document'" (click)="setEvidenceType('Document')">Document</button>
179
+ <button [class.active]="evidenceType === 'Photo'" (click)="setEvidenceType('Photo')">Photo</button>
180
+ </div>
181
+ <div class="evidence-actions">
182
+ <label class="evidence-file-label">
183
+ [Choose File]
184
+ <input type="file" multiple (change)="onEvidenceFileSelectType($event)" style="display:none;" />
185
+ </label>
186
+ </div>
187
+ <hr class="evidence-hr" />
188
+ <div class="evidence-list-block">
189
+ <div class="evidence-list-title">Uploaded Evidence ({{ evidenceType }}):</div>
190
+ <div *ngFor="let file of evidenceFiles[evidencePanelCase.caseId][evidenceType]" class="evidence-file-row">
191
+ <i [ngClass]="getEvidenceIcon(file.name)" class="evidence-file-icon"></i>
192
+ <span class="evidence-file-name">{{ file.name }}</span>
193
+ <a class="evidence-view-link" href="#" (click)="viewEvidenceFile(file)">(View)</a>
194
  </div>
195
+ </div>
196
+ <hr class="evidence-hr" />
197
+ </div>
198
+
199
+ <!-- Full-page overlay for details (no evidence upload here) -->
200
+ <div *ngIf="selectedCase" class="fullpage-popup-overlay">
201
+ <div class="fullpage-popup-content">
202
+ <div class="case-details-title">Case Details</div>
203
+ <div class="details-sections">
204
+ <ng-container *ngFor="let sectionKey of ['crime', 'suspect', 'notes']">
205
+ <div class="details-section-card">
206
+ <div class="section-title">{{ sections[sectionKey].title }}</div>
207
+ <div class="subgroup-pills">
208
+ <button *ngFor="let subgroup of getSubgroups(sectionKey)"
209
+ [class.active]="selectedSubgroup[sectionKey] === subgroup"
210
+ (click)="selectSubgroup(sectionKey, subgroup)">
211
+ {{ subgroup }}
212
+ </button>
213
+ </div>
214
+ <div class="fields-table-2col">
215
+ <div class="fields-col fields-col-labels">
216
+ <div class="field-label" *ngFor="let field of getFieldsForSubgroup(sectionKey, selectedSubgroup[sectionKey])">
217
+ {{ field }}
218
+ </div>
219
+ </div>
220
+ <div class="fields-col fields-col-values">
221
+ <div class="field-value" *ngFor="let field of getFieldsForSubgroup(sectionKey, selectedSubgroup[sectionKey])">
222
+ {{ getFieldValue(selectedCase, sectionKey, field) }}
223
+ </div>
224
+ </div>
225
+ </div>
226
+ </div>
227
+ </ng-container>
228
  </div>
229
+ <button class="btn close-btn-bottom" (click)="closeDetails()" title="Close">&times;</button>
230
  </div>
231
+ </div>
232
+ </div>
233
+ <!-- End of record-card -->
234
 
src/app/case-details-page/case-details-page.component.ts CHANGED
@@ -2,6 +2,8 @@ import { Component, OnInit } from '@angular/core';
2
  import { Router } from '@angular/router';
3
  import { CaseStoreService, PoliceCase } from '../shared/case-store.service';
4
 
 
 
5
  @Component({
6
  selector: 'app-case-details-page',
7
  templateUrl: './case-details-page.component.html',
@@ -12,6 +14,59 @@ export class CaseDetailsPageComponent implements OnInit {
12
  showDetails = false;
13
  selectedCase: PoliceCase | null = null;
14
  username: string = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  constructor(private caseStore: CaseStoreService, private router: Router) {}
17
 
@@ -20,14 +75,59 @@ export class CaseDetailsPageComponent implements OnInit {
20
  this.username = localStorage.getItem('username') || sessionStorage.getItem('username') || '';
21
  }
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  openDetails(c: PoliceCase): void {
24
  this.selectedCase = c;
25
  this.showDetails = true;
 
26
  }
27
 
28
  closeDetails(): void {
29
  this.showDetails = false;
30
  this.selectedCase = null;
 
31
  }
32
 
33
  goToDetect(): void {
@@ -37,4 +137,246 @@ export class CaseDetailsPageComponent implements OnInit {
37
  navigateHome(): void {
38
  this.router.navigate(['/']);
39
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
 
2
  import { Router } from '@angular/router';
3
  import { CaseStoreService, PoliceCase } from '../shared/case-store.service';
4
 
5
+ type EvidenceType = 'Document' | 'Photo';
6
+
7
  @Component({
8
  selector: 'app-case-details-page',
9
  templateUrl: './case-details-page.component.html',
 
14
  showDetails = false;
15
  selectedCase: PoliceCase | null = null;
16
  username: string = '';
17
+ q: string = '';
18
+
19
+ filterStatus: string = '';
20
+ filterCrimeType: string = '';
21
+ filterDateFrom: string = '';
22
+ filterDateTo: string = '';
23
+
24
+ sections: any = {
25
+ crime: {
26
+ title: 'Crime Details',
27
+ subgroups: {
28
+ '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'],
29
+ 'Location & People': ['Location', 'Jurisdiction / PS', 'Scene Type', 'Reported By', 'Reported Contact', 'Witness Count', 'Victim Name', 'Victim Contact', 'Victim Summary', 'Suspected Offender Known?', 'Suspect Link'],
30
+ 'Offence & Context': ['Legal Sections / Charges', 'Offence Category', 'Offence Description', 'Suspected Motive', 'Confirmed Motive', 'Weapon Involved', 'Property Loss / Damage'],
31
+ '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'],
32
+ 'Operational Notes': ['Investigating Officer', 'Duty Person', 'Supervising Officer', 'Patrol Notes', 'Arrest Made', 'Arrest Location', 'Initial Actions Taken', 'riskLevel', 'Confidentiality'],
33
+ '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'],
34
+ 'Remark': ['Remark']
35
+ }
36
+ },
37
+ suspect: {
38
+ title: 'Suspect Details',
39
+ subgroups: {
40
+ 'Identity': ['Suspect ID', 'Suspect Name', 'Alias / Nickname', 'Age', 'Gender', 'Nationality', 'Nationality ID / Passport Number', 'Languages', 'Address', 'Known Aliases', 'Government ID'],
41
+ 'Physical Description': ['Height (cm)', 'Weight (kg)', 'Build', 'Hair Color', 'Eye Color', 'Distinguishing Marks', 'Tattoo Details', 'Scar Details', 'Photo Upload'],
42
+ 'Background': ['Employment', 'Education', 'Occupation', 'Company', 'Workplace Address', 'Marital Status', 'Known Habits', 'Known Financial Details'],
43
+ 'Known Associates': ['Associate Names', 'Gang Affiliation', 'Family Connections', 'Social Media Handles'],
44
+ 'Prior Records': ['Criminal History', 'Prior Arrests', 'Probation/Parole Status'],
45
+ 'Remark': ['Remark']
46
+ }
47
+ },
48
+ notes: {
49
+ title: 'Evidence and Documents',
50
+ subgroups: {
51
+ 'Investigation Notes': ['Initial Findings', 'Detailed Notes', 'Status', 'Version History / Updates'],
52
+ 'Evidence Files': ['Evidence Photos', 'Evidence Videos', 'Evidence Documents'],
53
+ 'Links and Recommendation': ['Links to Evidence', 'Final Recommendations'],
54
+ 'Remark': ['Remark']
55
+ }
56
+ }
57
+ };
58
+
59
+ selectedSubgroup: any = {
60
+ crime: 'Identification & Timing',
61
+ suspect: 'Identity',
62
+ notes: 'Investigation Notes'
63
+ };
64
+
65
+ uploadedEvidence: File[] = [];
66
+
67
+ evidencePanelCase: PoliceCase | null = null;
68
+ evidenceType: EvidenceType = 'Document';
69
+ evidenceFiles: { [caseId: string]: { Document: File[]; Photo: File[] } } = {};
70
 
71
  constructor(private caseStore: CaseStoreService, private router: Router) {}
72
 
 
75
  this.username = localStorage.getItem('username') || sessionStorage.getItem('username') || '';
76
  }
77
 
78
+ get filteredCases(): PoliceCase[] {
79
+ let filtered = this.cases;
80
+
81
+ if (this.filterStatus) {
82
+ filtered = filtered.filter(c => c.status === this.filterStatus);
83
+ }
84
+ if (this.filterCrimeType) {
85
+ filtered = filtered.filter(c => c.crime === this.filterCrimeType);
86
+ }
87
+ // Add date filtering logic here if needed
88
+
89
+ if (!this.q?.trim()) return filtered;
90
+ const qLower = this.q.trim().toLowerCase();
91
+ return filtered.filter(c =>
92
+ (c.caseId || '').toLowerCase().includes(qLower) ||
93
+ (c.status || '').toLowerCase().includes(qLower) ||
94
+ (c.crime || '').toLowerCase().includes(qLower) ||
95
+ (c.police?.address || '').toLowerCase().includes(qLower) ||
96
+ (c.accused?.name || '').toLowerCase().includes(qLower)
97
+ );
98
+ }
99
+
100
+ get totalCases(): number {
101
+ return this.cases.length;
102
+ }
103
+ get openCases(): number {
104
+ return this.cases.filter(c => c.status === 'Open').length;
105
+ }
106
+ get closedCases(): number {
107
+ return this.cases.filter(c => c.status === 'Closed').length;
108
+ }
109
+
110
+ get reviewCases(): number {
111
+ return this.cases.filter(c => c.status === 'Under Investigation').length;
112
+ }
113
+
114
+ get statusTypes(): string[] {
115
+ return Array.from(new Set(this.cases.map(c => c.status).filter(s => !!s))) as string[];
116
+ }
117
+ get crimeTypes(): string[] {
118
+ return Array.from(new Set(this.cases.map(c => c.crime).filter(s => !!s))) as string[];
119
+ }
120
+
121
  openDetails(c: PoliceCase): void {
122
  this.selectedCase = c;
123
  this.showDetails = true;
124
+ document.body.classList.add('popup-open');
125
  }
126
 
127
  closeDetails(): void {
128
  this.showDetails = false;
129
  this.selectedCase = null;
130
+ document.body.classList.remove('popup-open');
131
  }
132
 
133
  goToDetect(): void {
 
137
  navigateHome(): void {
138
  this.router.navigate(['/']);
139
  }
140
+
141
+ applyFilters() {
142
+ // No-op: filteredCases getter will use filters
143
+ }
144
+ resetFilters() {
145
+ this.filterStatus = '';
146
+ this.filterCrimeType = '';
147
+ this.filterDateFrom = '';
148
+ this.filterDateTo = '';
149
+ this.q = '';
150
+ }
151
+ getSubgroups(sectionKey: string): string[] {
152
+ return Object.keys(this.sections[sectionKey].subgroups);
153
+ }
154
+
155
+ getFieldsForSubgroup(sectionKey: string, subgroup: string): string[] {
156
+ return this.sections[sectionKey].subgroups[subgroup] || [];
157
+ }
158
+
159
+ selectSubgroup(sectionKey: string, subgroup: string) {
160
+ this.selectedSubgroup[sectionKey] = subgroup;
161
+ }
162
+
163
+ getFieldValue(sc: any, sectionKey: string, field: string): any {
164
+ // Map field labels to PoliceCase property paths (copied from recordpage.component.ts)
165
+ const fieldMap: { [key: string]: string | string[] } = {
166
+ // Crime Details
167
+ 'Case ID': 'caseId',
168
+ 'FIR / Ref #': 'firRef',
169
+ 'Crime Type': 'crime',
170
+ 'Case Category': 'caseCategory',
171
+ 'Date & Time (Entry)': 'dateTime',
172
+ 'Occurred From': 'occurredFrom',
173
+ 'Occurred To': 'occurredTo',
174
+ 'Time Reported': 'timeReported',
175
+ 'Time Discovered': 'timeDiscovered',
176
+ 'Country': 'country',
177
+ 'State': 'state',
178
+ 'District': 'district',
179
+ 'Number of Victims': 'numberOfVictims',
180
+ 'Brief Description': 'briefDescription',
181
+ 'Location': ['police', 'address'],
182
+ 'Jurisdiction / PS': 'jurisdiction',
183
+ 'Scene Type': 'sceneType',
184
+ 'Reported By': 'reportedBy',
185
+ 'Reported Contact': 'reportedContact',
186
+ 'Witness Count': 'witnessCount',
187
+ 'Victim Name': 'victimName',
188
+ 'Victim Contact': 'victimContact',
189
+ 'Victim Summary': 'victimSummary',
190
+ 'Suspected Offender Known?': 'suspectedOffenderKnown',
191
+ 'Suspect Link': 'suspectLink',
192
+ 'Legal Sections / Charges': 'legalSections',
193
+ 'Offence Category': 'offenceCategory',
194
+ 'Offence Description': 'offenceDescription',
195
+ 'Suspected Motive': 'suspectedMotive',
196
+ 'Confirmed Motive': 'confirmedMotive',
197
+ 'Weapon Involved': 'weaponInvolved',
198
+ 'Property Loss / Damage': 'propertyLoss',
199
+ 'Evidence Collected': 'evidenceCollected',
200
+ 'Forensic Tests Required': 'forensicTestsRequired',
201
+ 'Scene Condition': 'sceneCondition',
202
+ 'Photos / Video?': 'photosVideo',
203
+ 'CCTV Present?': 'cctvPresent',
204
+ 'CCTV Sources / IDs': 'cctvSources',
205
+ 'Physical Evidence (list)': 'physicalEvidence',
206
+ 'Chain of Custody?': 'chainOfCustody',
207
+ 'Digital Evidence': 'digitalEvidence',
208
+ 'Evidence Storage Reference': 'evidenceStorageReference',
209
+ 'Investigating Officer': ['police', 'name'],
210
+ 'Duty Person': ['police', 'dutyPerson'],
211
+ 'Supervising Officer': ['police', 'supervisingOfficer'],
212
+ 'Patrol Notes': ['police', 'patrolNotes'],
213
+ 'Arrest Made': 'arrestMade',
214
+ 'Arrest Location': 'arrestLocation',
215
+ 'Initial Actions Taken': 'initialActionsTaken',
216
+ 'riskLevel': 'riskLevel',
217
+ 'Confidentiality': 'confidentiality',
218
+ 'Biometric / Forensic IDs': 'biometricIds',
219
+ 'DNA Ref ID': 'dnaRefId',
220
+ 'Fingerprint ID': 'fingerprintId',
221
+ 'Case Status': 'status',
222
+ 'Linked Cases': 'linkedCases',
223
+ 'arrestCount': 'arrestCount',
224
+ 'Case Priority': 'casePriority',
225
+ 'Follow-up Date': 'followUpDate',
226
+ 'Court Case ID': 'courtCaseId',
227
+ 'Next Hearing Date': 'nextHearingDate',
228
+ 'Final Summary': 'finalSummary',
229
+ 'Remark': 'remark',
230
+ // Suspect Details
231
+ 'Suspect ID': ['accused', 'suspectId'],
232
+ 'Suspect Name': ['accused', 'name'],
233
+ 'Alias / Nickname': ['accused', 'alias'],
234
+ 'Age': ['accused', 'age'],
235
+ 'Gender': ['accused', 'gender'],
236
+ 'Nationality': ['accused', 'nationality'],
237
+ 'Nationality ID / Passport Number': ['accused', 'passportNumber'],
238
+ 'Languages': ['accused', 'languages'],
239
+ 'Address': ['accused', 'address'],
240
+ 'Known Aliases': ['accused', 'knownAliases'],
241
+ 'Government ID': ['accused', 'governmentId'],
242
+ 'Height (cm)': ['accused', 'height'],
243
+ 'Weight (kg)': ['accused', 'weight'],
244
+ 'Build': ['accused', 'build'],
245
+ 'Hair Color': ['accused', 'hairColor'],
246
+ 'Eye Color': ['accused', 'eyeColor'],
247
+ 'Distinguishing Marks': ['accused', 'distinguishingMarks'],
248
+ 'Tattoo Details': ['accused', 'tattooDetails'],
249
+ 'Scar Details': ['accused', 'scarDetails'],
250
+ 'Photo Upload': ['accused', 'photoUpload'],
251
+ 'Employment': ['accused', 'employment'],
252
+ 'Education': ['accused', 'education'],
253
+ 'Occupation': ['accused', 'occupation'],
254
+ 'Company': ['accused', 'company'],
255
+ 'Workplace Address': ['accused', 'workplaceAddress'],
256
+ 'Marital Status': ['accused', 'maritalStatus'],
257
+ 'Known Habits': ['accused', 'knownHabits'],
258
+ 'Known Financial Details': ['accused', 'knownFinancialDetails'],
259
+ 'Associate Names': ['accused', 'associateNames'],
260
+ 'Gang Affiliation': ['accused', 'gangAffiliation'],
261
+ 'Family Connections': ['accused', 'familyConnections'],
262
+ 'Social Media Handles': ['accused', 'socialMediaHandles'],
263
+ 'Criminal History': ['accused', 'criminalHistory'],
264
+ 'Prior Arrests': ['accused', 'priorArrests'],
265
+ 'Probation/Parole Status': ['accused', 'probationStatus'],
266
+ // Notes/Evidence
267
+ 'Initial Findings': ['police', 'information'],
268
+ 'Detailed Notes': ['notes', 'detailedNotes'],
269
+ 'Status': 'status',
270
+ 'Version History / Updates': ['notes', 'versionHistory'],
271
+ 'Evidence Photos': ['legal', 'evidencePhotos'],
272
+ 'Evidence Videos': ['legal', 'evidenceVideos'],
273
+ 'Evidence Documents': ['legal', 'evidenceDocuments'],
274
+ 'Links to Evidence': ['legal', 'linksToEvidence'],
275
+ 'Final Recommendations': ['legal', 'finalRecommendations'],
276
+ 'Witness Statements': ['legal', 'witnessStatements'],
277
+ 'Confessions': ['legal', 'confessions'],
278
+ // Audit Fields
279
+ 'Created By': 'createdBy',
280
+ 'Creation Date': 'creationDate',
281
+ 'Last Updated': 'lastUpdated',
282
+ 'Verified By': 'verifiedBy'
283
+ };
284
+
285
+ const path = fieldMap[field] || field;
286
+ if (Array.isArray(path)) {
287
+ let value = sc;
288
+ for (const p of path) {
289
+ if (value && value[p] !== undefined) value = value[p];
290
+ else return '—';
291
+ }
292
+ return value !== undefined && value !== null && value !== '' ? value : '—';
293
+ } else {
294
+ return sc[path] !== undefined && sc[path] !== null && sc[path] !== '' ? sc[path] : '—';
295
+ }
296
+ }
297
+
298
+ getProgressValue(caseObj: any): number {
299
+ let total = 0, filled = 0;
300
+ ['crime', 'suspect', 'notes'].forEach(sectionKey => {
301
+ const subgroups = this.getSubgroups(sectionKey);
302
+ subgroups.forEach(subgroup => {
303
+ const fields = this.getFieldsForSubgroup(sectionKey, subgroup);
304
+ total += fields.length;
305
+ fields.forEach(field => {
306
+ const value = this.getFieldValue(caseObj, sectionKey, field);
307
+ if (value && value !== '—') filled++;
308
+ });
309
+ });
310
+ });
311
+ return total ? Math.round((filled / total) * 100) : 0;
312
+ }
313
+ getNextActionMessage(c: any): string {
314
+ // Example logic based on status and progress
315
+ const progress = this.getProgressValue(c);
316
+ if (c.status === 'Closed' || progress === 100) {
317
+ return 'Case completed';
318
+ }
319
+ if (c.status === 'Under Review' || progress >= 75) {
320
+ return 'Await supervisor approval';
321
+ }
322
+ if (c.status === 'Open' || progress < 75) {
323
+ return 'Collect evidence';
324
+ }
325
+ return '—';
326
+ }
327
+ getCasePriority(c: PoliceCase): 'High' | 'Medium' | 'Low' {
328
+ // Automatic logic based on crime type and status
329
+ const highCrimes = ['Murder', 'Robbery'];
330
+ const mediumCrimes = ['Theft', 'Assault'];
331
+ if (highCrimes.includes(c.crime) || c.status === 'Open') {
332
+ return 'High';
333
+ }
334
+ if (mediumCrimes.includes(c.crime) || c.status === 'Under Investigation') {
335
+ return 'Medium';
336
+ }
337
+ return 'Low';
338
+ }
339
+
340
+ isInvestigator(): boolean {
341
+ // Simple role check: username contains 'investigator' (customize as needed)
342
+ return this.username.toLowerCase().includes('investigator');
343
+ }
344
+
345
+ onEvidenceUpload(event: any): void {
346
+ const files = Array.from(event.target.files) as File[];
347
+ this.uploadedEvidence = [...this.uploadedEvidence, ...files];
348
+ // You can add logic to save evidence to the case or backend here
349
+ }
350
+
351
+ showEvidencePanel(c: PoliceCase): void {
352
+ if (!c.caseId) return;
353
+ this.evidencePanelCase = c;
354
+ this.evidenceType = 'Document';
355
+ if (!this.evidenceFiles[c.caseId]) {
356
+ this.evidenceFiles[c.caseId] = { Document: [], Photo: [] };
357
+ }
358
+ }
359
+
360
+ setEvidenceType(type: EvidenceType): void {
361
+ this.evidenceType = type;
362
+ }
363
+
364
+ onEvidenceFileSelectType(event: any): void {
365
+ const files = Array.from(event.target.files) as File[];
366
+ if (this.evidencePanelCase && this.evidencePanelCase.caseId) {
367
+ this.evidenceFiles[this.evidencePanelCase.caseId][this.evidenceType].push(...files);
368
+ }
369
+ }
370
+
371
+ getEvidenceIcon(filename: string): string {
372
+ if (filename.match(/\.(jpg|jpeg|png|gif)$/i)) return 'fas fa-camera';
373
+ if (filename.match(/\.(mp4|avi|mov)$/i)) return 'fas fa-film';
374
+ if (filename.match(/\.(pdf)$/i)) return 'fas fa-file-pdf';
375
+ return 'fas fa-file';
376
+ }
377
+
378
+ viewEvidenceFile(file: File): void {
379
+ const url = URL.createObjectURL(file);
380
+ window.open(url, '_blank');
381
+ }
382
  }
src/app/case-store.service.ts CHANGED
@@ -1,8 +1,19 @@
1
  import { Injectable } from '@angular/core';
2
 
 
 
 
 
 
 
3
  export interface PoliceCase {
4
  // Optional metadata used by your UI
5
  caseId?: string;
 
 
 
 
 
6
  dateTime?: string;
7
  status?: 'Open' | 'Under Investigation' | 'Closed';
8
  crime: string;
@@ -22,6 +33,12 @@ export interface PoliceCase {
22
  address: string;
23
  occupation?: string;
24
  };
 
 
 
 
 
 
25
  }
26
 
27
  @Injectable({ providedIn: 'root' })
@@ -34,7 +51,21 @@ export class CaseStoreService {
34
  }
35
 
36
  /** Create (newest first) */
 
 
 
 
 
 
 
 
 
37
  addPoliceCase(c: PoliceCase): void {
 
 
 
 
 
38
  this.cases.unshift(c);
39
  this.save();
40
  }
@@ -47,11 +78,20 @@ export class CaseStoreService {
47
  /** Update by array index */
48
  updatePoliceCaseAt(index: number, updated: PoliceCase): void {
49
  if (index >= 0 && index < this.cases.length) {
 
50
  this.cases[index] = updated;
51
  this.save();
52
  }
53
  }
54
 
 
 
 
 
 
 
 
 
55
  /**
56
  * Convenience: map Info page reactive-form value to PoliceCase and store it.
57
  * Call with the whole this.form.value from InfopageComponent.
@@ -60,17 +100,23 @@ export class CaseStoreService {
60
  const crime = (formValue && formValue.crime) || {};
61
  const suspect = (formValue && formValue.suspect) || {};
62
  const notes = (formValue && formValue.notes) || {};
 
63
 
64
  const mapped: PoliceCase = {
65
  caseId: crime.caseId || '',
 
 
 
 
 
66
  dateTime: crime.dateTime || '',
67
  status: notes.status || 'Open',
68
  crime: crime.crimeType || 'Unknown',
69
  police: {
70
  name: notes.officerInCharge || '—',
71
- station: '—', // not captured on Info page
72
  address: crime.location || '—',
73
- pincode: '', // not captured on Info page
74
  dutyPerson: notes.officerInCharge || '—',
75
  modeOfCrime: crime.crimeType || '—',
76
  information: notes.initialFindings || ''
@@ -81,7 +127,17 @@ export class CaseStoreService {
81
  gender: suspect.gender || '—',
82
  address: suspect.address || '—',
83
  occupation: suspect.alias || ''
84
- }
 
 
 
 
 
 
 
 
 
 
85
  };
86
 
87
  this.addPoliceCase(mapped);
 
1
  import { Injectable } from '@angular/core';
2
 
3
+ export interface LegalInfo {
4
+ witnessStatements: string;
5
+ confessions: string;
6
+ evidence: File[]; // Assuming you're collecting file(s) as evidence
7
+ }
8
+
9
  export interface PoliceCase {
10
  // Optional metadata used by your UI
11
  caseId?: string;
12
+ firRef?: string;
13
+ occurredFrom?: string;
14
+ occurredTo?: string;
15
+ jurisdiction?: string;
16
+ sceneType?: string;
17
  dateTime?: string;
18
  status?: 'Open' | 'Under Investigation' | 'Closed';
19
  crime: string;
 
33
  address: string;
34
  occupation?: string;
35
  };
36
+ legal: LegalInfo; // Add the legal property here
37
+ victimName?: string;
38
+ caseCategory?: string;
39
+ reportedBy?: string;
40
+ lastUpdated?: string;
41
+ verifiedBy?: string;
42
  }
43
 
44
  @Injectable({ providedIn: 'root' })
 
51
  }
52
 
53
  /** Create (newest first) */
54
+ private getNextCaseId(): string {
55
+ const max = this.cases
56
+ .map(c => c.caseId)
57
+ .map(id => parseInt((id || '').replace('CASE-', ''), 10))
58
+ .filter(n => !isNaN(n))
59
+ .reduce((a, b) => Math.max(a, b), 0);
60
+ return `CASE-${(max + 1).toString().padStart(3, '0')}`;
61
+ }
62
+
63
  addPoliceCase(c: PoliceCase): void {
64
+ if (!c.caseId) {
65
+ c.caseId = this.getNextCaseId();
66
+ }
67
+ c.lastUpdated = new Date().toISOString();
68
+ c.verifiedBy = c.verifiedBy || '';
69
  this.cases.unshift(c);
70
  this.save();
71
  }
 
78
  /** Update by array index */
79
  updatePoliceCaseAt(index: number, updated: PoliceCase): void {
80
  if (index >= 0 && index < this.cases.length) {
81
+ updated.lastUpdated = new Date().toISOString();
82
  this.cases[index] = updated;
83
  this.save();
84
  }
85
  }
86
 
87
+ /** Delete by array index */
88
+ deletePoliceCaseAt(index: number): void {
89
+ if (index >= 0 && index < this.cases.length) {
90
+ this.cases.splice(index, 1);
91
+ this.save();
92
+ }
93
+ }
94
+
95
  /**
96
  * Convenience: map Info page reactive-form value to PoliceCase and store it.
97
  * Call with the whole this.form.value from InfopageComponent.
 
100
  const crime = (formValue && formValue.crime) || {};
101
  const suspect = (formValue && formValue.suspect) || {};
102
  const notes = (formValue && formValue.notes) || {};
103
+ const legal = (formValue && formValue.legal) || {};
104
 
105
  const mapped: PoliceCase = {
106
  caseId: crime.caseId || '',
107
+ firRef: crime['FIR / Ref #'] || formValue['FIR / Ref #'] || '',
108
+ occurredFrom: crime['Occurred From'] || formValue['Occurred From'] || '',
109
+ occurredTo: crime['Occurred To'] || formValue['Occurred To'] || '',
110
+ jurisdiction: crime['Jurisdiction / PS'] || formValue['Jurisdiction / PS'] || '',
111
+ sceneType: crime['Scene Type'] || formValue['Scene Type'] || '',
112
  dateTime: crime.dateTime || '',
113
  status: notes.status || 'Open',
114
  crime: crime.crimeType || 'Unknown',
115
  police: {
116
  name: notes.officerInCharge || '—',
117
+ station: '—',
118
  address: crime.location || '—',
119
+ pincode: '',
120
  dutyPerson: notes.officerInCharge || '—',
121
  modeOfCrime: crime.crimeType || '—',
122
  information: notes.initialFindings || ''
 
127
  gender: suspect.gender || '—',
128
  address: suspect.address || '—',
129
  occupation: suspect.alias || ''
130
+ },
131
+ legal: {
132
+ witnessStatements: legal.witnessStatements || '',
133
+ confessions: legal.confessions || '',
134
+ evidence: legal.evidence || []
135
+ },
136
+ victimName: crime.victimName || formValue['Victim Name'] || '',
137
+ caseCategory: crime.caseCategory || formValue['Case Category'] || '',
138
+ reportedBy: crime.reportedBy || formValue['Reported By'] || '',
139
+ lastUpdated: new Date().toISOString(),
140
+ verifiedBy: formValue.verifiedBy || '',
141
  };
142
 
143
  this.addPoliceCase(mapped);
src/app/homepage/homepage.component.css CHANGED
@@ -2,8 +2,7 @@ body, html {
2
  margin: 0;
3
  padding: 0;
4
  font-family: 'Arial', 'Segoe UI', 'Roboto', sans-serif;
5
- background: url('/assets/background.jpg') no-repeat center center fixed !important;
6
- background-size: cover !important;
7
  }
8
 
9
  .homepage-container {
@@ -65,14 +64,19 @@ body, html {
65
  /* Add gap between logo and browser border */
66
  .logo-img {
67
  width: 6vw;
68
- height: 6vw;
69
- border-radius: 50%;
70
- box-shadow: 0 0 15px rgba(255, 255, 255, 0.8);
71
  position: fixed;
72
- top: 18px; /* Add gap from top */
73
- left: 18px; /* Add gap from left */
74
  z-index: 300;
75
  margin: 0;
 
 
 
 
 
 
 
 
76
  }
77
 
78
  /* Move Py-Detect text further down by increasing top value of .logo-title-row */
@@ -91,10 +95,10 @@ body, html {
91
 
92
  .py-detect-title {
93
  position: fixed;
94
- margin-left: 132px;
95
- margin-top: -208px;
96
  text-align: left;
97
- font-size: 4vw;
98
  color: #38bdf8;
99
  font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
100
  font-weight: 900;
@@ -125,6 +129,18 @@ body, html {
125
  border: 2px solid #23272b;
126
  }
127
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  .py-detect-title .py-letter.d {
129
  color: #e3f6ff;
130
  text-shadow: 0 0 6px #38bdf8;
@@ -212,6 +228,49 @@ body, html {
212
  height: 100vh;
213
  }
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  .sign-in-form {
216
  background: rgba(30,41,59,0.92);
217
  padding: 32px 48px 32px 64px; /* Reduced vertical and horizontal padding */
@@ -535,469 +594,141 @@ label {
535
  box-shadow: 0 8px 24px #38bdf888;
536
  }
537
 
538
- /* Keep app-sign-in/up wrapper */
539
- .app-sign-in, .app-sign-up {
540
- position: relative;
541
- background: #fff;
542
- border-radius: 14px;
543
- box-shadow: 0 20px 60px rgba(0,0,0,.35);
544
- }
545
-
546
- .py-shape {
547
- display: inline-block;
548
- width: 18px;
549
- height: 4px;
550
- background: #38bdf8;
551
- margin: 0 8px;
552
- vertical-align: middle;
553
- border-radius: 2px;
554
- box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff;
555
- border: 2px solid #23272b;
556
- }
557
-
558
- .auth-topright {
559
- position: fixed;
560
- top: 32px;
561
- right: 48px;
562
- z-index: 200;
563
- display: flex;
564
- gap: 18px;
565
- }
566
-
567
- .auth-btn {
568
- font-family: 'Montserrat', 'Poppins', 'Arial Black', Arial, sans-serif;
569
- font-size: 1.1rem;
570
- font-weight: 700;
571
- letter-spacing: 2px;
572
- background: linear-gradient(90deg, #38bdf8 0%, #23272b 100%);
573
- color: #e3f6ff;
574
- box-shadow: 0 2px 16px #38bdf888;
575
- border: none;
576
- border-radius: 12px;
577
- padding: 0.7rem 2rem;
578
- cursor: pointer;
579
- transition: background 0.3s, color 0.3s, box-shadow 0.3s;
580
- }
581
-
582
- .auth-btn:hover {
583
- background: linear-gradient(90deg, #23272b 0%, #38bdf8 100%);
584
- color: #bae6fd;
585
- box-shadow: 0 2px 24px #bae6fd88;
586
- }
587
 
588
- .py-detect-image {
589
- width: 420px;
590
- max-width: 100%;
591
- height: auto;
592
- border-radius: 24px;
593
- box-shadow: 0 8px 32px #38bdf844, 0 0 24px #1e293b88;
594
- border: 3px solid #38bdf8;
595
- background: #23272b;
596
- margin-top: 48px;
597
- margin-bottom: 48px;
598
- display: block;
599
- }
600
 
601
- /* Footer Styles */
602
- .footer {
603
- background: linear-gradient(90deg, #18181b 0%, #38bdf8 100%); /* Dual color: black to cyan */
604
- color: #fff; /* All text white */
605
- padding: 0; /* Remove extra padding for a slim footer */
606
- text-align: center;
607
- width: 100%;
608
- position: fixed;
609
- bottom: 0;
610
- left: 0;
611
- z-index: 200;
612
- height: 46px; /* Further reduced height for a slim footer */
613
- display: flex;
614
- align-items: center;
615
- justify-content: center;
616
  }
617
 
618
- .footer-content {
 
619
  display: flex;
620
- flex-direction: row;
621
  justify-content: space-between;
622
  align-items: center;
623
- width: 100%;
624
- max-width: 1200px;
625
- margin: 0 auto;
626
- padding: 0 32px;
627
- gap: 64px;
628
- }
629
-
630
- .footer-left,
631
- .footer-center,
632
- .footer-right {
633
- margin: 0;
634
- min-width: 0;
635
- flex: 1 1 1;
636
- display: flex;
637
- align-items: center;
638
- color: #fff !important;
639
- justify-content: center;
640
- }
641
-
642
- .footer-center,
643
- .footer-right {
644
- color: #fff !important;
645
- }
646
-
647
- .footer-center .footer-links {
648
- display: flex;
649
- flex-direction: row;
650
- gap: 32px;
651
- align-items: center;
652
- margin: 0;
653
- padding: 0;
654
- flex-wrap: nowrap;
655
- white-space: nowrap;
656
- }
657
-
658
- .footer-center .footer-links li {
659
- display: flex;
660
- align-items: center;
661
- margin: 0;
662
- }
663
-
664
- .footer-right .social-media {
665
- display: flex;
666
- flex-direction: row;
667
- gap: 18px;
668
- align-items: center;
669
- margin-left: 24px;
670
- }
671
-
672
- .footer-center .footer-links a {
673
- color: #fff !important;
674
- text-decoration: none !important;
675
- }
676
-
677
- .footer-center .footer-links a:hover {
678
- color: #38bdf8 !important;
679
- text-decoration: none !important;
680
- }
681
-
682
- .footer-right .social-media a,
683
- .footer-right .social-media i {
684
- color: #fff !important;
685
- text-decoration: none !important;
686
- }
687
-
688
- @media screen and (max-width: 900px) {
689
- .footer-content {
690
- flex-direction: column;
691
- gap: 16px;
692
- padding: 8px 8px;
693
- }
694
-
695
- .footer-left,
696
- .footer-center,
697
- .footer-right {
698
- justify-content: center;
699
- width: 100%;
700
- margin: 0;
701
- }
702
-
703
- .footer-center .footer-links {
704
- flex-wrap: wrap;
705
- justify-content: center;
706
- gap: 12px;
707
- white-space: normal;
708
- }
709
-
710
- .footer-right .social-media {
711
- margin-left: 0;
712
- justify-content: center;
713
- }
714
- }
715
-
716
- /* Align detection info boxes to start from left, with space at right */
717
- .detection-info-row {
718
- display: flex;
719
- flex-direction: row;
720
- flex-wrap: nowrap;
721
- gap: 24px;
722
- justify-content: flex-start;
723
- align-items: stretch;
724
- margin: 0 0 0 48px; /* Add left margin, remove auto centering */
725
- width: auto;
726
- max-width: 1200px;
727
- }
728
-
729
- .detection-info-box {
730
- background: rgba(30,41,59,0.92);
731
- border-radius: 16px;
732
- box-shadow: 0 2px 16px #38bdf844, 0 0 12px #1e293b88;
733
- border-left: 6px solid #38bdf8;
734
- border-top: 2px solid #1e293b;
735
- padding: 18px 24px 16px 24px;
736
- min-width: 220px;
737
- max-width: 280px;
738
- flex: 1 1 0;
739
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
740
- color: #e3f6ff;
741
- font-size: 22px;
742
- font-weight: 400;
743
- line-height: 1.5;
744
- letter-spacing: 0.5px;
745
- position: relative;
746
- margin-bottom: 0;
747
- height: 100%;
748
- display: flex;
749
- flex-direction: column;
750
- justify-content: flex-start;
751
- opacity: 0;
752
- transform: translateY(40px) scale(0.98);
753
- animation: detectionBoxFadeIn 0.7s cubic-bezier(0.23, 1, 0.32, 1) forwards;
754
- transition: transform 0.25s cubic-bezier(0.23, 1, 0.32, 1), box-shadow 0.25s;
755
- }
756
-
757
- .detection-info-box:hover {
758
- transform: scale(1.06);
759
- box-shadow: 0 8px 32px #38bdf888, 0 0 24px #1e293b88;
760
- z-index: 10;
761
- }
762
-
763
- .detection-info-box:nth-child(2) {
764
- animation-delay: 0.15s;
765
- }
766
-
767
- .detection-info-box:nth-child(3) {
768
- animation-delay: 0.3s;
769
- }
770
-
771
- .detection-info-box:nth-child(4) {
772
- animation-delay: 0.45s;
773
- }
774
-
775
- @keyframes detectionBoxFadeIn {
776
- 0% {
777
- opacity: 0;
778
- transform: translateY(40px) scale(0.98);
779
- }
780
-
781
- 80% {
782
- opacity: 1;
783
- transform: translateY(-4px) scale(1.02);
784
- }
785
-
786
- 100% {
787
- opacity: 1;
788
- transform: translateY(0) scale(1);
789
- }
790
- }
791
-
792
- @media screen and (max-width: 1200px) {
793
- .detection-info-row {
794
- flex-wrap: wrap;
795
- justify-content: center;
796
- }
797
-
798
- .detection-info-box {
799
- max-width: 98vw;
800
- min-width: 0;
801
- font-size: 15px;
802
- padding: 14px 6vw;
803
- }
804
- }
805
-
806
- @media screen and (max-width: 900px) {
807
- .detection-info-row {
808
- flex-direction: column;
809
- gap: 18px;
810
- align-items: center;
811
- }
812
-
813
- .detection-info-box {
814
- max-width: 98vw;
815
- min-width: 0;
816
- font-size: 15px;
817
- padding: 14px 6vw;
818
- height: auto;
819
- }
820
- }
821
-
822
- /* Footer social icon styles */
823
- .footer-social-icon {
824
- width: 32px;
825
- height: 32px;
826
- margin: 0 8px;
827
- vertical-align: middle;
828
- filter: drop-shadow(0 0 6px #38bdf8);
829
- transition: transform 0.2s, filter 0.2s;
830
- }
831
-
832
- .footer-social-icon:hover {
833
- transform: scale(1.15);
834
- filter: drop-shadow(0 0 12px #bae6fd);
835
- }
836
-
837
- /* Style for Next button in footer */
838
- .footer-next-btn {
839
- margin-left: 24px;
840
- padding: 12px 32px;
841
- font-size: 1.1rem;
842
- font-weight: 600;
843
- border-radius: 10px;
844
- background: linear-gradient(90deg, #38bdf8 0%, #23272b 100%);
845
- color: #e3f6ff;
846
- border: none;
847
- box-shadow: 0 2px 12px #38bdf888;
848
- cursor: pointer;
849
- transition: background 0.3s, color 0.3s, box-shadow 0.3s;
850
- }
851
-
852
- .footer-next-btn:hover {
853
- background: linear-gradient(90deg, #23272b 0%, #38bdf8 100%);
854
- color: #bae6fd;
855
- box-shadow: 0 2px 24px #bae6fd88;
856
- }
857
-
858
- .tour-btn {
859
- background: linear-gradient(90deg, #38bdf8 0%, #712d74 100%);
860
  color: #fff;
861
- font-size: 1.2rem;
862
- font-weight: 700;
863
  border: none;
864
- border-radius: 12px;
865
- padding: 14px 38px;
866
- box-shadow: 0 2px 16px #38bdf888;
867
  cursor: pointer;
868
- margin: 0 auto;
869
- display: block;
870
- transition: background 0.3s, color 0.3s, box-shadow 0.3s;
871
- }
872
-
873
- .tour-btn:hover {
874
- background: linear-gradient(90deg, #712d74 0%, #38bdf8 100%);
875
- color: #bae6fd;
876
- box-shadow: 0 2px 24px #bae6fd88;
877
- }
878
-
879
- .tour-overlay {
880
- position: fixed;
881
- inset: 0;
882
- background: rgba(24,24,27,0.92);
883
- z-index: 2000;
884
- display: flex;
885
- align-items: center;
886
- justify-content: center;
887
  }
 
888
 
889
- .tour-content {
890
- background: #23272b;
891
- color: #fff;
892
- border-radius: 18px;
893
- padding: 48px 36px 36px 36px;
894
- box-shadow: 0 8px 32px #38bdf844, 0 0 24px #1e293b88;
895
- max-width: 480px;
896
- width: 90vw;
897
  text-align: center;
898
- position: relative;
899
- }
900
-
901
- .tour-close {
902
- position: absolute;
903
- top: 18px;
904
- right: 18px;
905
- width: 44px;
906
- height: 44px;
907
- border: none;
908
- background: #18181b;
909
- color: #fff;
910
- border-radius: 50%;
911
- cursor: pointer;
912
- font-size: 2rem;
913
- font-weight: bold;
914
  display: flex;
915
- align-items: center;
916
  justify-content: center;
917
- box-shadow: 0 2px 8px rgba(0,0,0,0.18);
918
- z-index: 10;
919
- transition: background 0.2s, color 0.2s;
920
- }
921
-
922
- .tour-close:hover {
923
- background: #333;
924
- color: #f134d0;
925
- }
926
-
927
- .tour-next {
928
- margin-top: 24px;
929
- background: linear-gradient(90deg, #38bdf8 0%, #712d74 100%);
930
- color: #fff;
931
- font-size: 1rem;
932
- font-weight: 600;
933
- border: none;
934
- border-radius: 10px;
935
- padding: 12px 32px;
936
- box-shadow: 0 2px 12px #38bdf888;
937
- cursor: pointer;
938
- transition: background 0.3s, color 0.3s, box-shadow 0.3s;
939
- }
940
-
941
- .tour-next:hover {
942
- background: linear-gradient(90deg, #712d74 0%, #38bdf8 100%);
943
- color: #bae6fd;
944
- box-shadow: 0 2px 24px #bae6fd88;
945
- }
946
-
947
- .info-link {
948
- margin-left: 12px;
949
- color: #38bdf8;
950
- font-size: 0.95em;
951
- cursor: pointer;
952
- text-decoration: underline;
953
- font-weight: 500;
954
- transition: color 0.2s;
955
- }
956
-
957
- .info-link:hover {
958
- color: #bae6fd;
959
- }
960
-
961
- .detection-info-box .detection-description,
962
- .detection-info-box .detection-list {
963
- margin-top: 10px;
964
- }
965
-
966
- .detection-icon {
967
- font-size: 3vw;
968
- }
969
-
970
- .detection-info-box .detection-icon {
971
- font-size: 3vw !important;
972
  }
973
 
974
- .welcome-title {
975
- margin-left: 12px;
976
- margin-bottom: 8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
977
  }
978
 
979
- .main-title {
980
- font-size: 2.2em;
981
- font-weight: bold;
982
- background: linear-gradient(90deg, #f134d0 0%, #38bdf8 100%);
983
- -webkit-background-clip: text;
984
- -webkit-text-fill-color: transparent;
985
- background-clip: text;
986
- text-fill-color: transparent;
987
- letter-spacing: 2px;
988
  }
989
 
990
- .sub-title {
991
- font-size: 1.2em;
992
- font-weight: bold;
993
- color: #fff;
994
  }
995
 
996
- .detection-description {
997
- margin: 12px 0 18px 0;
998
- color: #e3f6ff;
999
- font-size: 1em;
1000
- font-weight: 400;
1001
  }
1002
 
1003
  .read-more-btn {
@@ -1116,3 +847,130 @@ label {
1116
  background: #333;
1117
  color: #38bdf8;
1118
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
 
8
  .homepage-container {
 
64
  /* Add gap between logo and browser border */
65
  .logo-img {
66
  width: 6vw;
 
 
 
67
  position: fixed;
68
+ top: 21px;
69
+ left: 36px;
70
  z-index: 300;
71
  margin: 0;
72
+ background: #ffffff;
73
+ max-width: 4.2vw;
74
+ min-width: 56px;
75
+ height: auto;
76
+ border-radius: 50%;
77
+ padding: 4px;
78
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25);
79
+ transition: transform .25s ease;
80
  }
81
 
82
  /* Move Py-Detect text further down by increasing top value of .logo-title-row */
 
95
 
96
  .py-detect-title {
97
  position: fixed;
98
+ margin-left: 97px;
99
+ margin-top: -228px;
100
  text-align: left;
101
+ font-size: 3vw;
102
  color: #38bdf8;
103
  font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
104
  font-weight: 900;
 
129
  border: 2px solid #23272b;
130
  }
131
 
132
+ .py-shape {
133
+ display: inline-block;
134
+ width: 18px;
135
+ height: 4px;
136
+ background: #38bdf8;
137
+ margin: 0 8px;
138
+ vertical-align: middle;
139
+ border-radius: 2px;
140
+ box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff;
141
+ border: 2px solid #23272b;
142
+ }
143
+
144
  .py-detect-title .py-letter.d {
145
  color: #e3f6ff;
146
  text-shadow: 0 0 6px #38bdf8;
 
228
  height: 100vh;
229
  }
230
 
231
+ .auth-topright {
232
+ position: fixed;
233
+ top: 46px;
234
+ right: 48px;
235
+ z-index: 200;
236
+ display: flex;
237
+ gap: 18px;
238
+ }
239
+
240
+ .auth-btn {
241
+ font-family: 'Montserrat', 'Poppins', 'Arial Black', Arial, sans-serif;
242
+ font-size: 1.1rem;
243
+ font-weight: 700;
244
+ letter-spacing: 2px;
245
+ background: linear-gradient(90deg, #38bdf8 0%, #23272b 100%);
246
+ color: #e3f6ff;
247
+ box-shadow: 0 2px 16px #38bdf888;
248
+ border: none;
249
+ border-radius: 12px;
250
+ padding: 0.7rem 2rem;
251
+ cursor: pointer;
252
+ transition: background 0.3s, color 0.3s, box-shadow 0.3s;
253
+ }
254
+
255
+ .auth-btn:hover {
256
+ background: linear-gradient(90deg, #23272b 0%, #38bdf8 100%);
257
+ color: #bae6fd;
258
+ box-shadow: 0 2px 24px #bae6fd88;
259
+ }
260
+
261
+ .py-detect-image {
262
+ width: 420px;
263
+ max-width: 100%;
264
+ height: auto;
265
+ border-radius: 24px;
266
+ box-shadow: 0 8px 32px #38bdf844, 0 0 24px #1e293b88;
267
+ border: 3px solid #38bdf8;
268
+ background: #23272b;
269
+ margin-top: 48px;
270
+ margin-bottom: 48px;
271
+ display: block;
272
+ }
273
+
274
  .sign-in-form {
275
  background: rgba(30,41,59,0.92);
276
  padding: 32px 48px 32px 64px; /* Reduced vertical and horizontal padding */
 
594
  box-shadow: 0 8px 24px #38bdf888;
595
  }
596
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
 
598
+ /* Preserve existing header logo/title and auth buttons styles from before (no change) */
 
 
 
 
 
 
 
 
 
 
 
599
 
600
+ /* New Landing styles (scoped to homepage to avoid affecting auth pages) */
601
+ .landing {
602
+ min-height: 100vh;
603
+ /* Use global body.homepage-bg background; remove local hero override */
604
+ background: transparent;
605
+ color: #fff;
 
 
 
 
 
 
 
 
 
606
  }
607
 
608
+ /* Simple navbar that does not override existing top-right auth */
609
+ .navbar {
610
  display: flex;
 
611
  justify-content: space-between;
612
  align-items: center;
613
+ padding: 15px 30px;
614
+ }
615
+ .navbar h1 { font-size: 22px; letter-spacing: 2px; margin: 0; }
616
+ .nav-right { display: flex; align-items: center; gap: 15px; }
617
+
618
+ /* Dropdown */
619
+ .dropdown { position: relative; display: inline-block; }
620
+ .dropbtn { background-color: #222; color: #fff; padding: 10px 16px; border: none; cursor: pointer; border-radius: 4px; }
621
+ .dropdown-content { display: none; position: absolute; right: 0; background: #333; min-width: 180px; box-shadow: 0 8px 16px rgba(0,0,0,.3); border-radius: 4px; z-index: 1; }
622
+ .dropdown-content a { color: #fff; padding: 12px 16px; text-decoration: none; display: block; }
623
+ .dropdown-content a:hover { background: #444; }
624
+ .dropdown:hover .dropdown-content { display: block; }
625
+
626
+ /* Buttons */
627
+ .btn {
628
+ background: #c62828;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
629
  color: #fff;
 
 
630
  border: none;
631
+ padding: 10px 16px;
632
+ border-radius: 4px;
 
633
  cursor: pointer;
634
+ font-weight: bold;
635
+ text-decoration: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
636
  }
637
+ .btn:hover { background: #e53935; }
638
 
639
+ /* Hero */
640
+ .hero {
 
 
 
 
 
 
641
  text-align: center;
642
+ min-height: 31vh;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
643
  display: flex;
644
+ flex-direction: column;
645
  justify-content: center;
646
+ align-items: center;
647
+ padding: 2vw;
648
+ position: fixed;
649
+ top: 37vh;
650
+ left: calc(100% - 68vw);
651
+ width: 32vw;
652
+ text-wrap: auto;
653
+ font-weight: 800;
654
+ /*background: #011329d6;*/
655
+ border-radius: 4vw;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
  }
657
 
658
+ .blue-section, .how-to-use, .features {
659
+ position: relative;
660
+ z-index: 1;
661
+ }
662
+
663
+
664
+ /* Specialisations */
665
+ .specialisations { padding: 25px 10%; text-align: center; background: linear-gradient(to right, #011329 0%, #010207 23%, #71a4c2 23%, #0171a3 77%, #010207 77%, #011329 100%); }
666
+ .specialisation-cards { display: flex; justify-content: space-between; gap: 30px; flex-wrap: wrap; }
667
+ .card { background: #fff; padding: 40px 30px; flex: 1 1 350px; max-width: 400px; box-shadow: 0 4px 12px rgba(0,0,0,.1); transition: transform .3s; }
668
+ .card:hover { transform: translateY(-8px); }
669
+ .card .icon { font-size: 48px; margin-bottom: 20px; color: #c62828; }
670
+ .card h3 { font-size: 20px; margin-bottom: 15px; color: #c62828; }
671
+ .card p { font-size: 16px; line-height: 1.6; color: #000; }
672
+
673
+ /* Mission */
674
+ .mission { background: transparent; color: #fff; padding: 60px 10%; text-align: center; }
675
+ .mission-row { display: flex; justify-content: space-between; gap: 30px; margin-top: 20px; margin-left: 7vw; flex-wrap: wrap; }
676
+ .mission-left, .mission-right { flex: 1; min-width: 300px; text-align: left; font-size: 16px; line-height: 1.6; color: #f0f4f8; }
677
+
678
+ /* Features */
679
+ .features { padding: 60px 10%; background: #011329; color: #010207; }
680
+ .feature-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 60px; background: #f9f9f9; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,.1); padding: 30px; gap: 20px; flex-wrap: wrap; }
681
+ .feature-row.reverse { flex-direction: row-reverse; }
682
+ .feature-text { flex: 1 1 320px; padding: 20px; }
683
+ .feature-text h2 { color: #c62828; margin-bottom: 15px; }
684
+ .feature-text p { font-size: 20px; line-height: 1.6; }
685
+ .feature-image { flex: 1 1 320px; padding: 20px; }
686
+ .feature-image img { width: 100%; border-radius: 10px; box-shadow: 0 5px 15px rgba(0,0,0,.2); }
687
+
688
+ /* Footer */
689
+ footer { background: linear-gradient(to right, #011022, #01030a); color: #fff; text-align: center; padding: 15px 10px; }
690
+ footer a { color: #fff; text-decoration: none; margin: 0 10px; }
691
+ footer .social-icons { margin-top:12px; display:flex; justify-content:center; gap:28px; flex-wrap:wrap; }
692
+ footer .social-icons .si-wrap { display:flex; align-items:center; gap:10px; font-size:15px; font-weight:600; letter-spacing:.5px; font-family:'Segoe UI', Arial, sans-serif; color:#cfe9f7; }
693
+ footer .social-icons .si-wrap .si-text { transition:color .25s; }
694
+ footer .social-icons .si { width:42px; height:42px; display:inline-flex; align-items:center; justify-content:center; border-radius:50%; background:#122434; color:#38bdf8; font-size:18px; box-shadow:0 0 0 1px #214055, 0 4px 14px #0006; transition:background .25s, color .25s, transform .25s, box-shadow .25s; text-decoration:none; }
695
+ footer .social-icons .si:hover { background:#38bdf8; color:#0c2533; transform:translateY(-4px); box-shadow:0 6px 20px #38bdf8aa,0 0 0 2px #38bdf8 inset; }
696
+ footer .social-icons .si-wrap:hover .si-text { color:#ffffff; }
697
+ footer .social-icons .si.fb { background:#1877f2; color:#fff; }
698
+ footer .social-icons .si.fb:hover { background:#fff; color:#1877f2; }
699
+ footer .social-icons .si.yt { background:#ff0000; color:#fff; }
700
+ footer .social-icons .si.yt:hover { background:#fff; color:#ff0000; }
701
+ footer .social-icons .si.li { background:#0a66c2; color:#fff; }
702
+ footer .social-icons .si.li:hover { background:#fff; color:#0a66c2; }
703
+ footer .social-icons .si.ig { background:radial-gradient(circle at 30% 110%,#fdf497 0%,#fd5949 45%,#d6249f 60%,#285AEB 90%); color:#fff; }
704
+ footer .social-icons .si.ig:hover { filter:brightness(1.15); color:#fff; box-shadow:0 6px 22px rgba(253,89,73,.6); }
705
+
706
+ /* Keep existing modal styling (unchanged) */
707
+ .modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 1000; }
708
+ .modal { position: fixed; inset: 0; display: grid; place-items: center; padding: 24px; z-index: 1001; background: #ffffff45; }
709
+
710
+ /* Ensure info dialog is a real centered popup overlay */
711
+ .dialog-modal {
712
+ position: fixed;
713
+ inset: 0;
714
+ display: grid;
715
+ place-items: center;
716
+ z-index: 2001; /* above backdrop and page */
717
+ pointer-events: auto;
718
  }
719
 
720
+ .modal-backdrop--fade {
721
+ animation: fadeInBackdrop .25s ease-out both;
 
 
 
 
 
 
 
722
  }
723
 
724
+ .dialog-modal--zoom {
725
+ animation: zoomInModal .28s cubic-bezier(0.23, 1, 0.32, 1) both;
 
 
726
  }
727
 
728
+ /* Dialog card sizing */
729
+ .dialog-content {
730
+ max-width: 720px;
731
+ width: min(92vw, 720px);
 
732
  }
733
 
734
  .read-more-btn {
 
847
  background: #333;
848
  color: #38bdf8;
849
  }
850
+
851
+ .back-colo {
852
+ background: #011329;
853
+ width: 100%;
854
+ height: 7vw;
855
+ position: fixed;
856
+ z-index: 3;
857
+ }
858
+
859
+ /* how to use */
860
+ .how-to-use {
861
+ padding: 80px 10%;
862
+ text-align: center;
863
+ background: linear-gradient(to right, #011329 0%, #010207 23%, #01040b 23%, #01030a 77%, #010207 77%, #011329 100%);
864
+ color: #fff;
865
+ }
866
+
867
+
868
+ .how-to-use .section-title {
869
+ font-size: 28px;
870
+ color: #0d9de3; /* blue highlight */
871
+ margin-bottom: 10px;
872
+ }
873
+
874
+ .how-to-use .section-subtitle {
875
+ font-size: 16px;
876
+ color: #f0f4f8;
877
+ margin-bottom: 40px;
878
+ }
879
+
880
+ .use-cards {
881
+ display: flex;
882
+ justify-content: space-between;
883
+ flex-wrap: wrap;
884
+ gap: 25px;
885
+ }
886
+
887
+ .use-card {
888
+ background: #fff;
889
+ color: #000;
890
+ flex: 1 1 calc(20% - 20px); /* 5 cards in a row */
891
+ min-width: 220px;
892
+ border-radius: 12px;
893
+ padding: 25px 20px;
894
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
895
+ transition: transform 0.3s;
896
+ }
897
+
898
+ .use-card:hover {
899
+ transform: translateY(-6px);
900
+ }
901
+
902
+ .use-card .icon {
903
+ font-size: 40px;
904
+ margin-bottom: 15px;
905
+ color: #3328c6; /* red accent */
906
+ }
907
+
908
+ .use-card h3 {
909
+ font-size: 18px;
910
+ margin-bottom: 10px;
911
+ color: #3328c6;
912
+ }
913
+
914
+ .use-card p {
915
+ font-size: 16px;
916
+ line-height: 1.5;
917
+ color: #000;
918
+ }
919
+
920
+ .social-icons {
921
+ display: flex;
922
+ justify-content: center;
923
+ gap: 15px;
924
+ margin-top: 10px;
925
+ }
926
+
927
+ .social-icon {
928
+ display: inline-flex;
929
+ align-items: center;
930
+ justify-content: center;
931
+ width: 36px;
932
+ height: 36px;
933
+ border-radius: 50%;
934
+ background: rgba(255, 255, 255, 0.1);
935
+ color: white;
936
+ text-decoration: none;
937
+ font-size: 18px;
938
+ transition: all 0.3s ease;
939
+ border: 1px solid rgba(255, 255, 255, 0.2);
940
+ }
941
+
942
+ footer .social-icons {
943
+ margin-top: 10px;
944
+ }
945
+
946
+ footer .social-icons img {
947
+ width: 25px;
948
+ margin: 0 8px;
949
+ vertical-align: middle;
950
+ }
951
+
952
+ .social-icon:hover {
953
+ transform: translateY(-3px);
954
+ background: rgba(255, 255, 255, 0.2);
955
+ }
956
+
957
+ /* Specific colors for each platform */
958
+ .social-icon.facebook:hover {
959
+ background: #3b5998;
960
+ box-shadow: 0 4px 8px rgba(59, 89, 152, 0.3);
961
+ }
962
+
963
+ .social-icon.youtube:hover {
964
+ background: #ff0000;
965
+ box-shadow: 0 4px 8px rgba(255, 0, 0, 0.3);
966
+ }
967
+
968
+ .social-icon.linkedin:hover {
969
+ background: #0077b5;
970
+ box-shadow: 0 4px 8px rgba(0, 119, 181, 0.3);
971
+ }
972
+
973
+ .social-icon.instagram:hover {
974
+ background: linear-gradient(45deg, #f09433, #e6683c, #dc2743, #cc2366, #bc1888);
975
+ box-shadow: 0 4px 8px rgba(188, 24, 136, 0.3);
976
+ }
src/app/homepage/homepage.component.html CHANGED
@@ -1,4 +1,5 @@
1
  <!-- Logo and Py-Detect Title (kept as your last version) -->
 
2
  <div class="logo-title-row">
3
  <img src="/assets/pykara-logo.png" alt="PyDetect Logo" class="logo-img" />
4
  <div class="py-detect-title">
@@ -13,100 +14,240 @@
13
  <span class="py-letter t2">T</span>
14
  </div>
15
  </div>
 
 
 
 
 
 
 
16
 
17
- <div class="homepage-container">
18
- <!-- Top-right: Sign-In/Sign-Up buttons (reverted; unchanged from your last version) -->
19
- <div class="auth-topright">
20
- <button class="auth-btn" (click)="openSignIn()">Sign In</button>
21
- <button class="auth-btn" (click)="openSignUp()">Sign Up</button>
 
 
 
 
 
22
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
- <!-- Left-side instruction/info (position unchanged) -->
25
- <div class="left-instructions"
26
- style="position: absolute; left: 0; width: 50vw; flex-direction: column; justify-content: center; align-items: flex-start; padding-left: 80px; z-index: 10;">
27
- <div style="max-width: 520px;">
28
- <h1 style="font-size:2.5em; font-weight:800; color:#38bdf8; margin-bottom:12px;">
29
- Welcome to Py-Detect
30
- </h1>
31
- <div style="font-size: 1.2em; color: #e3f6ff; margin-bottom: 18px; width: 33vw; text-align: justify;">
32
- {{ welcomeText }}
33
  </div>
34
- <!-- Know More as a styled button -->
35
- <button class="know-more-btn" (click)="openInfoDialog()"
36
- style="font-size:1.1em; font-weight:600; color:#fff;
37
- background:linear-gradient(90deg,#38bdf8 0%,#23272b 100%);
38
- border:none; border-radius:22px; padding:10px 32px;
39
- box-shadow:0 2px 12px #38bdf888; cursor:pointer;
40
- transition:background 0.3s, color 0.3s, box-shadow 0.3s;
41
- margin-top:8px; letter-spacing:1px;">
42
- Know more
43
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  </div>
45
  </div>
 
46
 
47
- <!-- Optional tour overlay (unchanged) -->
48
- <div class="tour-overlay" *ngIf="showTourOverlay">
49
- <div class="tour-content">
50
- <button class="tour__close" (click)="showTourOverlay = false" aria-label="Close">×</button>
51
- <h2>Welcome to Py-Detect!</h2>
52
- <p>Discover what Py-Detect can do for you:</p>
53
- <ul>
54
- <li>🕵️ Intelligent lie detection platform</li>
55
- <li>🔍 AI-powered analysis of responses</li>
56
- <li>🎯 Designed for investigators, educators, HR, and more</li>
57
- <li>💡 Fast, non-invasive, and data-driven insights</li>
58
- </ul>
59
- <button class="tour-next" (click)="showTourOverlay = false">Got it!</button>
 
 
60
  </div>
61
  </div>
62
- </div>
63
 
64
- <!-- Footer (unchanged) -->
65
- <footer class="footer">
66
- <div class="footer-content">
67
- <div class="footer-left">
68
- <p>© 2025 Pykara Technologies. All rights reserved.</p>
69
  </div>
70
- <div class="footer-center">
71
- <ul class="footer-links">
72
- <li><a href="/about">About Us</a></li>
73
- <li><a href="/contact">Contact</a></li>
74
- <li><a href="/privacy">Privacy Policy</a></li>
75
- <li><a href="/terms">Terms & Conditions</a></li>
76
- </ul>
77
  </div>
78
- <div class="footer-right">
79
- <div class="social-media">
80
- <a href="https://facebook.com" target="_blank" aria-label="Facebook"><i class="fa-brands fa-facebook-f"></i></a>
81
- <a href="https://twitter.com" target="_blank" aria-label="Twitter"><i class="fa-brands fa-twitter"></i></a>
82
- <a href="https://linkedin.com" target="_blank" aria-label="LinkedIn"><i class="fa-brands fa-linkedin-in"></i></a>
83
- <a href="https://instagram.com" target="_blank" aria-label="Instagram"><i class="fa-brands fa-instagram"></i></a>
84
- </div>
 
 
 
 
 
 
 
 
 
 
85
  </div>
86
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  </footer>
88
 
89
  <!-- ===== Auth Modals (unchanged) ===== -->
90
  <div class="modal-backdrop" *ngIf="showSignIn || showSignUp" (click)="closeModal()"></div>
91
  <div class="modal" *ngIf="showSignIn || showSignUp" role="dialog" aria-modal="true">
92
  <ng-container *ngIf="showSignIn">
93
- <button class="signin-close" (click)="closeModal()" aria-label="Close">×</button>
94
- <app-sign-in (switchToSignUp)="openSignUp()"
95
- (signInSuccess)="handleSignInSuccess()">
96
  </app-sign-in>
97
  </ng-container>
98
  <ng-container *ngIf="showSignUp">
99
- <button class="signup-close" (click)="closeModal()" aria-label="Close">×</button>
100
- <app-sign-up (switchToSignIn)="openSignIn()">
101
  </app-sign-up>
102
  </ng-container>
103
  </div>
104
 
105
- <!-- ===== Info Dialog (unchanged) ===== -->
106
  <div class="modal-backdrop modal-backdrop--fade" *ngIf="showInfoDialog" (click)="closeInfoDialog()"></div>
107
  <div class="modal dialog-modal dialog-modal--zoom" *ngIf="showInfoDialog" role="dialog" aria-modal="true" aria-label="About Py-Detect">
108
- <div class="dialog-content">
109
- <!-- Close button at top-right (replaces Back) -->
110
  <button class="dialog-close" (click)="closeInfoDialog()" aria-label="Close">×</button>
111
 
112
  <h3>How It Works</h3>
@@ -119,3 +260,5 @@
119
  <p>It is fast, non-invasive, configurable for different scenarios, and supports decisions with clear, data-driven evidence.</p>
120
  </div>
121
  </div>
 
 
 
1
  <!-- Logo and Py-Detect Title (kept as your last version) -->
2
+ <div class="back-colo">
3
  <div class="logo-title-row">
4
  <img src="/assets/pykara-logo.png" alt="PyDetect Logo" class="logo-img" />
5
  <div class="py-detect-title">
 
14
  <span class="py-letter t2">T</span>
15
  </div>
16
  </div>
17
+ </div>
18
+
19
+ <!-- Top-right auth buttons (unchanged) -->
20
+ <div class="auth-topright">
21
+ <button class="auth-btn" (click)="openSignIn()">Sign In</button>
22
+ <button class="auth-btn" (click)="openSignUp()">Sign Up</button>
23
+ </div>
24
 
25
+ <!-- New Landing Hero + Sections -->
26
+ <div class="landing">
27
+ <!-- Hero Section -->
28
+ <div class="hero">
29
+ <h2>Real-Time Truth & Deception Detection</h2>
30
+ <p>
31
+ One intelligent platform for law enforcement, recruiters, and security teams.
32
+ Analyse verbal responses and body language in real-time to detect deception, assess risks, and build trust.
33
+ </p>
34
+ <a href="#" class="btn know-more" (click)="openInfoDialog(); $event.preventDefault()">Know More</a>
35
  </div>
36
+ </div>
37
+
38
+
39
+ <!-- Blue Background Wrapper -->
40
+ <div class="blue-section">
41
+ <section class="specialisations">
42
+ <!-- Mission Section -->
43
+ <section class="mission">
44
+ <h2 class="section-title">OUR MISSION IS SIMPLE & IMPACTFUL</h2>
45
+ <div class="mission-row">
46
+ <div class="mission-left">
47
+ <p>
48
+ Py-Detect is designed to reduce deception, fraud, and risk by enabling
49
+ investigators, recruiters, and security professionals to uncover hidden truths
50
+ with precision and speed. Our goal is to create safer communities, fairer
51
+ recruitment processes, and stronger organisational security.
52
+ </p>
53
+ </div>
54
+ <div class="mission-right">
55
+ <p>
56
+ By combining AI-powered verbal analysis, body language<br> detection, and facial
57
+ recognition, Py-Detect provides<br> real-time deception scores and behavioural insights.<br>
58
+ This empowers law enforcement, HR teams, and security<br> officers to make
59
+ confident, evidence-based decisions that<br> enhance safety, trust, and efficiency.
60
+ </p>
61
+ </div>
62
+ </div>
63
+ </section>
64
+
65
+ <div class="specialisation-cards">
66
+ <!-- Card 1 -->
67
+ <div class="card">
68
+ <i class="fas fa-user-secret icon"></i>
69
+ <h3>AI-Powered Investigation</h3>
70
+ <p>
71
+ Advanced NLP and computer vision analyse verbal and non-verbal cues in real time,
72
+ supporting law enforcement with reliable truth detection.
73
+ </p>
74
+ </div>
75
 
76
+ <!-- Card 2 -->
77
+ <div class="card">
78
+ <i class="fas fa-bullseye icon"></i>
79
+ <h3>High Accuracy</h3>
80
+ <p>
81
+ Trained on diverse behavioural datasets, Py-Detect delivers strong accuracy across
82
+ multiple scenarios including crime, recruitment, and security.
83
+ </p>
 
84
  </div>
85
+
86
+ <!-- Card 3 -->
87
+ <div class="card">
88
+ <i class="fas fa-shield-alt icon"></i>
89
+ <h3>Secure & Scalable</h3>
90
+ <p>
91
+ Built with data privacy in mind, Py-Detect ensures secure handling of sensitive inputs
92
+ while scaling for enterprise or government use.
93
+ </p>
94
+ </div>
95
+ </div>
96
+ </section>
97
+ </div>
98
+
99
+ <!-- How to Use Section -->
100
+ <section class="how-to-use">
101
+ <h2 class="section-title">How to Use Py-Detect?</h2>
102
+ <p class="section-subtitle">
103
+ Py-Detect supports both <strong>Admins</strong> (e.g., Inspectors) and <strong>Users</strong> (e.g., Constables)
104
+ to manage cases, conduct investigations, and analyse suspects with AI.
105
+ </p>
106
+
107
+ <div class="use-cards">
108
+ <!-- Step 1 -->
109
+ <div class="use-card">
110
+ <i class="fas fa-user-tie icon"></i>
111
+ <h3>1. Admin Creates Case</h3>
112
+ <p>
113
+ The Admin (Inspector) logs in, enters case details, and assigns
114
+ the investigation to specific users.
115
+ </p>
116
+ </div>
117
+
118
+ <!-- Step 2 -->
119
+ <div class="use-card">
120
+ <i class="fas fa-user-check icon"></i>
121
+ <h3>2. User Views Assignment</h3>
122
+ <p>
123
+ Assigned Users (Constables) log in to see their cases,
124
+ review case details, and prepare for investigation.
125
+ </p>
126
+ </div>
127
+
128
+ <!-- Step 3 -->
129
+ <div class="use-card">
130
+ <i class="fas fa-video icon"></i>
131
+ <h3>3. Investigation Recording</h3>
132
+ <p>
133
+ During interrogation, the system generates AI-based questions.
134
+ Video and audio responses are recorded in real time.
135
+ </p>
136
+ </div>
137
+
138
+ <!-- Step 4 -->
139
+ <div class="use-card">
140
+ <i class="fas fa-brain icon"></i>
141
+ <h3>4. AI Analysis</h3>
142
+ <p>
143
+ Py-Detect analyses tone, body language, and facial expressions
144
+ to produce a deception score and behavioural insights.
145
+ </p>
146
+ </div>
147
+
148
+ <!-- Step 5 -->
149
+ <div class="use-card">
150
+ <i class="fas fa-file-alt icon"></i>
151
+ <h3>5. Report & Decision</h3>
152
+ <p>
153
+ A final AI-powered prediction report is generated,
154
+ helping investigators make evidence-based decisions quickly.
155
+ </p>
156
  </div>
157
  </div>
158
+ </section>
159
 
160
+ <!-- Features Section -->
161
+ <section id="features" class="features">
162
+ <!-- Crime Investigation -->
163
+ <div class="feature-row">
164
+ <div class="feature-text">
165
+ <h2>Crime Investigation</h2>
166
+ <p>
167
+ Py-Detect assists law enforcement and police by analysing suspects’ verbal responses
168
+ and body language during interrogations. With AI-driven facial recognition, tone analysis,
169
+ and micro-expression detection, investigators gain deeper behavioural insights and
170
+ a real-time deception score to uncover hidden truths.
171
+ </p>
172
+ </div>
173
+ <div class="feature-image">
174
+ <img src="assets/3.JPG" alt="Crime Investigation">
175
  </div>
176
  </div>
 
177
 
178
+ <!-- Recruitment Analysis -->
179
+ <div class="feature-row reverse">
180
+ <div class="feature-image">
181
+ <img src="assets/6.JPG" alt="Recruitment Analysis">
 
182
  </div>
183
+ <div class="feature-text">
184
+ <h2>Recruitment Analysis</h2>
185
+ <p>
186
+ In hiring processes, Py-Detect supports recruiters by evaluating candidates’ speech clarity,
187
+ consistency, and emotional cues during interviews. AI-based analysis highlights honesty levels
188
+ and potential stress indicators, enabling smarter, bias-free hiring decisions.
189
+ </p>
190
  </div>
191
+ </div>
192
+
193
+ <!-- Court Room -->
194
+ <div class="feature-row">
195
+ <div class="feature-text">
196
+ <h2>Court Room</h2>
197
+ <p>
198
+ Py-Detect strengthens courtroom proceedings by analysing witnesses’ verbal
199
+ responses, tone, and body language in real time. The system highlights potential
200
+ stress indicators, inconsistencies, and deception cues, providing judges and
201
+ legal professionals with deeper behavioural insights.
202
+ This ensures more reliable evaluations, supports fair trials, and reduces the
203
+ risks of biased or inaccurate testimony.
204
+ </p>
205
+ </div>
206
+ <div class="feature-image">
207
+ <img src="assets/4.JPG" alt="Court Room">
208
  </div>
209
  </div>
210
+ </section>
211
+
212
+ <!-- Footer from provided design -->
213
+ <footer>
214
+ <p>© 2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
215
+ <a href="#">Contact</a> |
216
+ <a href="#">Privacy Policy</a> |
217
+ <a href="#">Terms & Conditions</a>
218
+ <div class="social-icons">
219
+ <a href="#" class="social-icon facebook" aria-label="Facebook">
220
+ <i class="fab fa-facebook-f"></i>
221
+ </a>
222
+ <a href="#" class="social-icon youtube" aria-label="YouTube">
223
+ <i class="fab fa-youtube"></i>
224
+ </a>
225
+ <a href="#" class="social-icon linkedin" aria-label="LinkedIn">
226
+ <i class="fab fa-linkedin-in"></i>
227
+ </a>
228
+ <a href="#" class="social-icon instagram" aria-label="Instagram">
229
+ <i class="fab fa-instagram"></i>
230
+ </a>
231
+ </div>
232
  </footer>
233
 
234
  <!-- ===== Auth Modals (unchanged) ===== -->
235
  <div class="modal-backdrop" *ngIf="showSignIn || showSignUp" (click)="closeModal()"></div>
236
  <div class="modal" *ngIf="showSignIn || showSignUp" role="dialog" aria-modal="true">
237
  <ng-container *ngIf="showSignIn">
238
+ <app-sign-in (switchToSignUp)="openSignUp()" (close)="closeModal()">
 
 
239
  </app-sign-in>
240
  </ng-container>
241
  <ng-container *ngIf="showSignUp">
242
+ <app-sign-up (switchToSignIn)="openSignIn()" (close)="closeModal()">
 
243
  </app-sign-up>
244
  </ng-container>
245
  </div>
246
 
247
+ <!-- ===== Info Dialog ===== -->
248
  <div class="modal-backdrop modal-backdrop--fade" *ngIf="showInfoDialog" (click)="closeInfoDialog()"></div>
249
  <div class="modal dialog-modal dialog-modal--zoom" *ngIf="showInfoDialog" role="dialog" aria-modal="true" aria-label="About Py-Detect">
250
+ <div class="dialog-content" (click)="$event.stopPropagation()">
 
251
  <button class="dialog-close" (click)="closeInfoDialog()" aria-label="Close">×</button>
252
 
253
  <h3>How It Works</h3>
 
260
  <p>It is fast, non-invasive, configurable for different scenarios, and supports decisions with clear, data-driven evidence.</p>
261
  </div>
262
  </div>
263
+
264
+
src/app/homepage/sign-in/sign-in.component.css CHANGED
@@ -1,243 +1,226 @@
1
- :host {
2
- display: block
3
- }
4
-
5
-
6
- .auth-box {
7
- width: 54vw;
8
- display: grid;
9
- grid-template-columns: 1fr;
10
- background: #2b1b6b;
11
- border-radius: 18px;
12
- overflow: hidden;
13
- box-shadow: 0 20px 60px 0 #18181b, 0 0 32px #38bdf888;
14
- min-height: 420px;
15
- max-height: 520px;
16
- z-index: 1100;
17
- animation: popupZoomIn .35s cubic-bezier(0.23, 1, 0.32, 1);
18
- }
19
-
20
- @keyframes popupZoomIn {
21
- 0% { opacity: 0; transform: scale(0.96) translateY(16px); }
22
- 100% { opacity: 1; transform: scale(1) translateY(0); }
23
- }
24
-
25
- .panel-right {
26
- position: relative;
27
- background: radial-gradient(120% 120% at 20% 50%,rgba(0,0,0,.25) 0%,rgba(0,0,0,0) 60%);
28
- width: 100%;
29
- height: 100%;
30
  display: flex;
31
- align-items: stretch;
32
- justify-content: stretch;
 
 
 
33
  }
34
 
35
- .right-image {
36
- width: 100%;
37
- height: 100%;
38
  display: flex;
39
- align-items: stretch;
40
- justify-content: stretch;
41
- }
42
-
43
- .right-image img {
44
- width: 100%;
45
- height: 100%;
46
- object-fit: cover;
47
- display: block;
48
- }
49
-
50
- .panel-left {
51
- padding: clamp(22px,3vw,38px);
52
- background: white;
53
- color: black;
54
- }
55
-
56
- .brand-mark {
57
- width: 4vw;
58
- margin-bottom: 14px;
59
- border: 2px solid #b1b1b17d;
60
- box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
61
- }
62
-
63
- .title {
64
- margin: 0 0 6px;
65
- font-size: clamp(22px,3vw,26px);
66
- font-weight: 700
67
- }
68
- .subtitle {
69
- margin: 0 0 8px; /* Reduced space below Create Account text */
70
  }
71
-
72
- .form {
73
- display: grid;
74
- gap: 12px
 
75
  }
76
-
77
- .field {
78
- display: grid;
79
- gap: 6px
 
 
 
 
 
 
 
 
80
  }
81
-
82
- label {
 
 
83
  font-weight: 600;
84
- font-size: 13px;
85
- }
86
-
87
- input[type="email"], input[type="password"] {
88
- color: black;
89
- border: 1px solid rgb(0 0 0 / 57%);
90
- border-radius: 10px;
91
- padding: 11px 12px;
92
- outline: none
93
- }
94
-
95
- input::placeholder {
96
- color: #808080;
97
  }
98
-
99
- input:focus {
100
- border-color: #a78bfa;
101
- box-shadow: 0 0 0 3px rgba(167,139,250,.25)
 
 
 
 
 
 
 
102
  }
103
-
104
- .error {
105
- color: red;
106
- font-size: 12px
 
 
 
 
107
  }
108
-
109
- .btn {
110
  width: 100%;
111
- border-radius: 999px;
112
- padding: 12px 18px;
113
- cursor: pointer
114
- }
115
-
116
- .btn-primary {
117
- background: #0b0f1a;
118
- color: #fff;
119
- border: none;
120
- font-weight: 700
121
  }
122
-
123
- .btn[aria-busy="true"] {
124
- opacity: .75;
125
- cursor: progress
126
  }
127
-
128
- .footnote {
129
- margin: 14px 0 0;
130
- font-size: 13px;
131
  }
132
-
133
- .footnote a {
134
- color: red;
135
- text-decoration: underline
136
- }
137
-
138
- .admin-info {
139
- margin: 10px 0 16px 0;
140
- color: #f134d0;
141
- font-size: 1em;
142
  font-weight: 600;
143
- text-align: center;
 
 
144
  }
145
-
146
- .admin-checkbox {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  display: flex;
 
148
  align-items: center;
149
- gap: 8px;
150
- margin: 10px 0 0 0;
151
- }
152
- .admin-checkbox input[type="checkbox"] {
153
- accent-color: #2b1b6b;
154
- width: 18px;
155
- height: 18px;
156
- }
157
- .admin-checkbox label {
158
- color: #2b1b6b;
159
- font-size: 1em;
160
- font-weight: 600;
161
- cursor: pointer;
162
- }
163
-
164
-
165
- @keyframes fadeInBackdrop {
166
- from { opacity: 0; }
167
- to { opacity: 1; }
168
  }
169
-
170
- @media(min-width:900px) {
171
- .auth-box {
172
- grid-template-columns: 520px 1fr
173
- }
174
- }
175
-
176
-
177
- .topTitle {
178
  display: flex;
179
  align-items: center;
180
- gap: 21px;
 
 
 
 
 
 
 
 
 
 
181
  }
182
-
183
- .topHeader {
184
- font-size: 1vw;
 
 
185
  }
186
-
187
- .signin-close {
188
- position: absolute;
189
- top: 158px;
190
- right: 450px;
191
- width: 44px;
192
- height: 3px;
193
  border: none;
194
- background: #18181b;
195
- color: #fff;
196
- border-radius: 50%;
 
 
197
  cursor: pointer;
198
- font-size: 2rem;
199
- font-weight: bold;
200
- display: flex;
201
- align-items: center;
202
- justify-content: center;
203
- box-shadow: 0 2px 8px rgba(0,0,0,0.18);
204
- z-index: 10;
205
  transition: background 0.2s, color 0.2s;
 
206
  }
207
-
208
- .signin-close:hover {
209
- background: #333;
210
- color: #f134d0;
211
  }
212
-
213
- .signin-close:hover, .signup-close:hover {
214
- background: linear-gradient(135deg, #333 60%, #23272b 100%);
215
- color: #38bdf8;
216
- box-shadow: 0 8px 24px #38bdf888;
217
  }
218
-
219
- .modal__close {
 
 
 
 
 
 
 
 
 
 
220
  position: absolute;
221
  top: 18px;
222
  right: 18px;
223
- width: 44px;
224
- height: 44px;
225
  border: none;
226
- background: #18181b;
227
  color: #fff;
228
  border-radius: 50%;
229
- cursor: pointer;
230
  font-size: 2rem;
231
  font-weight: bold;
232
  display: flex;
233
  align-items: center;
234
  justify-content: center;
235
- box-shadow: 0 2px 8px rgba(0,0,0,0.18);
236
  z-index: 10;
237
  transition: background 0.2s, color 0.2s;
 
238
  }
 
 
 
 
239
 
240
- .modal__close:hover {
241
- background: #333;
242
- color: #f134d0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  }
 
1
+ .signin-popup {
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100vw;
6
+ height: 100vh;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  display: flex;
8
+ flex-direction: column;
9
+ align-items: center;
10
+ justify-content: center;
11
+ z-index: 1000;
12
+ background: rgb(30 41 59 / 67%);
13
  }
14
 
15
+ .signin-header {
16
+ width: 420px;
17
+ max-width: 95vw;
18
  display: flex;
19
+ justify-content: flex-end;
20
+ align-items: flex-start;
21
+ padding: 0;
22
+ position: absolute;
23
+ top: -28px;
24
+ right: 12px;
25
+ pointer-events: none;
26
+ z-index: 2;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
+ .signin-logo {
29
+ display: flex;
30
+ align-items: center;
31
+ gap: 12px;
32
+ pointer-events: auto;
33
  }
34
+ .signin-login-link {
35
+ font-size: 1.1rem;
36
+ color: #fff;
37
+ margin-top: 0;
38
+ pointer-events: auto;
39
+ background: #18314a;
40
+ border-radius: 0 0 12px 12px;
41
+ padding: 8px 18px 4px 18px;
42
+ box-shadow: 0 2px 8px #0002;
43
+ position: relative;
44
+ right: 0;
45
+ top: 0;
46
  }
47
+ .signin-login-link a {
48
+ color: #1de9b6;
49
+ text-decoration: underline;
50
+ margin-left: 6px;
51
  font-weight: 600;
52
+ cursor: pointer;
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
54
+ .signin-box {
55
+ background: #18314a;
56
+ border-radius: 22px;
57
+ box-shadow: 0 12px 48px #000a;
58
+ padding: 48px 38px 32px 38px;
59
+ width: 420px;
60
+ max-width: 95vw;
61
+ display: flex;
62
+ flex-direction: column;
63
+ align-items: center;
64
+ position: relative;
65
  }
66
+ .signin-title {
67
+ color: #38bdf8;
68
+ font-size: 2.1rem;
69
+ font-weight: 800;
70
+ margin-bottom: 12px;
71
+ text-align: center;
72
+ letter-spacing: 1px;
73
+ text-shadow: 0 2px 8px #0008;
74
  }
75
+ form {
 
76
  width: 100%;
 
 
 
 
 
 
 
 
 
 
77
  }
78
+ .signin-row {
79
+ display: flex;
80
+ gap: 24px;
81
+ margin-bottom: 18px;
82
  }
83
+ .signin-field {
84
+ flex: 1;
85
+ display: flex;
86
+ flex-direction: column;
87
  }
88
+ .signin-field label {
89
+ color: #fff;
 
 
 
 
 
 
 
 
90
  font-weight: 600;
91
+ margin-bottom: 6px;
92
+ font-size: 1rem;
93
+ letter-spacing: 0.5px;
94
  }
95
+ .signin-field input {
96
+ background: #fff;
97
+ color: #18314a;
98
+ border: none;
99
+ border-radius: 8px;
100
+ padding: 12px 14px;
101
+ font-size: 1rem;
102
+ margin-bottom: 2px;
103
+ box-shadow: 0 1px 4px #0002;
104
+ transition: border 0.2s, box-shadow 0.2s;
105
+ }
106
+ .signin-field input:focus {
107
+ outline: 2px solid #1de9b6;
108
+ border-color: #1de9b6;
109
+ box-shadow: 0 0 0 2px #1de9b688;
110
+ }
111
+ .signin-field input::placeholder {
112
+ color: #b0b8c1;
113
+ opacity: 1;
114
+ }
115
+ .signin-field small.error {
116
+ color: #ff5252;
117
+ font-size: 0.85rem;
118
+ margin-top: 2px;
119
+ text-shadow: 0 1px 2px #0008;
120
+ }
121
+ .signin-options-row {
122
  display: flex;
123
+ justify-content: space-between;
124
  align-items: center;
125
+ margin-bottom: 12px;
126
+ margin-top: -8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  }
128
+ .remember-me {
 
 
 
 
 
 
 
 
129
  display: flex;
130
  align-items: center;
131
+ gap: 6px;
132
+ font-size: 1rem;
133
+ color: #b0b8c1;
134
+ }
135
+ .remember-me input[type="checkbox"] {
136
+ accent-color: #38bdf8;
137
+ width: 16px;
138
+ height: 16px;
139
+ }
140
+ .forgot-password {
141
+ font-size: 1rem;
142
  }
143
+ .forgot-link {
144
+ color: #38bdf8;
145
+ text-decoration: underline;
146
+ font-weight: 500;
147
+ cursor: pointer;
148
  }
149
+ .signin-btn {
150
+ width: 100%;
151
+ background: #38bdf8;
152
+ color: #18314a;
 
 
 
153
  border: none;
154
+ border-radius: 8px;
155
+ padding: 14px 0;
156
+ font-size: 1.1rem;
157
+ font-weight: 700;
158
+ margin-bottom: 18px;
159
  cursor: pointer;
 
 
 
 
 
 
 
160
  transition: background 0.2s, color 0.2s;
161
+ box-shadow: 0 2px 8px #0003;
162
  }
163
+ .signin-btn:hover {
164
+ background: #13bfa6;
 
 
165
  }
166
+ .signin-footer {
167
+ color: #b0b8c1;
168
+ font-size: 0.95rem;
169
+ text-align: center;
170
+ margin-top: 8px;
171
  }
172
+ .signin-footer .footer-sep {
173
+ display: block;
174
+ height: 16px;
175
+ }
176
+ .signin-footer a {
177
+ color: #38bdf8;
178
+ text-decoration: underline;
179
+ margin-left: 4px;
180
+ font-weight: 600;
181
+ cursor: pointer;
182
+ }
183
+ .signin-close {
184
  position: absolute;
185
  top: 18px;
186
  right: 18px;
187
+ width: 38px;
188
+ height: 38px;
189
  border: none;
190
+ background: #14263c;
191
  color: #fff;
192
  border-radius: 50%;
 
193
  font-size: 2rem;
194
  font-weight: bold;
195
  display: flex;
196
  align-items: center;
197
  justify-content: center;
198
+ cursor: pointer;
199
  z-index: 10;
200
  transition: background 0.2s, color 0.2s;
201
+ box-shadow: 0 2px 8px #0005;
202
  }
203
+ .signin-close:hover {
204
+ background: #38bdf8;
205
+ color: #18314a;
206
+ }
207
 
208
+ @media (max-width: 700px) {
209
+ .signin-box {
210
+ padding: 18px 6vw 18px 6vw;
211
+ width: 98vw;
212
+ }
213
+ .signin-header {
214
+ flex-direction: column;
215
+ align-items: flex-start;
216
+ width: 98vw;
217
+ padding: 0 0 12px 0;
218
+ }
219
+ .signin-title {
220
+ font-size: 1.3rem;
221
+ }
222
+ .signin-row {
223
+ flex-direction: column;
224
+ gap: 0;
225
+ }
226
  }
src/app/homepage/sign-in/sign-in.component.html CHANGED
@@ -1,45 +1,44 @@
1
- <section class="auth-page">
2
- <div class="signin-modal-backdrop"></div>
3
- <div class="auth-box" role="dialog" aria-labelledby="siTitle">
4
- <!-- Left (form) -->
5
- <div class="panel-left">
6
- <div class="topTitle">
7
- <img class="brand-mark" src="/assets/pykara-logo.png" alt="Brand" />
8
- <span class="topHeader"><b>Welcome back!</b></span>
 
 
 
 
 
 
 
 
 
9
  </div>
10
- <h2 id="siTitle" class="title">Log in</h2>
11
-
12
- <form class="form" [formGroup]="form" (ngSubmit)="signIn()" novalidate>
13
- <div class="field">
14
- <label for="email">
15
- Email
16
- </label>
17
- <input id="email" type="email" formControlName="email" placeholder="you@example.com" />
18
  </div>
19
-
20
- <div class="field">
21
- <label for="password">
22
- Password
23
- </label>
24
- <input id="password" type="password" formControlName="password" placeholder="••••••••" />
 
 
25
  </div>
26
-
27
- <button class="btn btn-primary" type="submit">
28
- Sign in
29
- </button>
30
- </form>
31
-
32
- <p class="footnote">
33
- New here?
34
- <a href="#" (click)="goToSignUp(); $event.preventDefault()">Create an account</a>
35
- </p>
36
- </div>
37
-
38
- <!-- Right (image) -->
39
- <div class="panel-right">
40
- <div class="right-image">
41
- <img src="/assets/signin.png" alt="Decorative" />
42
  </div>
 
 
 
 
 
 
 
43
  </div>
44
  </div>
45
  </section>
 
1
+ <section class="signin-popup">
2
+ <div class="signin-header">
3
+ <div class="signin-logo">
4
+ <!-- No logo or CO. text as per sign-up style -->
5
+ </div>
6
+ </div>
7
+ <div class="signin-box">
8
+ <button class="signin-close" type="button" (click)="closePopup()" aria-label="Close">&times;</button>
9
+ <h2 class="signin-title">Log In</h2>
10
+ <form [formGroup]="form" (ngSubmit)="signIn()" novalidate>
11
+ <div class="signin-row">
12
+ <div class="signin-field">
13
+ <label for="email">Email</label>
14
+ <input id="email" type="email" placeholder="you@example.com" formControlName="email" [attr.aria-invalid]="form.get('email')?.invalid && form.get('email')?.touched" />
15
+ <small class="error" *ngIf="form.get('email')?.touched && form.get('email')?.hasError('required')">Email is required.</small>
16
+ <small class="error" *ngIf="form.get('email')?.touched && form.get('email')?.hasError('email')">Please enter a valid email address.</small>
17
+ </div>
18
  </div>
19
+ <div class="signin-row">
20
+ <div class="signin-field">
21
+ <label for="password">Password</label>
22
+ <input id="password" type="password" placeholder="••••••••" formControlName="password" [attr.aria-invalid]="form.get('password')?.invalid && form.get('password')?.touched" />
23
+ <small class="error" *ngIf="form.get('password')?.touched && form.get('password')?.hasError('required')">Password is required.</small>
 
 
 
24
  </div>
25
+ </div>
26
+ <div class="signin-row signin-options-row">
27
+ <div class="remember-me">
28
+ <input id="rememberMe" type="checkbox" />
29
+ <label for="rememberMe">Remember me</label>
30
+ </div>
31
+ <div class="forgot-password">
32
+ <a href="#" class="forgot-link">Forgot password?</a>
33
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  </div>
35
+ <button class="signin-btn" type="submit">Sign In</button>
36
+ </form>
37
+ <div class="signin-footer">
38
+ <span>Don't have an account?</span>
39
+ <a href="#" (click)="goToSignUp(); $event.preventDefault()">Sign up here!</a>
40
+ <span class="footer-sep"></span>
41
+ &copy; Pykara Technologies, 2025. All rights reserved.
42
  </div>
43
  </div>
44
  </section>
src/app/homepage/sign-in/sign-in.component.ts CHANGED
@@ -15,6 +15,7 @@ import { SignInService } from './sign-in.service'; // Import SignInService
15
  })
16
  export class SignInComponent {
17
  @Output() switchToSignUp = new EventEmitter<void>();
 
18
  form: FormGroup;
19
 
20
  constructor(private fb: FormBuilder, private router: Router, private signInService: SignInService) {
@@ -28,6 +29,10 @@ export class SignInComponent {
28
  this.switchToSignUp.emit();
29
  }
30
 
 
 
 
 
31
  signIn() {
32
  if (this.form.invalid) {
33
  console.log("Form is invalid:", this.form.errors);
@@ -42,14 +47,29 @@ export class SignInComponent {
42
  this.signInService.signIn(payload).subscribe(
43
  (response) => {
44
  console.log('Login successful:', response);
45
- // Navigate to case details or home page after login success
46
- this.router.navigate(['/case-details']);
 
 
 
 
 
 
 
 
47
  },
48
  (error) => {
49
  console.error('Login failed:', error);
50
  alert('Invalid email or password!');
51
  }
52
  );
 
53
 
 
 
 
 
 
 
54
  }
55
  }
 
15
  })
16
  export class SignInComponent {
17
  @Output() switchToSignUp = new EventEmitter<void>();
18
+ @Output() close = new EventEmitter<void>();
19
  form: FormGroup;
20
 
21
  constructor(private fb: FormBuilder, private router: Router, private signInService: SignInService) {
 
29
  this.switchToSignUp.emit();
30
  }
31
 
32
+ closePopup() {
33
+ this.close.emit();
34
+ }
35
+
36
  signIn() {
37
  if (this.form.invalid) {
38
  console.log("Form is invalid:", this.form.errors);
 
47
  this.signInService.signIn(payload).subscribe(
48
  (response) => {
49
  console.log('Login successful:', response);
50
+ // Save role to localStorage for future logins
51
+ if (response && response.role) {
52
+ localStorage.setItem('role', response.role);
53
+ }
54
+ // Redirect based on role
55
+ if (response && response.role === 'admin') {
56
+ this.router.navigate(['/infopage']);
57
+ } else {
58
+ this.router.navigate(['/case-details']);
59
+ }
60
  },
61
  (error) => {
62
  console.error('Login failed:', error);
63
  alert('Invalid email or password!');
64
  }
65
  );
66
+ }
67
 
68
+ ngOnInit() {
69
+ // If already logged in and role is admin, redirect to infopage
70
+ const role = localStorage.getItem('role');
71
+ if (role === 'admin') {
72
+ this.router.navigate(['/infopage']);
73
+ }
74
  }
75
  }
src/app/homepage/sign-in/sign-in.service.ts CHANGED
@@ -7,14 +7,9 @@ import { Observable } from 'rxjs';
7
  })
8
  export class SignInService {
9
 
10
- private baseUrl = location.hostname.endsWith('hf.space')
11
- ? 'https://pykara-py-detect-backend.hf.space'
12
- : 'http://localhost:5000';
13
-
14
  constructor(private http: HttpClient) { }
15
 
16
  signIn(payload: any): Observable<any> {
17
- //return this.http.post('http://127.0.0.1:5000/sign-in', payload);
18
- return this.http.post(`${this.baseUrl}/sign-in`, payload);
19
  }
20
  }
 
7
  })
8
  export class SignInService {
9
 
 
 
 
 
10
  constructor(private http: HttpClient) { }
11
 
12
  signIn(payload: any): Observable<any> {
13
+ return this.http.post('http://127.0.0.1:5000/sign-in', payload);
 
14
  }
15
  }
src/app/homepage/sign-up/sign-up.component.css CHANGED
@@ -1,9 +1,7 @@
1
  :host {
2
- display: block
3
  }
4
 
5
-
6
-
7
  /* Two-column card */
8
  .auth-box {
9
  width: 49vw;
@@ -12,14 +10,13 @@
12
  background: #2b1b6b; /* purple base under left panel */
13
  border-radius: 14px;
14
  overflow: hidden;
15
- box-shadow: 0 20px 60px rgba(0,0,0,.35);
16
  position: relative;
17
  }
18
-
19
  /* Right column (image) */
20
  .panel-right {
21
  position: relative;
22
- background: radial-gradient(120% 120% at 20% 50%, rgba(0,0,0,.25) 0%, rgba(0,0,0,0) 60%);
23
  }
24
 
25
  .panel-right::before {
@@ -27,7 +24,7 @@
27
  content: "";
28
  position: absolute;
29
  inset: 0;
30
- background: linear-gradient(90deg, rgba(0,0,0,.45) 0%, rgba(0,0,0,0) 26%);
31
  pointer-events: none;
32
  }
33
 
@@ -40,12 +37,12 @@
40
 
41
  .right-image img {
42
  width: 100%;
43
- display: block
44
  }
45
 
46
  /* Left column (form) */
47
  .panel-left {
48
- padding: clamp(22px,3.5vw,36px);
49
  background: white; /* brighter purple */
50
  color: black;
51
  }
@@ -59,8 +56,8 @@
59
 
60
  .title {
61
  margin: 0 0 6px;
62
- font-size: clamp(22px,3vw,26px);
63
- font-weight: 700
64
  }
65
 
66
  .subtitle {
@@ -69,12 +66,13 @@
69
 
70
  .form {
71
  display: grid;
72
- gap: 12px
73
  }
74
 
 
75
  .field {
76
  display: grid;
77
- gap: 6px
78
  }
79
 
80
  label {
@@ -83,43 +81,33 @@ label {
83
  }
84
 
85
  /* Inputs: translucent with subtle border */
86
- input[type="text"], input[type="password"] {
87
  color: #000000;
88
  border: 1px solid rgb(0 0 0 / 57%);
89
  border-radius: 10px;
90
- padding: 11px 12px;
91
  outline: none;
92
  }
93
 
94
  input::placeholder {
95
- color: #808080
96
  }
97
 
98
- input:focus {
99
  border-color: #a78bfa;
100
- box-shadow: 0 0 0 3px rgba(167,139,250,.25)
101
  }
102
 
103
  select.input-field {
104
- color: #000000;
105
- border: 1px solid rgb(0 0 0 / 57%);
106
- border-radius: 10px;
107
- padding: 11px 12px;
108
- outline: none;
109
  font-size: 16px;
110
  width: 100%;
111
  background: #fff;
112
  box-sizing: border-box;
113
  }
114
 
115
- select.input-field:focus {
116
- border-color: #a78bfa;
117
- box-shadow: 0 0 0 3px rgba(167,139,250,.25);
118
- }
119
-
120
  .error {
121
  color: red;
122
- font-size: 12px
123
  }
124
 
125
  /* Buttons */
@@ -127,26 +115,26 @@ select.input-field:focus {
127
  width: 100%;
128
  border-radius: 999px;
129
  padding: 12px 18px;
130
- cursor: pointer
131
  }
132
 
133
  .btn-primary {
134
  background: #0b0f1a;
135
  color: #fff;
136
  border: none;
137
- font-weight: 700
138
  }
139
 
140
  .btn-dark {
141
  background: #111827;
142
  color: #fff;
143
  border: none;
144
- margin-top: 8px
145
  }
146
 
147
  .btn[aria-busy="true"] {
148
  opacity: .75;
149
- cursor: progress
150
  }
151
 
152
  .footnote {
@@ -156,13 +144,13 @@ select.input-field:focus {
156
 
157
  .footnote a {
158
  color: red;
159
- text-decoration: underline
160
  }
161
 
162
  /* Layout: stack on small, split on >= 900px */
163
- @media(min-width:900px) {
164
  .auth-box {
165
- grid-template-columns: 520px 1fr
166
  }
167
  }
168
 
@@ -186,60 +174,260 @@ select.input-field:focus {
186
  }
187
  }
188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
- .signup-close {
 
 
 
 
 
191
  position: absolute;
192
- top: 18px;
193
- right: 18px;
 
 
 
 
 
 
 
 
 
194
  width: 48px;
195
  height: 48px;
196
- border: none;
197
- background: linear-gradient(135deg, #23272b 60%, #18181b 100%);
198
- color: #fff;
199
- border-radius: 50%;
200
- cursor: pointer;
 
201
  font-size: 2.2rem;
202
- font-weight: bold;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  display: flex;
 
204
  align-items: center;
205
- justify-content: center;
206
- box-shadow: 0 4px 16px rgba(30,41,59,0.28);
207
- z-index: 20;
208
- transition: background 0.2s, color 0.2s, box-shadow 0.2s;
209
- outline: none;
210
  }
211
-
212
- .signup-close:hover {
213
- background: linear-gradient(135deg, #333 60%, #23272b 100%);
214
  color: #38bdf8;
215
- box-shadow: 0 8px 24px #38bdf888;
 
 
 
 
 
216
  }
217
-
218
- .signin-close {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  position: absolute;
220
  top: 18px;
221
  right: 18px;
222
- width: 48px;
223
- height: 48px;
224
  border: none;
225
- background: linear-gradient(135deg, #23272b 60%, #18181b 100%);
226
  color: #fff;
227
  border-radius: 50%;
228
- cursor: pointer;
229
- font-size: 2.2rem;
230
  font-weight: bold;
231
  display: flex;
232
  align-items: center;
233
  justify-content: center;
234
- box-shadow: 0 4px 16px rgba(30,41,59,0.28);
235
- z-index: 20;
236
- transition: background 0.2s, color 0.2s, box-shadow 0.2s;
237
- outline: none;
 
 
 
 
238
  }
239
 
240
- .signin-close:hover {
241
- background: linear-gradient(135deg, #333 60%, #23272b 100%);
242
- color: #38bdf8;
243
- box-shadow: 0 8px 24px #38bdf888;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  }
245
 
 
 
 
 
 
 
 
 
 
 
1
  :host {
2
+ display: block;
3
  }
4
 
 
 
5
  /* Two-column card */
6
  .auth-box {
7
  width: 49vw;
 
10
  background: #2b1b6b; /* purple base under left panel */
11
  border-radius: 14px;
12
  overflow: hidden;
13
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
14
  position: relative;
15
  }
 
16
  /* Right column (image) */
17
  .panel-right {
18
  position: relative;
19
+ background: radial-gradient(120% 120% at 20% 50%, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0) 60%);
20
  }
21
 
22
  .panel-right::before {
 
24
  content: "";
25
  position: absolute;
26
  inset: 0;
27
+ background: linear-gradient(90deg, rgba(0, 0, 0, 0.45) 0%, rgba(0, 0, 0, 0) 26%);
28
  pointer-events: none;
29
  }
30
 
 
37
 
38
  .right-image img {
39
  width: 100%;
40
+ display: block;
41
  }
42
 
43
  /* Left column (form) */
44
  .panel-left {
45
+ padding: clamp(22px, 3.5vw, 36px);
46
  background: white; /* brighter purple */
47
  color: black;
48
  }
 
56
 
57
  .title {
58
  margin: 0 0 6px;
59
+ font-size: clamp(22px, 3vw, 26px);
60
+ font-weight: 700;
61
  }
62
 
63
  .subtitle {
 
66
 
67
  .form {
68
  display: grid;
69
+ gap: 25px; /* Increased gap for better spacing between fields */
70
  }
71
 
72
+ /* Field-specific margin adjustments */
73
  .field {
74
  display: grid;
75
+ gap: 12px; /* Increased gap between label/input pairs */
76
  }
77
 
78
  label {
 
81
  }
82
 
83
  /* Inputs: translucent with subtle border */
84
+ input[type="text"], input[type="password"], select.input-field {
85
  color: #000000;
86
  border: 1px solid rgb(0 0 0 / 57%);
87
  border-radius: 10px;
88
+ padding: 12px 14px; /* Increased padding */
89
  outline: none;
90
  }
91
 
92
  input::placeholder {
93
+ color: #808080;
94
  }
95
 
96
+ input:focus, select.input-field:focus {
97
  border-color: #a78bfa;
98
+ box-shadow: 0 0 0 3px rgba(167, 139, 250, .25);
99
  }
100
 
101
  select.input-field {
 
 
 
 
 
102
  font-size: 16px;
103
  width: 100%;
104
  background: #fff;
105
  box-sizing: border-box;
106
  }
107
 
 
 
 
 
 
108
  .error {
109
  color: red;
110
+ font-size: 12px;
111
  }
112
 
113
  /* Buttons */
 
115
  width: 100%;
116
  border-radius: 999px;
117
  padding: 12px 18px;
118
+ cursor: pointer;
119
  }
120
 
121
  .btn-primary {
122
  background: #0b0f1a;
123
  color: #fff;
124
  border: none;
125
+ font-weight: 700;
126
  }
127
 
128
  .btn-dark {
129
  background: #111827;
130
  color: #fff;
131
  border: none;
132
+ margin-top: 8px;
133
  }
134
 
135
  .btn[aria-busy="true"] {
136
  opacity: .75;
137
+ cursor: progress;
138
  }
139
 
140
  .footnote {
 
144
 
145
  .footnote a {
146
  color: red;
147
+ text-decoration: underline;
148
  }
149
 
150
  /* Layout: stack on small, split on >= 900px */
151
+ @media(min-width: 900px) {
152
  .auth-box {
153
+ grid-template-columns: 520px 1fr;
154
  }
155
  }
156
 
 
174
  }
175
  }
176
 
177
+ .signup-popup {
178
+ position: fixed;
179
+ top: 0;
180
+ left: 0;
181
+ width: 100vw;
182
+ height: 100vh;
183
+ display: flex;
184
+ flex-direction: column;
185
+ align-items: center;
186
+ justify-content: center;
187
+ z-index: 1000;
188
+ background: rgb(30 41 59 / 67%);
189
+ }
190
 
191
+ .signup-header {
192
+ width: 100vw;
193
+ display: flex;
194
+ justify-content: space-between;
195
+ align-items: flex-start;
196
+ padding: 32px 48px 0 48px;
197
  position: absolute;
198
+ top: 0;
199
+ left: 0;
200
+ pointer-events: none;
201
+ }
202
+ .signup-logo {
203
+ display: flex;
204
+ align-items: center;
205
+ gap: 12px;
206
+ pointer-events: auto;
207
+ }
208
+ .signup-logo img {
209
  width: 48px;
210
  height: 48px;
211
+ border-radius: 8px;
212
+ background: #fff;
213
+ border: 2px solid #b1b1b17d;
214
+ }
215
+ .signup-brand {
216
+ color: #1de9b6;
217
  font-size: 2.2rem;
218
+ font-weight: 900;
219
+ letter-spacing: 2px;
220
+ }
221
+ /* Removed header login link positioning; new variant below form */
222
+ .signup-login-link {
223
+ font-size: 1.05rem;
224
+ color: #fff;
225
+ text-align: center;
226
+ margin: 14px 0 4px;
227
+ }
228
+ .signup-login-link.below { margin-top: 8px; }
229
+ .signup-login-link a {
230
+ color: #38bdf8;
231
+ text-decoration: underline;
232
+ margin-left: 6px;
233
+ font-weight: 600;
234
+ cursor: pointer;
235
+ }
236
+ .signup-box {
237
+ background: #18314a;
238
+ border-radius: 22px;
239
+ box-shadow: 0 12px 48px #000a;
240
+ padding: 48px 38px 32px 38px;
241
+ width: 520px;
242
+ max-width: 95vw;
243
  display: flex;
244
+ flex-direction: column;
245
  align-items: center;
246
+ position: relative;
 
 
 
 
247
  }
248
+ .signup-title {
 
 
249
  color: #38bdf8;
250
+ font-size: 2.1rem;
251
+ font-weight: 800;
252
+ margin-bottom: 32px;
253
+ text-align: center;
254
+ letter-spacing: 1px;
255
+ text-shadow: 0 2px 8px #0008;
256
  }
257
+ form {
258
+ width: 100%;
259
+ }
260
+ .signup-row {
261
+ display: flex;
262
+ gap: 24px;
263
+ margin-bottom: 18px;
264
+ }
265
+ .signup-field {
266
+ flex: 1;
267
+ display: flex;
268
+ flex-direction: column;
269
+ }
270
+ .signup-field label {
271
+ color: #fff;
272
+ font-weight: 600;
273
+ margin-bottom: 6px;
274
+ font-size: 1rem;
275
+ letter-spacing: 0.5px;
276
+ }
277
+ .signup-field input,
278
+ .signup-field select {
279
+ background: #fff;
280
+ color: #18314a;
281
+ border: none;
282
+ border-radius: 8px;
283
+ padding: 12px 14px;
284
+ font-size: 1rem;
285
+ margin-bottom: 2px;
286
+ box-shadow: 0 1px 4px #0002;
287
+ transition: border 0.2s, box-shadow 0.2s;
288
+ }
289
+ .signup-field input:focus,
290
+ .signup-field select:focus {
291
+ outline: 2px solid #1de9b6;
292
+ border-color: #1de9b6;
293
+ box-shadow: 0 0 0 2px #1de9b688;
294
+ }
295
+ .signup-field input::placeholder {
296
+ color: #b0b8c1;
297
+ opacity: 1;
298
+ }
299
+ .signup-field small.error {
300
+ color: #ff5252;
301
+ font-size: 0.85rem;
302
+ margin-top: 2px;
303
+ text-shadow: 0 1px 2px #0008;
304
+ }
305
+ .signup-checkbox {
306
+ display: flex;
307
+ align-items: center;
308
+ margin-bottom: 22px;
309
+ color: #fff;
310
+ font-size: 1rem;
311
+ }
312
+ .signup-checkbox input[type="checkbox"] {
313
+ margin-right: 10px;
314
+ accent-color: #1de9b6;
315
+ }
316
+ .signup-checkbox a {
317
+ color: #38bdf8;
318
+ text-decoration: underline;
319
+ margin-left: 4px;
320
+ }
321
+ .signup-btn {
322
+ width: 100%;
323
+ background: #38bdf8;
324
+ color: #18314a;
325
+ border: none;
326
+ border-radius: 8px;
327
+ padding: 14px 0;
328
+ font-size: 1.1rem;
329
+ font-weight: 700;
330
+ margin-bottom: 8px; /* reduced because login link follows */
331
+ cursor: pointer;
332
+ transition: background 0.2s, color 0.2s;
333
+ box-shadow: 0 2px 8px #0003;
334
+ }
335
+ .signup-btn:hover {
336
+ background: #13bfa6;
337
+ }
338
+ .signup-social-row {
339
+ display: flex;
340
+ gap: 18px;
341
+ width: 100%;
342
+ margin-bottom: 18px;
343
+ }
344
+ .signup-social {
345
+ flex: 1;
346
+ background: #fff;
347
+ color: #18314a;
348
+ border: none;
349
+ border-radius: 8px;
350
+ padding: 12px 0;
351
+ font-size: 1rem;
352
+ font-weight: 600;
353
+ cursor: pointer;
354
+ transition: background 0.2s, color 0.2s;
355
+ }
356
+ .signup-social.facebook:hover {
357
+ background: #3b5998;
358
+ color: #fff;
359
+ }
360
+ .signup-social.twitter:hover {
361
+ background: #1da1f2;
362
+ color: #fff;
363
+ }
364
+ .signup-footer {
365
+ color: #b0b8c1;
366
+ font-size: 0.95rem;
367
+ text-align: center;
368
+ margin-top: 8px;
369
+ }
370
+ .signup-close {
371
  position: absolute;
372
  top: 18px;
373
  right: 18px;
374
+ width: 38px;
375
+ height: 38px;
376
  border: none;
377
+ background: #14263c;
378
  color: #fff;
379
  border-radius: 50%;
380
+ font-size: 2rem;
 
381
  font-weight: bold;
382
  display: flex;
383
  align-items: center;
384
  justify-content: center;
385
+ cursor: pointer;
386
+ z-index: 10;
387
+ transition: background 0.2s, color 0.2s;
388
+ box-shadow: 0 2px 8px #0005;
389
+ }
390
+ .signup-close:hover {
391
+ background: #38bdf8;
392
+ color: #18314a;
393
  }
394
 
395
+ /* Animated glow loop for role info button when closed */
396
+ @keyframes pulseGlow { 0%{box-shadow:0 0 0 0 rgba(56,189,248,.75);} 70%{box-shadow:0 0 12px 6px rgba(56,189,248,0);} 100%{box-shadow:0 0 0 0 rgba(56,189,248,0);} }
397
+ .role-info-btn.glow-loop { animation:pulseGlow 2s ease-in-out infinite; }
398
+ .role-info-btn.glow-loop:hover { animation:none; }
399
+
400
+ /* Pop-in animation for role-help panel */
401
+ @keyframes popIn { 0%{opacity:0; transform:translateY(-8px) scale(.95);} 60%{opacity:1; transform:translateY(2px) scale(1.02);} 100%{opacity:1; transform:translateY(0) scale(1);} }
402
+ .role-help { animation:popIn .35s cubic-bezier(.23,1,.32,1); }
403
+
404
+ @media (max-width: 700px) {
405
+ .signup-box {
406
+ padding: 18px 6vw 18px 6vw;
407
+ width: 98vw;
408
+ }
409
+ .signup-header {
410
+ flex-direction: column;
411
+ align-items: flex-start;
412
+ width: 98vw;
413
+ padding: 0 0 12px 0;
414
+ }
415
+ .signup-title {
416
+ font-size: 1.3rem;
417
+ }
418
+ .signup-row {
419
+ flex-direction: column;
420
+ gap: 0;
421
+ }
422
+ .signup-login-link { font-size: .95rem; }
423
  }
424
 
425
+ /* Detached overlay modal (centered) */
426
+ .role-help-overlay { position:fixed; inset:0; background:rgba(15,33,50,.72); backdrop-filter:blur(4px); display:flex; align-items:center; justify-content:center; z-index:1400; animation:fadeInBackdrop .25s ease-out; }
427
+ .role-help-modal { position:relative; width:min(480px,90vw); background:#18314a; color:#e6f4fa; border:1px solid #27526b; border-radius:20px; padding:46px 34px 32px; box-shadow:0 18px 48px rgba(0,0,0,.55),0 0 0 1px #1d4358; animation:popIn .38s cubic-bezier(.23,1,.32,1); }
428
+ .role-help-title { margin:0 0 14px; font-size:1.55rem; font-weight:800; letter-spacing:.5px; color:#38bdf8; text-shadow:0 2px 8px #0008; }
429
+ .role-help-list { margin:0 0 14px 18px; padding:0; list-style:disc; }
430
+ .role-help-list li { margin:0 0 6px; line-height:1.4; }
431
+ .role-help-tip { margin:4px 0 0; font-size:.85rem; color:#9bd7ff; font-style:italic; }
432
+ .role-help-close { position:absolute; top:12px; right:12px; width:42px; height:42px; border:none; border-radius:50%; background:#102536; color:#38bdf8; font-size:1.5rem; font-weight:700; cursor:pointer; box-shadow:0 0 0 1px #1e3a4d; transition:background .25s,color .25s, transform .25s, box-shadow .25s; }
433
+ .role-help-close:hover { background:#38bdf8; color:#102536; box-shadow:0 0 0 2px #38bdf8,0 0 14px #38bdf8aa; transform:rotate(90deg); }
src/app/homepage/sign-up/sign-up.component.html CHANGED
@@ -1,98 +1,86 @@
1
- <section class="auth-page">
2
- <div class="auth-box" role="dialog" aria-labelledby="suTitle">
3
- <!-- Left (form) -->
4
- <div class="panel-left">
5
- <div class="topTitle">
6
- <img class="brand-mark" src="/assets/pykara-logo.png" alt="Brand" />
7
- <p class="topHeader"><b>Let's get started!</b></p>
 
 
 
 
 
 
 
 
 
 
 
 
8
  </div>
9
- <h2 id="suTitle" class="title">Create an account</h2>
10
-
11
- <form [formGroup]="form" (ngSubmit)="submit()" novalidate>
12
-
13
- <button class="btn btn-primary" type="submit">
14
- Sign up
15
- </button>
16
- <!-- Name -->
17
- <div class="field">
18
- <label for="name">
19
- Name
20
- <small *ngIf="controlHasError('name','required')" class="error">*</small>
21
- </label>
22
- <input id="name" type="text" placeholder="John"
23
- formControlName="name" [attr.aria-invalid]="controlHasError('name')" class="input-field" />
24
- <small *ngIf="controlHasError('name','minlength')" class="error">Enter at least 2 characters.</small>
25
  </div>
26
-
27
- <!-- Role dropdown (moved below Name, styled) -->
28
- <div class="field">
29
  <label for="role">
30
  Role
31
- <small *ngIf="controlHasError('role','required')" class="error">*</small>
32
  </label>
33
- <select id="role" formControlName="role"
34
- [attr.aria-invalid]="controlHasError('role')"
35
- class="input-field" required>
36
  <option value="">-- Select Role --</option>
 
37
  <option value="teachers">Teachers</option>
38
  <option value="lawyers">Lawyers</option>
39
  <option value="investigators">Investigators</option>
40
  <option value="others">Others</option>
41
  </select>
 
42
  </div>
43
-
44
- <!-- Email/Phone (contact) -->
45
- <div class="field">
46
- <label for="contact">
47
- Email
48
- <small *ngIf="controlHasError('contact','required')" class="error">*</small>
49
- </label>
50
- <input id="email" type="text" placeholder="hello@gmail.com" formControlName="email" [attr.aria-invalid]="controlHasError('email')" class="input-field" />
51
-
52
- <!--<input id="contact" type="text" placeholder="hello@gmail.com"
53
- formControlName="contact" [attr.aria-invalid]="controlHasError('contact')" class="input-field" />-->
54
- <small *ngIf="controlHasError('contact','pattern')" class="error">Enter a valid email/phone.</small>
55
- </div>
56
-
57
- <!-- Password -->
58
- <div class="field">
59
- <label for="password">
60
- Password
61
- <small *ngIf="controlHasError('password','required')" class="error">*</small>
62
- </label>
63
- <input id="password" type="password" placeholder="••••••••"
64
- formControlName="password" [attr.aria-invalid]="controlHasError('password')" />
65
- <small *ngIf="controlHasError('password','minlength')" class="error">Use at least 6 characters.</small>
66
  </div>
67
-
68
- <!-- Confirm password -->
69
- <div class="field">
70
- <label for="confirmPassword">
71
- Confirm password
72
- <small *ngIf="controlHasError('confirmPassword','required')" class="error">*</small>
73
- </label>
74
- <input id="confirmPassword" type="password" placeholder="••••••••"
75
- formControlName="confirmPassword" [attr.aria-invalid]="showPwdMismatch()" />
76
- <small *ngIf="showPwdMismatch()" class="error">Passwords do not match.</small>
77
  </div>
78
-
79
- <!--<button class="btn btn-primary" type="submit" [disabled]="form.invalid || submitting()" [attr.aria-busy]="submitting()">
80
- Sign up1
81
- </button>-->
82
-
83
- <p class="footnote">
84
- Already have an account?
85
- <!-- CHANGED: Use click handler instead of routerLink -->
86
- <a href="#" (click)="goToLogin(); $event.preventDefault()">Log in</a>
87
- </p>
88
- </form>
89
- </div>
90
-
91
- <!-- Right (image) -->
92
- <div class="panel-right">
93
- <div class="right-image">
94
- <img src="/assets/signup.png" alt="Decorative" />
95
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  </div>
97
  </div>
98
  </section>
 
1
+ <section class="signup-popup" (click)="hideRoleInfo()">
2
+ <div class="signup-header">
3
+ <div class="signup-logo"></div>
4
+ </div>
5
+ <div class="signup-box" (click)="$event.stopPropagation()">
6
+ <button class="signup-close" type="button" (click)="closePopup()" aria-label="Close">&times;</button>
7
+ <h2 class="signup-title">Create An Account</h2>
8
+ <form [formGroup]="form" (ngSubmit)="submit()" novalidate>
9
+ <div class="signup-row">
10
+ <div class="signup-field">
11
+ <label for="firstName">First Name</label>
12
+ <input id="firstName" type="text" placeholder="First Name" formControlName="name" [attr.aria-invalid]="controlHasError('name')" />
13
+ <small *ngIf="controlHasError('name','required') && form.get('name')?.touched" class="error">First name is required.</small>
14
+ <small *ngIf="controlHasError('name','minlength') && form.get('name')?.touched" class="error">Enter at least 2 characters.</small>
15
+ </div>
16
+ <div class="signup-field">
17
+ <label for="lastName">Last Name</label>
18
+ <input id="lastName" type="text" placeholder="Last Name" />
19
+ </div>
20
  </div>
21
+ <div class="signup-row">
22
+ <div class="signup-field">
23
+ <label for="email">Email</label>
24
+ <input id="email" type="text" placeholder="email@gmail.com" formControlName="email" [attr.aria-invalid]="controlHasError('email')" />
25
+ <small *ngIf="controlHasError('email','required') && form.get('email')?.touched" class="error">Email is required.</small>
26
+ <small *ngIf="controlHasError('email','pattern') && form.get('email')?.touched" class="error">Enter a valid email/phone.</small>
 
 
 
 
 
 
 
 
 
 
27
  </div>
28
+ <div class="signup-field role-field-wrapper">
 
 
29
  <label for="role">
30
  Role
31
+ <button type="button" class="role-info-btn" [class.glow-loop]="!showRoleInfo" (click)="toggleRoleInfo($event)" aria-label="Role descriptions">i</button>
32
  </label>
33
+ <select id="role" formControlName="role" [attr.aria-invalid]="controlHasError('role')">
 
 
34
  <option value="">-- Select Role --</option>
35
+ <option value="admin">Admin</option>
36
  <option value="teachers">Teachers</option>
37
  <option value="lawyers">Lawyers</option>
38
  <option value="investigators">Investigators</option>
39
  <option value="others">Others</option>
40
  </select>
41
+ <small *ngIf="controlHasError('role','required') && form.get('role')?.touched" class="error">Role is required.</small>
42
  </div>
43
+ </div>
44
+ <div class="signup-row">
45
+ <div class="signup-field">
46
+ <label for="password">Create Password</label>
47
+ <input id="password" type="password" placeholder="••••••••" formControlName="password" [attr.aria-invalid]="controlHasError('password')" />
48
+ <small *ngIf="controlHasError('password','required') && form.get('password')?.touched" class="error">Password is required.</small>
49
+ <small *ngIf="controlHasError('password','minlength') && form.get('password')?.touched" class="error">Use at least 6 characters.</small>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  </div>
51
+ <div class="signup-field">
52
+ <label for="confirmPassword">Confirm Password</label>
53
+ <input id="confirmPassword" type="password" placeholder="••••••••" formControlName="confirmPassword" [attr.aria-invalid]="showPwdMismatch()" />
54
+ <small *ngIf="controlHasError('confirmPassword','required') && form.get('confirmPassword')?.touched" class="error">Confirm password is required.</small>
55
+ <small *ngIf="showPwdMismatch() && form.get('confirmPassword')?.touched" class="error">Passwords do not match.</small>
 
 
 
 
 
56
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  </div>
58
+ <div class="signup-checkbox">
59
+ <input type="checkbox" id="terms" required />
60
+ <label for="terms">Creating your account and you accepting <a href="#">Terms & Conditions.</a></label>
61
+ </div>
62
+ <button class="signup-btn" type="submit">Create Account</button>
63
+ <div class="signup-login-link below">
64
+ <span>Already have an account?</span>
65
+ <a href="#" (click)="goToLogin(); $event.preventDefault()">Sign in here!</a>
66
+ </div>
67
+ </form>
68
+ <div class="signup-footer">&copy; Pykara Technologies, 2025. All rights reserved.</div>
69
+ </div>
70
+
71
+ <!-- Detached overlay/modal so layout doesn't shift -->
72
+ <div class="role-help-overlay" *ngIf="showRoleInfo" (click)="hideRoleInfo()">
73
+ <div class="role-help-modal" role="dialog" aria-label="Role descriptions" (click)="$event.stopPropagation()">
74
+ <button type="button" class="role-help-close" aria-label="Close role info" (click)="hideRoleInfo()">×</button>
75
+ <h3 class="role-help-title">Choose the right role</h3>
76
+ <ul class="role-help-list">
77
+ <li><strong>Admin:</strong> Full control: users, roles, system settings.</li>
78
+ <li><strong>Teachers:</strong> Run assessments / training evaluations.</li>
79
+ <li><strong>Lawyers:</strong> Review analytics for case & witness prep.</li>
80
+ <li><strong>Investigators:</strong> Conduct interviews and capture signals.</li>
81
+ <li><strong>Others:</strong> General or limited access usage.</li>
82
+ </ul>
83
+ <p class="role-help-tip">Not sure? Select Others; an Admin can upgrade your role later.</p>
84
  </div>
85
  </div>
86
  </section>
src/app/homepage/sign-up/sign-up.component.ts CHANGED
@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
3
  import { FormBuilder, FormGroup, ReactiveFormsModule, Validators, AbstractControl } from '@angular/forms';
4
  import { Router, RouterLink } from '@angular/router';
5
  import { SignUpService } from './sign-up.service'; // Import the SignUpService
 
6
 
7
  @Component({
8
  selector: 'app-sign-up',
@@ -15,20 +16,29 @@ export class SignUpComponent {
15
  form: FormGroup;
16
  private isSubmitting = false;
17
 
 
 
 
 
 
18
  @Output() switchToSignIn = new EventEmitter<void>();
 
19
 
20
  constructor(private fb: FormBuilder, private router: Router, private signUpService: SignUpService) { // Inject SignUpService
21
  this.form = this.fb.group({
22
  name: ['', [Validators.required, Validators.minLength(2)]],
23
  email: ['', [
24
  Validators.required,
25
- Validators.pattern(/(^[^@]+@[^@]+\.[^@]+$)|(^\+?\d[\d\-\s]{8,14}\d$)/)
26
  ]],
27
  //gender: ['', [Validators.required]],
28
  password: ['', [Validators.required, Validators.minLength(6)]],
29
  confirmPassword: ['', [Validators.required]],
30
  role: ['', [Validators.required]],
31
  }, { validators: [this.passwordsMatchValidator] });
 
 
 
32
  }
33
 
34
  control(path: string): AbstractControl | null { return this.form.get(path); }
@@ -52,8 +62,6 @@ export class SignUpComponent {
52
  return pw && cpw && pw === cpw ? null : { passwordMismatch: true };
53
  }
54
 
55
-
56
-
57
  async submit() {
58
  // Confirm button click
59
  alert("Sign-Up button clicked!");
@@ -93,23 +101,27 @@ export class SignUpComponent {
93
  await this.signUpService.signUp(payload).toPromise();
94
  console.log("Sign-up request sent successfully!");
95
 
96
- // On success, navigate to Sign-In page
97
- this.router.navigateByUrl('/auth/signin');
 
 
 
 
98
  } catch (error) {
99
  console.error("Error occurred during sign-up:", error); // Log any errors from the API call
100
  }
101
  }
102
 
103
-
104
-
105
-
106
-
107
  navigateHome() { this.router.navigateByUrl('/'); }
108
 
109
  goToLogin() {
110
  this.switchToSignIn.emit(); // ← Emit event instead of router navigation
111
  }
112
 
 
 
 
 
113
  tr(key: string): string {
114
  const map: Record<string, string> = { title: 'Create your account', subtitle: 'Join to continue' };
115
  return map[key] || '';
 
3
  import { FormBuilder, FormGroup, ReactiveFormsModule, Validators, AbstractControl } from '@angular/forms';
4
  import { Router, RouterLink } from '@angular/router';
5
  import { SignUpService } from './sign-up.service'; // Import the SignUpService
6
+ import { AuthService } from '../../auth.service';
7
 
8
  @Component({
9
  selector: 'app-sign-up',
 
16
  form: FormGroup;
17
  private isSubmitting = false;
18
 
19
+ // Added: state & handlers for role info popover used in template
20
+ showRoleInfo = false;
21
+ toggleRoleInfo(ev?: Event) { ev?.stopPropagation(); this.showRoleInfo = !this.showRoleInfo; }
22
+ hideRoleInfo() { this.showRoleInfo = false; }
23
+
24
  @Output() switchToSignIn = new EventEmitter<void>();
25
+ @Output() close = new EventEmitter<void>();
26
 
27
  constructor(private fb: FormBuilder, private router: Router, private signUpService: SignUpService) { // Inject SignUpService
28
  this.form = this.fb.group({
29
  name: ['', [Validators.required, Validators.minLength(2)]],
30
  email: ['', [
31
  Validators.required,
32
+ Validators.pattern(/(^[^@]+@[^@]+\.[^@]+$)|(^\+?\d[\d\-\s]{8,14}\d$)/)
33
  ]],
34
  //gender: ['', [Validators.required]],
35
  password: ['', [Validators.required, Validators.minLength(6)]],
36
  confirmPassword: ['', [Validators.required]],
37
  role: ['', [Validators.required]],
38
  }, { validators: [this.passwordsMatchValidator] });
39
+
40
+ // Close popover when clicking anywhere in document (capture phase not needed here)
41
+ document.addEventListener('click', () => this.hideRoleInfo());
42
  }
43
 
44
  control(path: string): AbstractControl | null { return this.form.get(path); }
 
62
  return pw && cpw && pw === cpw ? null : { passwordMismatch: true };
63
  }
64
 
 
 
65
  async submit() {
66
  // Confirm button click
67
  alert("Sign-Up button clicked!");
 
101
  await this.signUpService.signUp(payload).toPromise();
102
  console.log("Sign-up request sent successfully!");
103
 
104
+ // On success, navigate based on role
105
+ if (payload.role === 'admin') {
106
+ this.router.navigateByUrl('/infopage');
107
+ } else {
108
+ this.router.navigateByUrl('/auth/signin');
109
+ }
110
  } catch (error) {
111
  console.error("Error occurred during sign-up:", error); // Log any errors from the API call
112
  }
113
  }
114
 
 
 
 
 
115
  navigateHome() { this.router.navigateByUrl('/'); }
116
 
117
  goToLogin() {
118
  this.switchToSignIn.emit(); // ← Emit event instead of router navigation
119
  }
120
 
121
+ closePopup() {
122
+ this.close.emit();
123
+ }
124
+
125
  tr(key: string): string {
126
  const map: Record<string, string> = { title: 'Create your account', subtitle: 'Join to continue' };
127
  return map[key] || '';
src/app/homepage/sign-up/sign-up.service.ts CHANGED
@@ -6,15 +6,11 @@ import { Observable } from 'rxjs'; // Import Observable for async handling
6
  providedIn: 'root'
7
  })
8
  export class SignUpService {
9
-
10
- private baseUrl = location.hostname.endsWith('hf.space')
11
- ? 'https://pykara-py-detect-backend.hf.space'
12
- : 'http://localhost:5000';
13
- //private apiUrl = 'http://127.0.0.1:5000'; // The base URL for your Flask backend
14
  constructor(private http: HttpClient) { }
15
 
16
  // Method to sign up a user
17
  signUp(payload: any): Observable<any> {
18
- return this.http.post(`${this.baseUrl }/sign-up`, payload);
19
  }
20
  }
 
6
  providedIn: 'root'
7
  })
8
  export class SignUpService {
9
+ private apiUrl = 'http://127.0.0.1:5000'; // The base URL for your Flask backend
 
 
 
 
10
  constructor(private http: HttpClient) { }
11
 
12
  // Method to sign up a user
13
  signUp(payload: any): Observable<any> {
14
+ return this.http.post(`${this.apiUrl}/sign-up`, payload);
15
  }
16
  }
src/app/infopage/infopage.component.css CHANGED
@@ -1,362 +1,2153 @@
1
- body, html {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  margin: 0;
3
- padding: 0;
4
- font-family: 'Arial', 'Segoe UI', 'Roboto', sans-serif;
5
- background: url('/assets/background.jpg') no-repeat center center fixed !important;
6
- background-size: cover !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  }
8
 
9
- .homepage-container {
10
- display: flex;
11
- flex-direction: row;
12
- height: 100vh;
13
- color: #e3f6ff;
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
 
21
- .left-section {
22
- flex: 1;
23
- display: flex;
24
- flex-direction: column;
25
- justify-content: center; /* Center vertically */
26
- align-items: center; /* Center horizontally */
27
- padding-left: 120px; /* Reduced from 240px to 120px */
28
- padding-top: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  position: relative;
30
  }
31
 
32
- .topbar {
33
- display: flex;
34
- align-items: flex-start;
35
- height: 80px;
36
- padding-left: 0;
37
- width: 100%;
38
- position: absolute;
39
- top: 0;
40
- left: 0;
41
- z-index: 30;
 
 
42
  }
43
 
44
- .logo {
45
- width: 120px;
46
- height: 120px;
47
- margin-left: 0;
48
- margin-top: 0;
49
- position: absolute;
50
- left: 0;
51
- top: 0;
52
- z-index: 40;
53
  }
54
 
55
- .logo-fixed {
56
- position: fixed;
57
- top: 0;
58
- left: 0;
59
- width: 120px;
60
- height: 120px;
61
- z-index: 100;
62
- margin: 0;
 
63
  }
64
 
65
- /* Add gap between logo and browser border */
66
- .logo-img {
67
- width: 6vw;
68
- height: 6vw;
69
- border-radius: 50%;
70
- box-shadow: 0 0 15px rgba(255, 255, 255, 0.8);
71
- position: fixed;
72
- top: 18px; /* Add gap from top */
73
- left: 18px; /* Add gap from left */
74
- z-index: 300;
75
- margin: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  }
77
 
78
- /* Move Py-Detect text further down by increasing top value of .logo-title-row */
79
- .logo-title-row {
 
 
 
 
 
 
 
80
  display: flex;
81
- flex-direction: row;
82
  align-items: center;
83
- position: absolute;
84
- top: 180px; /* Increased from 120px to move text further down */
85
- left: 48px;
86
- z-index: 300;
87
- gap: 32px;
88
- justify-content: flex-start;
89
- width: 100%;
90
  }
91
 
92
- .py-detect-title {
93
- position: fixed;
94
- margin-left: 132px;
95
- margin-top: -208px;
96
- text-align: left;
97
- font-size: 4vw;
98
- color: #38bdf8;
99
- font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
100
- font-weight: 900;
101
- letter-spacing: 6px;
102
- background: none;
103
  border: none;
104
- box-shadow: none;
105
- min-width: unset;
106
- max-width: unset;
 
 
 
 
 
 
 
107
  }
108
 
109
- /* Py-Detect title: each letter with its own color */
110
- .py-detect-title .py-letter.p {
111
- color: #e3f6ff;
112
- text-shadow: 0 0 6px #38bdf8;
113
- }
 
114
 
115
- .py-detect-title .py-letter.y {
116
- color: #38bdf8;
117
- text-shadow: 0 0 6px #38bdf8;
118
- }
119
 
120
- .py-detect-title .py-shape {
121
- color: #e3f6ff;
122
- background: #e3f6ff;
123
- text-shadow: 0 0 6px #38bdf8;
124
- box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff;
125
- border: 2px solid #23272b;
126
- }
127
 
128
- .py-detect-title .py-letter.d {
129
- color: #e3f6ff;
130
- text-shadow: 0 0 6px #38bdf8;
131
- }
 
 
132
 
133
- .py-detect-title .py-letter.e {
134
- color: #38bdf8;
135
- text-shadow: 0 0 6px #38bdf8;
136
- }
 
 
137
 
138
- .py-detect-title .py-letter.t {
139
- color: #e3f6ff;
140
- text-shadow: 0 0 6px #38bdf8;
141
- }
 
142
 
143
- .py-detect-title .py-letter.e2 {
144
- color: #38bdf8;
145
- text-shadow: 0 0 6px #38bdf8;
146
- }
 
 
 
 
 
 
147
 
148
- .py-detect-title .py-letter.c {
149
- color: #e3f6ff;
150
- text-shadow: 0 0 6px #38bdf8;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
- .py-detect-title .py-letter.t2 {
154
- color: #38bdf8;
155
- text-shadow: 0 0 6px #38bdf8;
156
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
- .py-shape {
159
- display: inline-block;
160
- width: 18px;
161
- height: 4px;
162
- background: #38bdf8;
163
- margin: 0 8px;
164
- vertical-align: middle;
165
- border-radius: 2px;
166
- box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff;
167
- border: 2px solid #23272b;
168
- }
169
- /* ===== Layout ===== */
170
- .content {
171
- min-height: calc(100vh - var(--masthead-min-height));
172
- display: flex;
173
- justify-content: center;
174
- padding: 16px;
175
  }
176
 
177
- .wrapper {
178
- max-width: 960px;
179
- width: 100%;
180
- margin: 80px auto 0 auto; /* Add top margin to wrapper to create space below title */
 
181
  }
182
 
183
- .page-title {
184
- margin: 0 0 12px;
185
- color: #fff;
186
- font-weight: 800;
187
  }
188
 
189
- /* ===== Steps / Card / Form ===== */
190
- .steps {
191
- display: flex;
192
- gap: 8px;
193
- margin-bottom: 16px;
194
  }
195
 
196
- .step {
197
- flex: 1;
198
- text-align: center;
199
- padding: 10px 8px;
200
- border-radius: 8px;
201
- background: #e9ecef;
202
- font-weight: 600;
203
- opacity: 0.8;
 
 
204
  }
205
 
206
- .step.active {
207
- background: #2f855a;
208
- color: #fff;
209
- opacity: 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  }
211
 
212
- .card {
213
- border: 1px solid #e2e8f0;
214
- border-radius: 10px;
215
- padding: 16px;
216
- background: #fff;
217
- box-shadow: 0 2px 10px rgba(0,0,0,.04);
218
- margin-bottom: 16px;
219
- width: 100%;
 
 
 
220
  }
221
 
222
- .grid {
223
- display: grid;
224
- grid-template-columns: repeat(2, minmax(0, 1fr));
225
- gap: 12px 16px;
226
  }
227
 
228
- label {
 
229
  display: flex;
230
- flex-direction: column; /* input below the label text */
231
- gap: 6px;
232
- font-size: 14px;
 
233
  }
234
 
235
- /* Put label text and * on the same line */
236
- .label-main {
237
- display: inline-flex;
 
 
 
 
 
 
238
  align-items: center;
239
- gap: 4px;
240
- font-weight: 600;
 
 
 
 
 
241
  }
242
 
243
- label.full {
244
- grid-column: 1 / -1;
 
245
  }
246
 
247
- input, select, textarea, button {
248
- font: inherit;
 
249
  }
250
 
251
- input, select, textarea {
252
- padding: 10px;
253
- border: 1px solid #cbd5e0;
254
- border-radius: 6px;
255
- background: #fdfdfd;
256
  }
257
 
258
- textarea {
259
- resize: vertical;
 
 
260
  }
261
 
262
- fieldset {
263
- border: 1px dashed #cbd5e0;
264
- border-radius: 8px;
265
- padding: 10px 12px;
266
  }
267
 
268
- legend {
269
- padding: 0 6px;
 
 
270
  }
271
 
272
- .req {
273
- color: #c53030;
274
- line-height: 1;
275
  }
276
 
277
- .error {
278
- color: #c53030;
279
- font-size: 12px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  }
281
 
282
- .row-between {
 
283
  display: flex;
 
284
  justify-content: space-between;
 
 
 
 
 
 
 
 
 
285
  align-items: center;
 
286
  }
287
 
288
- .list {
 
 
 
289
  display: flex;
290
- flex-direction: column;
291
  gap: 8px;
292
- margin-top: 8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  }
294
 
295
- .list-item {
296
  display: flex;
 
 
 
 
 
 
 
 
 
 
297
  gap: 8px;
 
 
 
 
 
298
  }
299
 
300
- button.link {
301
  background: none;
302
  border: none;
303
- color: #2b6cb0;
 
304
  cursor: pointer;
305
- padding: 0;
 
 
 
306
  }
307
 
308
- button.danger {
309
- background: #e53e3e;
310
- color: #fff;
311
- border: none;
312
- border-radius: 6px;
313
- padding: 8px 10px;
 
 
 
 
 
 
 
 
 
 
314
  cursor: pointer;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  }
316
 
317
- .files {
318
- margin: 6px 0 0;
319
- padding-left: 16px;
320
  }
321
 
322
- .actions {
323
  display: flex;
324
- justify-content: flex-end;
325
  gap: 8px;
326
  }
327
 
328
- button {
329
- padding: 10px 14px;
330
- border: 1px solid #cbd5e0;
331
  border-radius: 8px;
332
- background: #f7fafc;
 
 
333
  cursor: pointer;
 
 
 
 
 
334
  }
335
 
336
- button.primary {
337
- background: #2f855a;
338
- color: #fff;
339
- border-color: #2f855a;
340
- }
341
-
342
- button:disabled {
343
- opacity: 0.6;
344
- cursor: not-allowed;
345
- }
346
 
347
- .scrollable-suspect-form {
348
- max-height: 350px;
349
- overflow-y: auto;
350
- padding-right: 8px; /* Optional: for scrollbar space */
 
 
 
351
  }
352
 
353
- @media (max-width: 700px) {
354
- .grid {
355
- grid-template-columns: 1fr;
356
- }
 
 
 
 
 
 
357
 
358
- .brand-logo {
359
- width: 64px;
360
- height: 64px;
361
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  }
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ===== BALANCED AI DESIGN WITH READABLE CONTENT ===== */
2
+
3
+ /* ===== IMPROVED BODY BACKGROUND ===== */
4
+ body {
5
+ background:
6
+ radial-gradient(ellipse at top left, rgba(0, 212, 255, 0.08), transparent 40%),
7
+ radial-gradient(ellipse at bottom right, rgba(255, 0, 110, 0.06), transparent 40%),
8
+ linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f0f23 100%) !important;
9
+ background-attachment: fixed !important;
10
+ color: #e0e6ed !important;
11
+ font-family: 'Inter', 'Segoe UI', system-ui, sans-serif !important;
12
+ overflow-x: hidden !important;
13
+ }
14
+
15
+ /* ===== SUBTLE PARTICLE BACKGROUND ===== */
16
+ body::before {
17
+ content: '' !important;
18
+ position: fixed !important;
19
+ top: 0 !important;
20
+ left: 0 !important;
21
+ width: 100vw !important;
22
+ height: 100vh !important;
23
+ background-image:
24
+ radial-gradient(circle at 20% 30%, rgba(0, 212, 255, 0.15) 1px, transparent 1px),
25
+ radial-gradient(circle at 80% 70%, rgba(255, 0, 110, 0.12) 1px, transparent 1px) !important;
26
+ background-size: 150px 150px, 200px 200px !important;
27
+ animation: subtleParticles 20s linear infinite !important;
28
+ pointer-events: none !important;
29
+ z-index: -1 !important;
30
+ opacity: 0.4 !important;
31
+ }
32
+
33
+ @keyframes subtleParticles {
34
+ 0% { background-position: 0 0, 0 0; }
35
+ 100% { background-position: 150px 150px, -200px -200px; }
36
+ }
37
+
38
+ /* ===== ENHANCED HEADER WITH READABILITY ===== */
39
+ .site-header {
40
+ background: #011329;
41
+ box-shadow: 0 2px 12px #38bdf844;
42
+ margin-bottom: 18px;
43
+ position: relative;
44
+ z-index: 10;
45
+ padding-bottom: 0;
46
+ }
47
+
48
+ .header-inner {
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: space-between;
52
+ padding: 18px 32px 0 32px;
53
+ position: relative;
54
+ gap: 32px;
55
+ }
56
+
57
+ .logo-cluster {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 18px;
61
+ flex: 1 1 auto;
62
+ justify-content: center;
63
+ }
64
+
65
+ .logo-img-header {
66
+ width: 54px;
67
+ height: 54px;
68
+ border-radius: 50%;
69
+ background: #fff;
70
+ box-shadow: 0 2px 8px rgba(0,0,0,0.18);
71
+ padding: 4px;
72
+ margin-top: -6px; /* Move logo slightly upwards */
73
+ margin-bottom: 0;
74
+ }
75
+
76
+ .py-detect-title-header {
77
+ font-size: 2.1rem;
78
+ font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
79
+ font-weight: 900;
80
+ letter-spacing: 6px;
81
+ color: #38bdf8;
82
+ display: flex;
83
+ align-items: center;
84
+ gap: 2px;
85
+ margin-bottom: 1.5vh;
86
+ }
87
+
88
+ .py-detect-title-header .py-letter.p { color: #e3f6ff; text-shadow: 0 0 6px #38bdf8; }
89
+ .py-detect-title-header .py-letter.y { color: #38bdf8; text-shadow: 0 0 6px #38bdf8; }
90
+ .py-detect-title-header .py-shape { color: #e3f6ff; background: #e3f6ff; text-shadow: 0 0 6px #38bdf8; box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff; border: 2px solid #23272b; width: 18px; height: 4px; display: inline-block; margin: 0 8px; border-radius: 2px; }
91
+ .py-detect-title-header .py-letter.d { color: #e3f6ff; text-shadow: 0 0 6px #38bdf8; }
92
+ .py-detect-title-header .py-letter.e { color: #38bdf8; text-shadow: 0 0 6px #38bdf8; }
93
+ .py-detect-title-header .py-letter.t { color: #e3f6ff; text-shadow: 0 0 6px #38bdf8; }
94
+ .py-detect-title-header .py-letter.e2 { color: #38bdf8; text-shadow: 0 0 6px #38bdf8; }
95
+ .py-detect-title-header .py-letter.c { color: #e3f6ff; text-shadow: 0 0 6px #38bdf8; }
96
+ .py-detect-title-header .py-letter.t2 { color: #38bdf8; text-shadow: 0 0 6px #38bdf8; }
97
+
98
+ .header-progress {
99
+ display: flex;
100
+ align-items: center;
101
+ gap: 14px;
102
+ margin-right: 32px;
103
+ margin-left:89vh;
104
+ }
105
+
106
+ .pykara-analysis-label {
107
+ color: #38bdf8;
108
+ font-size: 1.1em;
109
+ font-weight: 700;
110
+ letter-spacing: 1px;
111
+ }
112
+
113
+ .pykara-progress-bar {
114
+ width: 220px;
115
+ height: 14px;
116
+ background: #e3f6ff;
117
+ border-radius: 8px;
118
+ overflow: hidden;
119
+ box-shadow: 0 2px 8px #38bdf844, 0 0 12px #38bdf8aa;
120
+ position: relative;
121
+ }
122
+
123
+ .pykara-progress-bar-inner {
124
+ height: 100%;
125
+ background: linear-gradient(270deg, #38bdf8, #06ffa5, #ff006e, #38bdf8);
126
+ background-size: 400% 100%;
127
+ border-radius: 8px 0 0 8px;
128
+ transition: width 0.4s cubic-bezier(.4,2,.6,1);
129
+ animation: progressBarGradientMove 2.5s linear infinite;
130
+ box-shadow: 0 0 16px #38bdf8cc, 0 0 8px #06ffa5aa;
131
+ }
132
+
133
+ @keyframes progressBarGradientMove {
134
+ 0% { background-position: 0% 50%; }
135
+ 100% { background-position: 100% 50%; }
136
+ }
137
+
138
+ .pykara-progress-percentage {
139
+ color: #38bdf8;
140
+ font-size: 1.05em;
141
+ font-weight: 700;
142
+ margin-left: 4px;
143
+ }
144
+
145
+ /* ===== READABLE PROGRESS BAR ===== */
146
+ .progress-container {
147
+ background: rgba(26, 26, 46, 0.9) !important;
148
+ backdrop-filter: blur(10px) !important;
149
+ padding: 8px 24px !important;
150
+ position: relative !important;
151
+ }
152
+
153
+ .progress-bar {
154
+ background: linear-gradient(90deg, #00d4ff 0%, #06ffa5 50%, #ff006e 100%) !important;
155
+ height: 6px !important;
156
+ border-radius: 3px !important;
157
+ position: relative !important;
158
+ box-shadow: 0 0 10px rgba(0, 212, 255, 0.4) !important;
159
+ }
160
+
161
+ .progress-text {
162
+ display: flex !important;
163
+ justify-content: space-between !important;
164
+ align-items: center !important;
165
+ font-size: 0.8rem !important;
166
+ color: #e0e6ed !important;
167
+ margin-bottom: 4px !important;
168
+ }
169
+
170
+ .progress-ai {
171
+ color: #06ffa5 !important;
172
+ font-weight: 600 !important;
173
+ }
174
+
175
+ /* ===== READABLE SECTION NAVIGATION ===== */
176
+ .section-navigation {
177
+ background: linear-gradient(135deg, rgba(26, 26, 46, 0.9), rgba(22, 33, 62, 0.95)) !important;
178
+ backdrop-filter: blur(15px) !important;
179
+ border-bottom: 1px solid rgba(0, 212, 255, 0.2) !important;
180
+ padding: 16px 0 !important;
181
+ position: relative !important;
182
+ }
183
+
184
+ .section-pills {
185
+ display: flex !important;
186
+ justify-content: center !important;
187
+ gap: 16px !important;
188
+ max-width: 1200px !important;
189
+ margin: 0 auto !important;
190
+ padding: 0 24px !important;
191
+ }
192
+
193
+ .section-pill {
194
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05)) !important;
195
+ border: 1px solid rgba(0, 212, 255, 0.3) !important;
196
+ backdrop-filter: blur(10px) !important;
197
+ padding: 12px 20px !important;
198
+ border-radius: 12px !important;
199
+ color: #e0e6ed !important;
200
+ font-weight: 500 !important;
201
+ font-size: 0.9rem !important;
202
+ cursor: pointer !important;
203
+ transition: all 0.3s ease !important;
204
+ position: relative !important;
205
+ display: flex !important;
206
+ align-items: center !important;
207
+ gap: 8px !important;
208
+ min-width: 150px !important;
209
+ justify-content: center !important;
210
+ }
211
+
212
+ .section-pill:hover {
213
+ border-color: rgba(0, 212, 255, 0.6) !important;
214
+ box-shadow: 0 0 20px rgba(0, 212, 255, 0.3) !important;
215
+ transform: translateY(-2px) !important;
216
+ }
217
+
218
+ .section-pill.active {
219
+ background: linear-gradient(135deg, rgba(0, 212, 255, 0.2), rgba(255, 0, 110, 0.1)) !important;
220
+ border-color: #00d4ff !important;
221
+ color: white !important;
222
+ box-shadow: 0 0 25px rgba(0, 212, 255, 0.4) !important;
223
+ transform: translateY(-3px) !important;
224
+ }
225
+
226
+ .section-pill::after {
227
+ content: attr(data-step) !important;
228
+ position: absolute !important;
229
+ top: -8px !important;
230
+ left: 12px !important;
231
+ width: 20px !important;
232
+ height: 20px !important;
233
+ background: linear-gradient(135deg, #7209b7, #4cc9f0) !important;
234
+ color: white !important;
235
+ border-radius: 50% !important;
236
+ display: flex !important;
237
+ align-items: center !important;
238
+ justify-content: center !important;
239
+ font-size: 0.7rem !important;
240
+ font-weight: 700 !important;
241
+ border: 2px solid rgba(26, 26, 46, 0.9) !important;
242
+ }
243
+
244
+ .section-pill.active::after {
245
+ background: linear-gradient(135deg, #ff006e, #8338ec) !important;
246
+ animation: activeStep 2s ease-in-out infinite !important;
247
+ }
248
+
249
+ @keyframes activeStep {
250
+ 0%, 100% { transform: scale(1); }
251
+ 50% { transform: scale(1.1); }
252
+ }
253
+
254
+ /* ===== READABLE SUBGROUP PILLS ===== */
255
+ .subgroup-pills {
256
+ background: linear-gradient(135deg, rgba(22, 33, 62, 0.9), rgba(26, 26, 46, 0.95)) !important;
257
+ backdrop-filter: blur(15px) !important;
258
+ border-bottom: 1px solid rgba(0, 212, 255, 0.2) !important;
259
+ padding: 16px 24px !important;
260
+ display: flex !important;
261
+ justify-content: center !important;
262
+ flex-wrap: wrap !important;
263
+ gap: 12px !important;
264
+ max-width: 1200px !important;
265
+ margin: 0 auto !important;
266
+ }
267
+
268
+ .subgroup-pills .pill {
269
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.04)) !important;
270
+ border: 1px solid rgba(0, 212, 255, 0.25) !important;
271
+ backdrop-filter: blur(8px) !important;
272
+ padding: 8px 16px !important;
273
+ border-radius: 20px !important;
274
+ color: #e0e6ed !important;
275
+ font-weight: 500 !important;
276
+ font-size: 0.85rem !important;
277
+ cursor: pointer !important;
278
+ transition: all 0.3s ease !important;
279
+ display: flex !important;
280
+ align-items: center !important;
281
+ gap: 6px !important;
282
+ min-width: 120px !important;
283
+ justify-content: center !important;
284
+ }
285
+
286
+ .subgroup-pills .pill:hover {
287
+ border-color: rgba(6, 255, 165, 0.6) !important;
288
+ box-shadow: 0 0 15px rgba(6, 255, 165, 0.3) !important;
289
+ transform: translateY(-1px) !important;
290
+ }
291
+
292
+ .subgroup-pills .pill.active {
293
+ background: linear-gradient(135deg, rgba(255, 0, 110, 0.2), rgba(131, 56, 236, 0.15)) !important;
294
+ border-color: #ff006e !important;
295
+ color: white !important;
296
+ box-shadow: 0 0 20px rgba(255, 0, 110, 0.4) !important;
297
+ transform: translateY(-2px) !important;
298
+ }
299
+
300
+ /* ===== READABLE CONTENT AREA ===== */
301
+ .investigation-container {
302
+ background: transparent !important;
303
+ padding: 24px 0 !important;
304
+ max-width: 100vw !important;
305
+ width: 96vw !important;
306
+ margin: 0 !important;
307
+ min-height: calc(100vh - 400px) !important;
308
+ position: relative !important;
309
+ left: 50% !important;
310
+ right: 50% !important;
311
+ margin-left: -48vw !important;
312
+ margin-right: -50vw !important;
313
+ box-sizing: border-box !important;
314
+ }
315
+
316
+ /* ===== FULL-WIDTH FORM CARD ===== */
317
+ .form-card {
318
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.92)) !important;
319
+ backdrop-filter: blur(20px) !important;
320
+ border: 1px solid rgba(0, 212, 255, 0.2) !important;
321
+ border-radius: 0 !important;
322
+ box-shadow:
323
+ 0 8px 32px rgba(0, 0, 0, 0.1),
324
+ 0 0 0 1px rgba(255, 255, 255, 0.2) !important;
325
+ color: #2C3E50 !important;
326
+ position: relative !important;
327
+ overflow: visible !important;
328
+ margin: 0 !important;
329
+ width: 100% !important;
330
+ max-width: 100% !important;
331
+ padding: 0 24px !important;
332
+ box-sizing: border-box !important;
333
+ }
334
+
335
+ .form-card::before {
336
+ content: '' !important;
337
+ position: absolute !important;
338
+ top: 0 !important;
339
+ left: -100% !important;
340
+ width: 100% !important;
341
+ height: 2px !important;
342
+ background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.6), transparent) !important;
343
+ animation: cardScan 4s linear infinite !important;
344
+ }
345
+
346
+ @keyframes cardScan {
347
+ 0% { left: -100%; }
348
+ 100% { left: 100%; }
349
+ }
350
+
351
+ .card-header {
352
+ background: linear-gradient(135deg, rgba(0, 212, 255, 0.1), rgba(255, 0, 110, 0.05)) !important;
353
+ border-bottom: 1px solid rgba(0, 212, 255, 0.2) !important;
354
+ padding: 16px 20px !important;
355
+ border-radius: 0 !important;
356
+ margin: 0 -24px !important;
357
+ margin-bottom: 0 !important;
358
+ }
359
+
360
+ .card-header h2 {
361
+ background: linear-gradient(45deg, #00d4ff, #ff006e) !important;
362
+ -webkit-background-clip: text !important;
363
+ -webkit-text-fill-color: transparent !important;
364
+ background-clip: text !important;
365
+ font-size: 1.1rem !important;
366
+ font-weight: 700 !important;
367
+ margin: 0 !important;
368
+ display: flex !important;
369
+ align-items: center !important;
370
+ gap: 8px !important;
371
+ }
372
+
373
+ .card-content {
374
+ padding: 20px 0 !important;
375
+ background: rgba(255, 255, 255, 0.98) !important;
376
+ color: #2C3E50 !important;
377
+ width: 100% !important;
378
+ max-width: 100% !important;
379
+ box-sizing: border-box !important;
380
+ }
381
+
382
+ .section-description {
383
+ background: linear-gradient(135deg, rgba(0, 212, 255, 0.1), rgba(6, 255, 165, 0.08)) !important;
384
+ border: 1px solid rgba(0, 212, 255, 0.2) !important;
385
+ border-radius: 8px !important;
386
+ padding: 12px 16px !important;
387
+ margin-bottom: 20px !important;
388
+ font-size: 0.85rem !important;
389
+ color: #2C3E50 !important;
390
+ border-left: 4px solid #00d4ff !important;
391
+ width: 100% !important;
392
+ max-width: 100% !important;
393
+ box-sizing: border-box !important;
394
+ }
395
+
396
+ /* ===== FULL-WIDTH FORM FIELDS ===== */
397
+ .fields-grid {
398
+ display: grid !important;
399
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)) !important;
400
+ gap: 16px !important;
401
+ margin-top: 16px !important;
402
+ width: 100% !important;
403
+ max-width: 100% !important;
404
+ box-sizing: border-box !important;
405
+ padding: 0 !important;
406
+ overflow: visible !important;
407
+ position: relative;
408
+ }
409
+
410
+ .field-container {
411
+ background: rgba(255, 255, 255, 0.9) !important;
412
+ border: 1px solid rgba(0, 212, 255, 0.2) !important;
413
+ border-radius: 8px !important;
414
+ padding: 12px !important;
415
+ transition: all 0.3s ease !important;
416
+ position: relative !important;
417
+ overflow: visible !important;
418
+ z-index: 1;
419
+ width: 100% !important;
420
+ max-width: 100% !important;
421
+ box-sizing: border-box !important;
422
+ }
423
+
424
+ .field-container:hover {
425
+ border-color: rgba(0, 212, 255, 0.4) !important;
426
+ box-shadow: 0 0 15px rgba(0, 212, 255, 0.1) !important;
427
+ transform: translateY(-1px) !important;
428
+ }
429
+
430
+ .field-label {
431
+ color: #2C3E50 !important;
432
+ font-weight: 600 !important;
433
+ font-size: 0.85rem !important;
434
+ margin-bottom: 6px !important;
435
+ display: flex !important;
436
+ align-items: center !important;
437
+ gap: 6px !important;
438
+ width: 100% !important;
439
+ }
440
+
441
+ .field-input {
442
+ width: 100% !important;
443
+ padding: 10px 12px !important;
444
+ border: 1px solid #E1E8ED !important;
445
+ border-radius: 6px !important;
446
+ background: white !important;
447
+ color: #2C3E50 !important;
448
+ font-size: 0.85rem !important;
449
+ transition: all 0.3s ease !important;
450
+ box-sizing: border-box !important;
451
+ }
452
+
453
+ .field-input:focus {
454
+ border-color: #00d4ff !important;
455
+ box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.1) !important;
456
+ outline: none !important;
457
+ }
458
+
459
+ .field-input::placeholder {
460
+ color: #7F8C8D !important;
461
+ }
462
+
463
+ /* ===== CARD ACTIONS FULL WIDTH ===== */
464
+ .card-actions {
465
+ display: flex !important;
466
+ align-items: center !important;
467
+ gap: 12px !important;
468
+ justify-content: flex-start !important;
469
+ width: 100% !important;
470
+ flex-wrap: wrap !important;
471
+ position: relative;
472
+ }
473
+
474
+ .card-actions-spacer {
475
+ flex: 1 1 auto;
476
+ }
477
+
478
+ .field-selector-container {
479
+ display: flex !important;
480
+ align-items: center !important;
481
+ justify-content: flex-end !important;
482
+ gap: 12px !important;
483
+ width: 100% !important;
484
+ }
485
+
486
+ .modern-field-selector-btn {
487
+ display: flex;
488
+ align-items: center;
489
+ justify-content: flex-end;
490
+ margin-left: auto;
491
+ position: relative;
492
+ }
493
+
494
+ .field-selector-btn.modern-selector-animated {
495
+ border-radius: 24px;
496
+ background: linear-gradient(135deg, #00d4ff 0%, #06ffa5 60%, #ff006e 100%);
497
+ color: #fff;
498
+ font-weight: 700;
499
+ font-size: 1rem;
500
+ padding: 10px 22px;
501
+ border: none;
502
+ box-shadow: 0 2px 12px rgba(0, 212, 255, 0.10);
503
+ display: flex;
504
+ align-items: center;
505
+ gap: 10px;
506
+ cursor: pointer;
507
+ transition: background 0.18s, box-shadow 0.18s, transform 0.18s;
508
+ outline: none;
509
+ position: relative;
510
+ z-index: 2;
511
+ animation: selectorPulse 2s infinite alternate;
512
+ }
513
+
514
+ .field-selector-btn.modern-selector-animated:hover,
515
+ .field-selector-btn.modern-selector-animated.active {
516
+ background: linear-gradient(135deg, #06ffa5 0%, #00d4ff 60%, #ff006e 100%);
517
+ box-shadow: 0 4px 24px rgba(0, 212, 255, 0.18);
518
+ transform: scale(1.04);
519
+ }
520
+
521
+ @keyframes selectorPulse {
522
+ 0% { box-shadow: 0 2px 12px rgba(0, 212, 255, 0.10); }
523
+ 100% { box-shadow: 0 6px 24px rgba(0, 212, 255, 0.18); }
524
+ }
525
+
526
+ .field-selector-btn.modern-selector-animated i {
527
+ font-size: 1.2em;
528
+ }
529
+
530
+ /* Highlight filled field-container with green border */
531
+ .field-container.filled {
532
+ border-color: #06ffa5 !important;
533
+ box-shadow: 0 0 0 2px rgba(6,255,165,0.12);
534
+ transition: border-color 0.25s, box-shadow 0.25s;
535
+ }
536
+
537
+ /* ===== REMOVE BODY OVERFLOW ===== */
538
+ body {
539
+ overflow-x: hidden !important;
540
+ width: 100vw !important;
541
+ box-sizing: border-box !important;
542
+ }
543
+
544
+ html {
545
+ overflow-x: hidden !important;
546
+ width: 100vw !important;
547
+ box-sizing: border-box !important;
548
+ }
549
+
550
+ /* ===== ENSURE ALL CONTAINERS ARE FULL WIDTH ===== */
551
+ .site-header,
552
+ .section-navigation,
553
+ .subgroup-pills,
554
+ .page-indicator-container {
555
+ width: 100vw !important;
556
+ max-width: 100vw !important;
557
+ margin: 0 !important;
558
+ box-sizing: border-box !important;
559
+ }
560
+
561
+ /* ===== RESPONSIVE FULL WIDTH ===== */
562
+ @media (max-width: 768px) {
563
+ .investigation-container {
564
+ padding: 16px 0 !important;
565
+ margin-left: -50vw !important;
566
+ margin-right: -50vw !important;
567
+ left: 50% !important;
568
+ right: 50% !important;
569
+ }
570
+
571
+ .form-card {
572
+ padding: 0 16px !important;
573
+ border-radius: 0 !important;
574
+ }
575
+
576
+ .card-header {
577
+ margin: 0 -16px !important;
578
+ padding: 12px 16px !important;
579
+ }
580
+
581
+ .fields-grid {
582
+ grid-template-columns: 1fr !important;
583
+ gap: 12px !important;
584
+ }
585
+
586
+ .card-actions {
587
+ flex-direction: column !important;
588
+ align-items: stretch !important;
589
+ gap: 8px !important;
590
+ }
591
+
592
+ .field-selector-container {
593
+ width: 100% !important;
594
+ }
595
+
596
+ .field-selector-btn {
597
+ width: 100% !important;
598
+ justify-content: center !important;
599
+ }
600
+ }
601
+
602
+ @media (max-width: 480px) {
603
+ .investigation-container {
604
+ padding: 12px 0 !important;
605
+ }
606
+
607
+ .form-card {
608
+ padding: 0 12px !important;
609
+ }
610
+
611
+ .card-header {
612
+ margin: 0 -12px !important;
613
+ padding: 10px 12px !important;
614
+ }
615
+
616
+ .card-content {
617
+ padding: 16px 0 !important;
618
+ }
619
+ }
620
+
621
+ /* Crime Details style for autosave button */
622
+ .autosave-indicator.crime-section-btn {
623
+ background: linear-gradient(135deg, #E8F4FD 0%, #F0F8FF 100%) !important;
624
+ border: 2px solid #4A90E2 !important;
625
+ color: #357ABD !important;
626
+ font-weight: 600 !important;
627
+ border-radius: 20px !important;
628
+ box-shadow: 0 2px 6px rgba(74, 144, 226, 0.08) !important;
629
+ padding: 10px 20px !important;
630
+ transition: all 0.3s;
631
+ display: flex !important;
632
+ align-items: center !important;
633
+ gap: 8px !important;
634
+ }
635
+
636
+ .autosave-indicator.crime-section-btn:hover {
637
+ border-color: #357ABD !important;
638
+ color: #fff !important;
639
+ background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%) !important;
640
+ box-shadow: 0 4px 12px rgba(74, 144, 226, 0.25) !important;
641
+ }
642
+
643
+ .autosave-right {
644
+ margin-left: auto !important;
645
+ margin-right: 0 !important;
646
+ }
647
+
648
+ .autosave-left {
649
+ margin-right: auto !important;
650
+ margin-left: 0 !important;
651
+ }
652
+
653
+ .header-actions {
654
+ display: flex !important;
655
+ align-items: center !important;
656
+ gap: 16px !important;
657
+ }
658
+
659
+ .header-actions.autosave-right {
660
+ display: flex !important;
661
+ justify-content: flex-end !important;
662
+ width: 100%;
663
+ }
664
+
665
+ .autosave-indicator {
666
+ display: flex !important;
667
+ align-items: center !important;
668
+ gap: 8px !important;
669
+ padding: 8px 16px !important;
670
+ background: rgba(255, 255, 255, 0.15) !important;
671
+ border: 1px solid rgba(255, 255, 255, 0.2) !important;
672
+ border-radius: 8px !important;
673
+ font-size: 0.875rem !important;
674
+ color: white !important;
675
+ font-weight: 500 !important;
676
+ backdrop-filter: blur(10px) !important;
677
+ }
678
+
679
+ .autosave-indicator.saving {
680
+ background: rgba(255, 255, 255, 0.25) !important;
681
+ animation: pulse 1.5s ease-in-out infinite !important;
682
+ }
683
+
684
+ .main-section-pill {
685
+ font-size: 1.25rem !important;
686
+ padding: 20px 36px !important;
687
+ min-width: 200px !important;
688
+ min-height: 64px !important;
689
+ border-width: 2.5px !important;
690
+ box-shadow: 0 4px 24px rgba(0, 212, 255, 0.18) !important;
691
+ }
692
+
693
+ .main-section-pill i {
694
+ font-size: 2rem !important;
695
+ }
696
+
697
+ .main-section-pill span {
698
+ font-size: 1.25em !important;
699
+ font-weight: 700 !important;
700
+ }
701
+
702
+ /* ===== MODERN NAVIGATION BUTTONS ===== */
703
+ .modern-nav-btns {
704
+ display: flex;
705
+ justify-content: flex-end;
706
+ gap: 18px;
707
+ margin-top: 32px;
708
+ margin-bottom: 8px;
709
+ }
710
+
711
+ .modern-round-btn {
712
+ width: 56px;
713
+ height: 56px;
714
+ border-radius: 50%;
715
+ border: none;
716
+ background: linear-gradient(135deg, #00d4ff 0%, #06ffa5 60%, #ff006e 100%);
717
+ color: #fff;
718
+ font-size: 2rem;
719
+ display: flex;
720
+ align-items: center;
721
+ justify-content: center;
722
+ box-shadow: 0 4px 24px rgba(0, 212, 255, 0.18), 0 1.5px 6px rgba(0,0,0,0.08);
723
+ cursor: pointer;
724
+ transition: transform 0.18s cubic-bezier(.4,2,.6,1), box-shadow 0.18s;
725
+ outline: none;
726
+ position: relative;
727
+ z-index: 2;
728
+ }
729
+
730
+ .modern-round-btn:active {
731
+ transform: scale(0.93);
732
+ box-shadow: 0 2px 8px rgba(0, 212, 255, 0.12);
733
+ }
734
+
735
+ .modern-round-btn:disabled {
736
+ opacity: 0.5;
737
+ cursor: not-allowed;
738
+ }
739
+
740
+ .next-btn-animated {
741
+ animation: bounceRight 1.2s infinite alternate;
742
+ }
743
+
744
+ @keyframes bounceRight {
745
+ 0% { transform: translateX(0); }
746
+ 60% { transform: translateX(6px) scale(1.08); }
747
+ 100% { transform: translateX(0); }
748
+ }
749
+
750
+ .submit-btn-animated {
751
+ animation: pulseSend 1.5s infinite alternate;
752
+ }
753
+
754
+ @keyframes pulseSend {
755
+ 0% { box-shadow: 0 4px 24px rgba(0, 212, 255, 0.18); }
756
+ 60% { box-shadow: 0 8px 32px rgba(255, 0, 110, 0.22); }
757
+ 100% { box-shadow: 0 4px 24px rgba(0, 212, 255, 0.18); }
758
+ }
759
+
760
+ .modern-round-btn i {
761
+ font-size: 2rem;
762
+ pointer-events: none;
763
+ }
764
+
765
+ /* ===== FLOATING NAVIGATION BUTTON ===== */
766
+ .modern-floating-nav-btn {
767
+ position: fixed;
768
+ bottom: 32px;
769
+ right: 32px;
770
+ z-index: 2000;
771
+ display: flex;
772
+ flex-direction: column;
773
+ gap: 16px;
774
+ pointer-events: none;
775
+ }
776
+
777
+ .modern-floating-nav-btn .modern-round-btn {
778
+ pointer-events: auto;
779
+ }
780
+
781
+ @media (max-width: 600px) {
782
+ .modern-floating-nav-btn {
783
+ bottom: 16px;
784
+ right: 16px;
785
+ }
786
+ }
787
+
788
+ /* Compact Card Header */
789
+ .compact-card-header {
790
+ display: flex;
791
+ align-items: center;
792
+ justify-content: space-between;
793
+ padding: 8px 16px !important;
794
+ min-height: 0;
795
+ background: linear-gradient(135deg, rgba(0, 212, 255, 0.06), rgba(255, 0, 110, 0.04)) !important;
796
+ border-bottom: 1px solid rgba(0, 212, 255, 0.12) !important;
797
+ margin: 0 -24px !important;
798
+ }
799
+
800
+ .card-header-main {
801
+ display: flex;
802
+ align-items: center;
803
+ gap: 8px;
804
+ }
805
+
806
+ .compact-title {
807
+ font-size: 1.1rem !important;
808
+ font-weight: 700 !important;
809
+ margin: 0 !important;
810
+ display: flex;
811
+ align-items: center;
812
+ gap: 8px;
813
+ color: #00d4ff !important;
814
+ background: none !important;
815
+ }
816
+
817
+ .compact-title i {
818
+ font-size: 1.1em;
819
+ color: #00d4ff;
820
+ }
821
+
822
+ .field-counter.compact-field-counter {
823
+ font-size: 0.95em !important;
824
+ font-weight: 600 !important;
825
+ color: #222 !important;
826
+ background: none !important;
827
+ padding: 0 0 0 8px !important;
828
+ box-shadow: none !important;
829
+ border-radius: 0 !important;
830
+ text-shadow: none !important;
831
+ }
832
+
833
+ .modern-field-selector-btn.compact-selector-btn {
834
+ margin-left: 16px;
835
+ display: flex;
836
+ align-items: center;
837
+ height: 32px;
838
+ }
839
+
840
+ .field-selector-btn.modern-selector-animated.compact-selector-btn-inner {
841
+ font-size: 0.95rem;
842
+ padding: 4px 14px;
843
+ min-width: 0;
844
+ height: 32px;
845
+ border-radius: 16px;
846
+ gap: 6px;
847
+ }
848
+
849
+ .field-selector-btn.modern-selector-animated.compact-selector-btn-inner i {
850
+ font-size: 1em;
851
+ }
852
+
853
+ .field-selector-counter {
854
+ font-size: 0.95em;
855
+ font-weight: 600;
856
+ margin-left: 2px;
857
+ }
858
+
859
+ /* ===== MODERN FIELD SELECTOR POPUP ===== */
860
+ .modern-field-selector-popup {
861
+ position: absolute;
862
+ top: 110%;
863
+ right: 0;
864
+ min-width: 260px;
865
+ background: #fff;
866
+ border-radius: 16px;
867
+ box-shadow: 0 8px 32px rgba(0,212,255,0.12);
868
+ z-index: 3000 !important;
869
+ padding: 18px 20px 16px 20px;
870
+ animation: popupFadeIn 0.25s cubic-bezier(.4,2,.6,1);
871
+ }
872
+
873
+ .modern-field-selector-btn {
874
+ position: relative;
875
+ }
876
+
877
+ @keyframes popupFadeIn {
878
+ from { opacity: 0; transform: translateY(-10px) scale(0.98); }
879
+ to { opacity: 1; transform: translateY(0) scale(1); }
880
+ }
881
+
882
+ .popup-header {
883
+ display: flex;
884
+ align-items: center;
885
+ justify-content: space-between;
886
+ margin-bottom: 12px;
887
+ }
888
+
889
+ .popup-header span {
890
+ font-weight: 700;
891
+ font-size: 1.1em;
892
+ display: flex;
893
+ align-items: center;
894
+ gap: 8px;
895
+ color: #00d4ff;
896
+ }
897
+
898
+ .popup-header i {
899
+ color: #00d4ff;
900
+ }
901
+
902
+ .popup-close-btn {
903
+ background: none;
904
+ border: none;
905
+ font-size: 1.2em;
906
+ color: #ff006e;
907
+ cursor: pointer;
908
+ transition: color 0.18s;
909
+ }
910
+ .popup-close-btn:hover {
911
+ color: #00d4ff;
912
+ }
913
+
914
+ .popup-fields-list {
915
+ max-height: 220px;
916
+ overflow: auto;
917
+ margin-bottom: 16px;
918
+ padding-right: 2px;
919
+ }
920
+
921
+ .popup-field-row {
922
+ margin-bottom: 8px;
923
+ }
924
+
925
+ .popup-field-label {
926
+ display: flex;
927
+ align-items: center;
928
+ gap: 8px;
929
+ font-size: 1em;
930
+ cursor: pointer;
931
+ user-select: none;
932
+ font-weight: 500;
933
+ color: #222;
934
+ border-radius: 6px;
935
+ padding: 2px 4px;
936
+ transition: background 0.15s;
937
+ }
938
+ .popup-field-label:hover {
939
+ background: #f0faff;
940
+ }
941
+
942
+ .popup-field-label input[type="checkbox"] {
943
+ accent-color: #00d4ff;
944
+ width: 18px;
945
+ height: 18px;
946
+ border-radius: 4px;
947
+ border: 1.5px solid #00d4ff;
948
+ margin: 0;
949
+ transition: box-shadow 0.15s;
950
+ }
951
+
952
+ .popup-field-label input[type="checkbox"]:checked {
953
+ box-shadow: 0 0 0 2px #06ffa5;
954
+ }
955
+
956
+ .popup-field-text {
957
+ font-weight: 500;
958
+ color: #222;
959
+ }
960
+
961
+ .popup-actions {
962
+ display: flex;
963
+ justify-content: space-between;
964
+ gap: 8px;
965
+ }
966
+
967
+ .popup-action-btn {
968
+ flex: 1;
969
+ border: none;
970
+ border-radius: 8px;
971
+ padding: 8px 0;
972
+ font-weight: 600;
973
+ font-size: 1em;
974
+ cursor: pointer;
975
+ transition: background 0.18s, color 0.18s, box-shadow 0.18s;
976
+ display: flex;
977
+ align-items: center;
978
+ justify-content: center;
979
+ gap: 6px;
980
+ }
981
+
982
+ .popup-action-btn.clear-btn {
983
+ background: #f8f8f8;
984
+ color: #ff006e;
985
+ }
986
+ .popup-action-btn.clear-btn:hover {
987
+ background: #ffe6f0;
988
+ color: #d1005b;
989
+ }
990
+
991
+ .popup-action-btn.selectall-btn {
992
+ background: #f8f8f8;
993
+ color: #06ffa5;
994
+ }
995
+ .popup-action-btn.selectall-btn:hover {
996
+ background: #e6fff7;
997
+ color: #00d4ff;
998
+ }
999
+
1000
+ .popup-action-btn.save-btn {
1001
+ background: linear-gradient(135deg,#00d4ff,#06ffa5,#ff006e);
1002
+ color: #fff;
1003
+ box-shadow: 0 2px 8px rgba(0,212,255,0.10);
1004
+ }
1005
+ .popup-action-btn.save-btn:hover {
1006
+ background: linear-gradient(135deg,#06ffa5,#00d4ff,#ff006e);
1007
+ color: #fff;
1008
+ box-shadow: 0 4px 16px rgba(0,212,255,0.18);
1009
+ }
1010
+
1011
+
1012
+ .form-card,
1013
+ .investigation-container {
1014
+ overflow: visible !important;
1015
+ }
1016
+
1017
+ .modern-field-selector-popup {
1018
+ z-index: 3000 !important;
1019
+ }
1020
+
1021
+ .info-btn, .glossy-info-btn {
1022
+ opacity: 0.5;
1023
+ background: rgba(10, 30, 60, 0.35);
1024
+ border: 2px solid #00d4ff33;
1025
+ box-shadow: none;
1026
+ }
1027
+ .info-btn:hover, .glossy-info-btn:hover {
1028
+ opacity: 0.8;
1029
+ background: rgba(10, 30, 60, 0.55);
1030
+ border: 2px solid #00d4ff;
1031
+ }
1032
+ .info-btn i, .glossy-info-btn i {
1033
+ color: #0a1e3c;
1034
+ font-size: 0.95em;
1035
+ text-shadow: none;
1036
+ }
1037
+
1038
+ .field-help {
1039
+ position: absolute !important;
1040
+ left: 36px;
1041
+ top: 50%;
1042
+ transform: translateY(-50%);
1043
+ min-width: 180px;
1044
+ max-width: 260px;
1045
+ background: rgba(10, 30, 60, 0.85);
1046
+ color: #fff;
1047
+ border-radius: 10px;
1048
+ border: 2px solid #00d4ff;
1049
+ box-shadow: 0 4px 24px rgba(0,212,255,0.10);
1050
+ padding: 10px 16px 10px 16px;
1051
+ z-index: 4000;
1052
+ font-size: 0.97em;
1053
+ font-weight: 500;
1054
+ animation: fadeInInfoModal 0.18s cubic-bezier(.4,2,.6,1);
1055
+ pointer-events: auto;
1056
+ white-space: pre-line;
1057
+ }
1058
+ @keyframes fadeInInfoModal {
1059
+ from { opacity: 0; transform: translateY(-50%) scale(0.98); }
1060
+ to { opacity: 1; transform: translateY(-50%) scale(1); }
1061
+ }
1062
+ .field-help strong {
1063
+ color: #00d4ff;
1064
+ font-weight: 700;
1065
+ font-size: 1.05em;
1066
+ display: block;
1067
+ margin-bottom: 2px;
1068
+ }
1069
+
1070
+ .file-upload-block {
1071
+ border: 2px solid #00d4ff;
1072
+ border-radius: 18px;
1073
+ background: rgba(240, 255, 255, 0.45);
1074
+ box-shadow: 0 2px 16px rgba(0,212,255,0.08);
1075
+ padding: 18px 18px 12px 18px;
1076
+ margin-bottom: 12px;
1077
+ position: relative;
1078
+ animation: fileGlow 2.2s infinite alternate;
1079
+ transition: box-shadow 0.22s, border-color 0.22s;
1080
+ }
1081
+ @keyframes fileGlow {
1082
+ 0% { box-shadow: 0 2px 16px rgba(0,212,255,0.08), 0 0 0 0 #06ffa5; border-color: #00d4ff; }
1083
+ 60% { box-shadow: 0 6px 32px rgba(0,212,255,0.18), 0 0 0 4px #06ffa5; border-color: #06ffa5; }
1084
+ 100% { box-shadow: 0 2px 16px rgba(0,212,255,0.08), 0 0 0 0 #00d4ff; border-color: #00d4ff; }
1085
+ }
1086
+
1087
+ .file-upload-block .file-drop-zone {
1088
+ background: rgba(255,255,255,0.85);
1089
+ border: 2px dashed #00d4ff;
1090
+ border-radius: 14px;
1091
+ padding: 18px 0 12px 0;
1092
+ text-align: center;
1093
+ transition: background 0.18s, border-color 0.18s, box-shadow 0.18s;
1094
+ cursor: pointer;
1095
+ margin-bottom: 8px;
1096
+ position: relative;
1097
+ }
1098
+ .file-upload-block .file-drop-zone:hover, .file-upload-block .file-drop-zone.drag-over {
1099
+ background: rgba(0,212,255,0.08);
1100
+ border-color: #06ffa5;
1101
+ box-shadow: 0 4px 24px rgba(0,212,255,0.18);
1102
+ }
1103
+
1104
+ .file-upload-block .drop-zone-content i {
1105
+ color: #00d4ff;
1106
+ font-size: 2.2em;
1107
+ margin-bottom: 6px;
1108
+ animation: fileIconBounce 1.4s infinite alternate;
1109
+ filter: drop-shadow(0 0 8px #06ffa5);
1110
+ transition: color 0.18s;
1111
+ }
1112
+ @keyframes fileIconBounce {
1113
+ 0% { transform: translateY(0) scale(1); }
1114
+ 60% { transform: translateY(-8px) scale(1.12); color: #06ffa5; }
1115
+ 100% { transform: translateY(0) scale(1); }
1116
+ }
1117
+
1118
+ .file-upload-block .drop-zone-content p {
1119
+ color: #222;
1120
+ font-size: 1.08em;
1121
+ font-weight: 500;
1122
  margin: 0;
1123
+ opacity: 0.85;
1124
+ }
1125
+
1126
+ .file-upload-block .file-input {
1127
+ opacity: 0;
1128
+ position: absolute;
1129
+ left: 0; top: 0; width: 100%; height: 100%;
1130
+ cursor: pointer;
1131
+ }
1132
+
1133
+ /* ===== HEADER STYLES ===== */
1134
+ .infopage-header {
1135
+ display: flex;
1136
+ align-items: center;
1137
+ justify-content: space-between;
1138
+ background: #011329;
1139
+ padding: 18px 32px 18px 32px;
1140
+ border-radius: 0 0 18px 18px;
1141
+ box-shadow: 0 2px 12px #38bdf844;
1142
+ margin-bottom: 18px;
1143
+ position: relative;
1144
+ z-index: 10;
1145
+ }
1146
+
1147
+ .header-left {
1148
+ display: flex;
1149
+ align-items: center;
1150
+ gap: 18px;
1151
+ }
1152
+
1153
+ .logo-img-header {
1154
+ width: 54px;
1155
+ height: 54px;
1156
+ border-radius: 50%;
1157
+ background: #fff;
1158
+ box-shadow: 0 2px 8px rgba(0,0,0,0.18);
1159
+ padding: 4px;
1160
+ margin-top: -6px; /* Move logo slightly upwards */
1161
+ margin-bottom: 1vh;
1162
+ }
1163
+
1164
+ .py-detect-title-header {
1165
+ font-size: 2.1rem;
1166
+ font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
1167
+ font-weight: 900;
1168
+ letter-spacing: 6px;
1169
+ color: #38bdf8;
1170
+ display: flex;
1171
+ align-items: center;
1172
+ gap: 2px;
1173
+ margin-bottom: 1.5vh;
1174
+ }
1175
+
1176
+ .py-detect-title-header .py-letter.p { color: #e3f6ff; text-shadow: 0 0 6px #38bdf8; }
1177
+ .py-detect-title-header .py-letter.y { color: #38bdf8; text-shadow: 0 0 6px #38bdf8; }
1178
+ .py-detect-title-header .py-shape { color: #e3f6ff; background: #e3f6ff; text-shadow: 0 0 6px #38bdf8; box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff; border: 2px solid #23272b; width: 18px; height: 4px; display: inline-block; margin: 0 8px; border-radius: 2px; }
1179
+ .py-detect-title-header .py-letter.d { color: #e3f6ff; text-shadow: 0 0 6px #38bdf8; }
1180
+ .py-detect-title-header .py-letter.e { color: #38bdf8; text-shadow: 0 0 6px #38bdf8; }
1181
+ .py-detect-title-header .py-letter.t { color: #e3f6ff; text-shadow: 0 0 6px #38bdf8; }
1182
+ .py-detect-title-header .py-letter.e2 { color: #38bdf8; text-shadow: 0 0 6px #38bdf8; }
1183
+ .py-detect-title-header .py-letter.c { color: #e3f6ff; text-shadow: 0 0 6px #38bdf8; }
1184
+ .py-detect-title-header .py-letter.t2 { color: #38bdf8; text-shadow: 0 0 6px #38bdf8; }
1185
+
1186
+ .header-right {
1187
+ display: flex;
1188
+ align-items: center;
1189
+ gap: 14px;
1190
+ }
1191
+
1192
+ .pykara-analysis-label {
1193
+ color: #38bdf8;
1194
+ font-size: 1.1em;
1195
+ font-weight: 700;
1196
+ letter-spacing: 1px;
1197
+ }
1198
+
1199
+ .pykara-progress-bar {
1200
+ width: 220px;
1201
+ height: 14px;
1202
+ background: #e3f6ff;
1203
+ border-radius: 8px;
1204
+ overflow: hidden;
1205
+ box-shadow: 0 2px 8px #38bdf844, 0 0 12px #38bdf8aa;
1206
+ position: relative;
1207
+ }
1208
+
1209
+ .pykara-progress-bar-inner {
1210
+ height: 100%;
1211
+ background: linear-gradient(270deg, #38bdf8, #06ffa5, #ff006e, #38bdf8);
1212
+ background-size: 400% 100%;
1213
+ border-radius: 8px 0 0 8px;
1214
+ transition: width 0.4s cubic-bezier(.4,2,.6,1);
1215
+ animation: progressBarGradientMove 2.5s linear infinite;
1216
+ box-shadow: 0 0 16px #38bdf8cc, 0 0 8px #06ffa5aa;
1217
+ }
1218
+
1219
+ @keyframes progressBarGradientMove {
1220
+ 0% { background-position: 0% 50%; }
1221
+ 100% { background-position: 100% 50%; }
1222
+ }
1223
+
1224
+ .pykara-progress-percentage {
1225
+ color: #38bdf8;
1226
+ font-size: 1.05em;
1227
+ font-weight: 700;
1228
+ margin-left: 4px;
1229
+ }
1230
+
1231
+ /* ===== READABLE PROGRESS BAR ===== */
1232
+ .progress-container {
1233
+ background: rgba(26, 26, 46, 0.9) !important;
1234
+ backdrop-filter: blur(10px) !important;
1235
+ padding: 8px 24px !important;
1236
+ position: relative !important;
1237
+ }
1238
+
1239
+ .progress-bar {
1240
+ background: linear-gradient(90deg, #00d4ff 0%, #06ffa5 50%, #ff006e 100%) !important;
1241
+ height: 6px !important;
1242
+ border-radius: 3px !important;
1243
+ position: relative !important;
1244
+ box-shadow: 0 0 10px rgba(0, 212, 255, 0.4) !important;
1245
+ }
1246
+
1247
+ .progress-text {
1248
+ display: flex !important;
1249
+ justify-content: space-between !important;
1250
+ align-items: center !important;
1251
+ font-size: 0.8rem !important;
1252
+ color: #e0e6ed !important;
1253
+ margin-bottom: 4px !important;
1254
  }
1255
 
1256
+ .progress-ai {
1257
+ color: #06ffa5 !important;
1258
+ font-weight: 600 !important;
 
 
 
 
 
 
 
1259
  }
1260
 
1261
+ /* ===== READABLE SECTION NAVIGATION ===== */
1262
+ .section-navigation {
1263
+ background: linear-gradient(135deg, rgba(26, 26, 46, 0.9), rgba(22, 33, 62, 0.95)) !important;
1264
+ backdrop-filter: blur(15px) !important;
1265
+ border-bottom: 1px solid rgba(0, 212, 255, 0.2) !important;
1266
+ padding: 16px 0 !important;
1267
+ position: relative !important;
1268
+ }
1269
+
1270
+ .section-pills {
1271
+ display: flex !important;
1272
+ justify-content: center !important;
1273
+ gap: 16px !important;
1274
+ max-width: 1200px !important;
1275
+ margin: 0 auto !important;
1276
+ padding: 0 24px !important;
1277
+ }
1278
+
1279
+ .section-pill {
1280
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05)) !important;
1281
+ border: 1px solid rgba(0, 212, 255, 0.3) !important;
1282
+ backdrop-filter: blur(10px) !important;
1283
+ padding: 12px 20px !important;
1284
+ border-radius: 12px !important;
1285
+ color: #e0e6ed !important;
1286
+ font-weight: 500 !important;
1287
+ font-size: 0.9rem !important;
1288
+ cursor: pointer !important;
1289
+ transition: all 0.3s ease !important;
1290
+ position: relative !important;
1291
+ display: flex !important;
1292
+ align-items: center !important;
1293
+ gap: 8px !important;
1294
+ min-width: 150px !important;
1295
+ justify-content: center !important;
1296
+ }
1297
+
1298
+ .section-pill:hover {
1299
+ border-color: rgba(0, 212, 255, 0.6) !important;
1300
+ box-shadow: 0 0 20px rgba(0, 212, 255, 0.3) !important;
1301
+ transform: translateY(-2px) !important;
1302
+ }
1303
+
1304
+ .section-pill.active {
1305
+ background: linear-gradient(135deg, rgba(0, 212, 255, 0.2), rgba(255, 0, 110, 0.1)) !important;
1306
+ border-color: #00d4ff !important;
1307
+ color: white !important;
1308
+ box-shadow: 0 0 25px rgba(0, 212, 255, 0.4) !important;
1309
+ transform: translateY(-3px) !important;
1310
+ }
1311
+
1312
+ .section-pill::after {
1313
+ content: attr(data-step) !important;
1314
+ position: absolute !important;
1315
+ top: -8px !important;
1316
+ left: 12px !important;
1317
+ width: 20px !important;
1318
+ height: 20px !important;
1319
+ background: linear-gradient(135deg, #7209b7, #4cc9f0) !important;
1320
+ color: white !important;
1321
+ border-radius: 50% !important;
1322
+ display: flex !important;
1323
+ align-items: center !important;
1324
+ justify-content: center !important;
1325
+ font-size: 0.7rem !important;
1326
+ font-weight: 700 !important;
1327
+ border: 2px solid rgba(26, 26, 46, 0.9) !important;
1328
+ }
1329
+
1330
+ .section-pill.active::after {
1331
+ background: linear-gradient(135deg, #ff006e, #8338ec) !important;
1332
+ animation: activeStep 2s ease-in-out infinite !important;
1333
+ }
1334
+
1335
+ @keyframes activeStep {
1336
+ 0%, 100% { transform: scale(1); }
1337
+ 50% { transform: scale(1.1); }
1338
+ }
1339
+
1340
+ /* ===== READABLE SUBGROUP PILLS ===== */
1341
+ .subgroup-pills {
1342
+ background: linear-gradient(135deg, rgba(22, 33, 62, 0.9), rgba(26, 26, 46, 0.95)) !important;
1343
+ backdrop-filter: blur(15px) !important;
1344
+ border-bottom: 1px solid rgba(0, 212, 255, 0.2) !important;
1345
+ padding: 16px 24px !important;
1346
+ display: flex !important;
1347
+ justify-content: center !important;
1348
+ flex-wrap: wrap !important;
1349
+ gap: 12px !important;
1350
+ max-width: 1200px !important;
1351
+ margin: 0 auto !important;
1352
+ }
1353
+
1354
+ .subgroup-pills .pill {
1355
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.04)) !important;
1356
+ border: 1px solid rgba(0, 212, 255, 0.25) !important;
1357
+ backdrop-filter: blur(8px) !important;
1358
+ padding: 8px 16px !important;
1359
+ border-radius: 20px !important;
1360
+ color: #e0e6ed !important;
1361
+ font-weight: 500 !important;
1362
+ font-size: 0.85rem !important;
1363
+ cursor: pointer !important;
1364
+ transition: all 0.3s ease !important;
1365
+ display: flex !important;
1366
+ align-items: center !important;
1367
+ gap: 6px !important;
1368
+ min-width: 120px !important;
1369
+ justify-content: center !important;
1370
+ }
1371
+
1372
+ .subgroup-pills .pill:hover {
1373
+ border-color: rgba(6, 255, 165, 0.6) !important;
1374
+ box-shadow: 0 0 15px rgba(6, 255, 165, 0.3) !important;
1375
+ transform: translateY(-1px) !important;
1376
+ }
1377
+
1378
+ .subgroup-pills .pill.active {
1379
+ background: linear-gradient(135deg, rgba(255, 0, 110, 0.2), rgba(131, 56, 236, 0.15)) !important;
1380
+ border-color: #ff006e !important;
1381
+ color: white !important;
1382
+ box-shadow: 0 0 20px rgba(255, 0, 110, 0.4) !important;
1383
+ transform: translateY(-2px) !important;
1384
+ }
1385
+
1386
+ /* ===== READABLE CONTENT AREA ===== */
1387
+ .investigation-container {
1388
+ background: transparent !important;
1389
+ padding: 24px 0 !important;
1390
+ max-width: 100vw !important;
1391
+ width: 96vw !important;
1392
+ margin: 0 !important;
1393
+ min-height: calc(100vh - 400px) !important;
1394
+ position: relative !important;
1395
+ left: 50% !important;
1396
+ right: 50% !important;
1397
+ margin-left: -48vw !important;
1398
+ margin-right: -50vw !important;
1399
+ box-sizing: border-box !important;
1400
+ }
1401
+
1402
+ /* ===== FULL-WIDTH FORM CARD ===== */
1403
+ .form-card {
1404
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.92)) !important;
1405
+ backdrop-filter: blur(20px) !important;
1406
+ border: 1px solid rgba(0, 212, 255, 0.2) !important;
1407
+ border-radius: 0 !important;
1408
+ box-shadow:
1409
+ 0 8px 32px rgba(0, 0, 0, 0.1),
1410
+ 0 0 0 1px rgba(255, 255, 255, 0.2) !important;
1411
+ color: #2C3E50 !important;
1412
+ position: relative !important;
1413
+ overflow: visible !important;
1414
+ margin: 0 !important;
1415
+ width: 100% !important;
1416
+ max-width: 100% !important;
1417
+ padding: 0 24px !important;
1418
+ box-sizing: border-box !important;
1419
+ }
1420
+
1421
+ .form-card::before {
1422
+ content: '' !important;
1423
+ position: absolute !important;
1424
+ top: 0 !important;
1425
+ left: -100% !important;
1426
+ width: 100% !important;
1427
+ height: 2px !important;
1428
+ background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.6), transparent) !important;
1429
+ animation: cardScan 4s linear infinite !important;
1430
+ }
1431
+
1432
+ @keyframes cardScan {
1433
+ 0% { left: -100%; }
1434
+ 100% { left: 100%; }
1435
+ }
1436
+
1437
+ .card-header {
1438
+ background: linear-gradient(135deg, rgba(0, 212, 255, 0.1), rgba(255, 0, 110, 0.05)) !important;
1439
+ border-bottom: 1px solid rgba(0, 212, 255, 0.2) !important;
1440
+ padding: 16px 20px !important;
1441
+ border-radius: 0 !important;
1442
+ margin: 0 -24px !important;
1443
+ margin-bottom: 0 !important;
1444
+ }
1445
+
1446
+ .card-header h2 {
1447
+ background: linear-gradient(45deg, #00d4ff, #ff006e) !important;
1448
+ -webkit-background-clip: text !important;
1449
+ -webkit-text-fill-color: transparent !important;
1450
+ background-clip: text !important;
1451
+ font-size: 1.1rem !important;
1452
+ font-weight: 700 !important;
1453
+ margin: 0 !important;
1454
+ display: flex !important;
1455
+ align-items: center !important;
1456
+ gap: 8px !important;
1457
+ }
1458
+
1459
+ .card-content {
1460
+ padding: 20px 0 !important;
1461
+ background: rgba(255, 255, 255, 0.98) !important;
1462
+ color: #2C3E50 !important;
1463
+ width: 100% !important;
1464
+ max-width: 100% !important;
1465
+ box-sizing: border-box !important;
1466
+ }
1467
+
1468
+ .section-description {
1469
+ background: linear-gradient(135deg, rgba(0, 212, 255, 0.1), rgba(6, 255, 165, 0.08)) !important;
1470
+ border: 1px solid rgba(0, 212, 255, 0.2) !important;
1471
+ border-radius: 8px !important;
1472
+ padding: 12px 16px !important;
1473
+ margin-bottom: 20px !important;
1474
+ font-size: 0.85rem !important;
1475
+ color: #2C3E50 !important;
1476
+ border-left: 4px solid #00d4ff !important;
1477
+ width: 100% !important;
1478
+ max-width: 100% !important;
1479
+ box-sizing: border-box !important;
1480
+ }
1481
+
1482
+ /* ===== FULL-WIDTH FORM FIELDS ===== */
1483
+ .fields-grid {
1484
+ display: grid !important;
1485
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)) !important;
1486
+ gap: 16px !important;
1487
+ margin-top: 16px !important;
1488
+ width: 100% !important;
1489
+ max-width: 100% !important;
1490
+ box-sizing: border-box !important;
1491
+ padding: 0 !important;
1492
+ overflow: visible !important;
1493
  position: relative;
1494
  }
1495
 
1496
+ .field-container {
1497
+ background: rgba(255, 255, 255, 0.9) !important;
1498
+ border: 1px solid rgba(0, 212, 255, 0.2) !important;
1499
+ border-radius: 8px !important;
1500
+ padding: 12px !important;
1501
+ transition: all 0.3s ease !important;
1502
+ position: relative !important;
1503
+ overflow: visible !important;
1504
+ z-index: 1;
1505
+ width: 100% !important;
1506
+ max-width: 100% !important;
1507
+ box-sizing: border-box !important;
1508
  }
1509
 
1510
+ .field-container:hover {
1511
+ border-color: rgba(0, 212, 255, 0.4) !important;
1512
+ box-shadow: 0 0 15px rgba(0, 212, 255, 0.1) !important;
1513
+ transform: translateY(-1px) !important;
 
 
 
 
 
1514
  }
1515
 
1516
+ .field-label {
1517
+ color: #2C3E50 !important;
1518
+ font-weight: 600 !important;
1519
+ font-size: 0.85rem !important;
1520
+ margin-bottom: 6px !important;
1521
+ display: flex !important;
1522
+ align-items: center !important;
1523
+ gap: 6px !important;
1524
+ width: 100% !important;
1525
  }
1526
 
1527
+ .field-input {
1528
+ width: 100% !important;
1529
+ padding: 10px 12px !important;
1530
+ border: 1px solid #E1E8ED !important;
1531
+ border-radius: 6px !important;
1532
+ background: white !important;
1533
+ color: #2C3E50 !important;
1534
+ font-size: 0.85rem !important;
1535
+ transition: all 0.3s ease !important;
1536
+ box-sizing: border-box !important;
1537
+ }
1538
+
1539
+ .field-input:focus {
1540
+ border-color: #00d4ff !important;
1541
+ box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.1) !important;
1542
+ outline: none !important;
1543
+ }
1544
+
1545
+ .field-input::placeholder {
1546
+ color: #7F8C8D !important;
1547
+ }
1548
+
1549
+ /* ===== CARD ACTIONS FULL WIDTH ===== */
1550
+ .card-actions {
1551
+ display: flex !important;
1552
+ align-items: center !important;
1553
+ gap: 12px !important;
1554
+ justify-content: flex-start !important;
1555
+ width: 100% !important;
1556
+ flex-wrap: wrap !important;
1557
+ position: relative;
1558
+ }
1559
+
1560
+ .card-actions-spacer {
1561
+ flex: 1 1 auto;
1562
  }
1563
 
1564
+ .field-selector-container {
1565
+ display: flex !important;
1566
+ align-items: center !important;
1567
+ justify-content: flex-end !important;
1568
+ gap: 12px !important;
1569
+ width: 100% !important;
1570
+ }
1571
+
1572
+ .modern-field-selector-btn {
1573
  display: flex;
 
1574
  align-items: center;
1575
+ justify-content: flex-end;
1576
+ margin-left: auto;
1577
+ position: relative;
 
 
 
 
1578
  }
1579
 
1580
+ .field-selector-btn.modern-selector-animated {
1581
+ border-radius: 24px;
1582
+ background: linear-gradient(135deg, #00d4ff 0%, #06ffa5 60%, #ff006e 100%);
1583
+ color: #fff;
1584
+ font-weight: 700;
1585
+ font-size: 1rem;
1586
+ padding: 10px 22px;
 
 
 
 
1587
  border: none;
1588
+ box-shadow: 0 2px 12px rgba(0, 212, 255, 0.10);
1589
+ display: flex;
1590
+ align-items: center;
1591
+ gap: 10px;
1592
+ cursor: pointer;
1593
+ transition: background 0.18s, box-shadow 0.18s, transform 0.18s;
1594
+ outline: none;
1595
+ position: relative;
1596
+ z-index: 2;
1597
+ animation: selectorPulse 2s infinite alternate;
1598
  }
1599
 
1600
+ .field-selector-btn.modern-selector-animated:hover,
1601
+ .field-selector-btn.modern-selector-animated.active {
1602
+ background: linear-gradient(135deg, #06ffa5 0%, #00d4ff 60%, #ff006e 100%);
1603
+ box-shadow: 0 4px 24px rgba(0, 212, 255, 0.18);
1604
+ transform: scale(1.04);
1605
+ }
1606
 
1607
+ @keyframes selectorPulse {
1608
+ 0% { box-shadow: 0 2px 12px rgba(0, 212, 255, 0.10); }
1609
+ 100% { box-shadow: 0 6px 24px rgba(0, 212, 255, 0.18); }
1610
+ }
1611
 
1612
+ .field-selector-btn.modern-selector-animated i {
1613
+ font-size: 1.2em;
1614
+ }
 
 
 
 
1615
 
1616
+ /* Highlight filled field-container with green border */
1617
+ .field-container.filled {
1618
+ border-color: #06ffa5 !important;
1619
+ box-shadow: 0 0 0 2px rgba(6,255,165,0.12);
1620
+ transition: border-color 0.25s, box-shadow 0.25s;
1621
+ }
1622
 
1623
+ /* ===== REMOVE BODY OVERFLOW ===== */
1624
+ body {
1625
+ overflow-x: hidden !important;
1626
+ width: 100vw !important;
1627
+ box-sizing: border-box !important;
1628
+ }
1629
 
1630
+ html {
1631
+ overflow-x: hidden !important;
1632
+ width: 100vw !important;
1633
+ box-sizing: border-box !important;
1634
+ }
1635
 
1636
+ /* ===== ENSURE ALL CONTAINERS ARE FULL WIDTH ===== */
1637
+ .site-header,
1638
+ .section-navigation,
1639
+ .subgroup-pills,
1640
+ .page-indicator-container {
1641
+ width: 100vw !important;
1642
+ max-width: 100vw !important;
1643
+ margin: 0 !important;
1644
+ box-sizing: border-box !important;
1645
+ }
1646
 
1647
+ /* ===== RESPONSIVE FULL WIDTH ===== */
1648
+ @media (max-width: 768px) {
1649
+ .investigation-container {
1650
+ padding: 16px 0 !important;
1651
+ margin-left: -50vw !important;
1652
+ margin-right: -50vw !important;
1653
+ left: 50% !important;
1654
+ right: 50% !important;
1655
+ }
1656
+
1657
+ .form-card {
1658
+ padding: 0 16px !important;
1659
+ border-radius: 0 !important;
1660
+ }
1661
+
1662
+ .card-header {
1663
+ margin: 0 -16px !important;
1664
+ padding: 12px 16px !important;
1665
+ }
1666
+
1667
+ .fields-grid {
1668
+ grid-template-columns: 1fr !important;
1669
+ gap: 12px !important;
1670
  }
1671
+
1672
+ .card-actions {
1673
+ flex-direction: column !important;
1674
+ align-items: stretch !important;
1675
+ gap: 8px !important;
1676
+ }
1677
+
1678
+ .field-selector-container {
1679
+ width: 100% !important;
1680
+ }
1681
+
1682
+ .field-selector-btn {
1683
+ width: 100% !important;
1684
+ justify-content: center !important;
1685
+ }
1686
+ }
1687
 
1688
+ @media (max-width: 480px) {
1689
+ .investigation-container {
1690
+ padding: 12px 0 !important;
1691
  }
1692
+
1693
+ .form-card {
1694
+ padding: 0 12px !important;
1695
+ }
1696
+
1697
+ .card-header {
1698
+ margin: 0 -12px !important;
1699
+ padding: 10px 12px !important;
1700
+ }
1701
+
1702
+ .card-content {
1703
+ padding: 16px 0 !important;
1704
+ }
1705
+ }
1706
 
1707
+ /* Crime Details style for autosave button */
1708
+ .autosave-indicator.crime-section-btn {
1709
+ background: linear-gradient(135deg, #E8F4FD 0%, #F0F8FF 100%) !important;
1710
+ border: 2px solid #4A90E2 !important;
1711
+ color: #357ABD !important;
1712
+ font-weight: 600 !important;
1713
+ border-radius: 20px !important;
1714
+ box-shadow: 0 2px 6px rgba(74, 144, 226, 0.08) !important;
1715
+ padding: 10px 20px !important;
1716
+ transition: all 0.3s;
1717
+ display: flex !important;
1718
+ align-items: center !important;
1719
+ gap: 8px !important;
 
 
 
 
1720
  }
1721
 
1722
+ .autosave-indicator.crime-section-btn:hover {
1723
+ border-color: #357ABD !important;
1724
+ color: #fff !important;
1725
+ background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%) !important;
1726
+ box-shadow: 0 4px 12px rgba(74, 144, 226, 0.25) !important;
1727
  }
1728
 
1729
+ .autosave-right {
1730
+ margin-left: auto !important;
1731
+ margin-right: 0 !important;
 
1732
  }
1733
 
1734
+ .autosave-left {
1735
+ margin-right: auto !important;
1736
+ margin-left: 0 !important;
 
 
1737
  }
1738
 
1739
+ .header-actions {
1740
+ display: flex !important;
1741
+ align-items: center !important;
1742
+ gap: 16px !important;
1743
+ }
1744
+
1745
+ .header-actions.autosave-right {
1746
+ display: flex !important;
1747
+ justify-content: flex-end !important;
1748
+ width: 100%;
1749
  }
1750
 
1751
+ .autosave-indicator {
1752
+ display: flex !important;
1753
+ align-items: center !important;
1754
+ gap: 8px !important;
1755
+ padding: 8px 16px !important;
1756
+ background: rgba(255, 255, 255, 0.15) !important;
1757
+ border: 1px solid rgba(255, 255, 255, 0.2) !important;
1758
+ border-radius: 8px !important;
1759
+ font-size: 0.875rem !important;
1760
+ color: white !important;
1761
+ font-weight: 500 !important;
1762
+ backdrop-filter: blur(10px) !important;
1763
+ }
1764
+
1765
+ .autosave-indicator.saving {
1766
+ background: rgba(255, 255, 255, 0.25) !important;
1767
+ animation: pulse 1.5s ease-in-out infinite !important;
1768
  }
1769
 
1770
+ .main-section-pill {
1771
+ font-size: 1.25rem !important;
1772
+ padding: 20px 36px !important;
1773
+ min-width: 200px !important;
1774
+ min-height: 64px !important;
1775
+ border-width: 2.5px !important;
1776
+ box-shadow: 0 4px 24px rgba(0, 212, 255, 0.18) !important;
1777
+ }
1778
+
1779
+ .main-section-pill i {
1780
+ font-size: 2rem !important;
1781
  }
1782
 
1783
+ .main-section-pill span {
1784
+ font-size: 1.25em !important;
1785
+ font-weight: 700 !important;
 
1786
  }
1787
 
1788
+ /* ===== MODERN NAVIGATION BUTTONS ===== */
1789
+ .modern-nav-btns {
1790
  display: flex;
1791
+ justify-content: flex-end;
1792
+ gap: 18px;
1793
+ margin-top: 32px;
1794
+ margin-bottom: 8px;
1795
  }
1796
 
1797
+ .modern-round-btn {
1798
+ width: 56px;
1799
+ height: 56px;
1800
+ border-radius: 50%;
1801
+ border: none;
1802
+ background: linear-gradient(135deg, #00d4ff 0%, #06ffa5 60%, #ff006e 100%);
1803
+ color: #fff;
1804
+ font-size: 2rem;
1805
+ display: flex;
1806
  align-items: center;
1807
+ justify-content: center;
1808
+ box-shadow: 0 4px 24px rgba(0, 212, 255, 0.18), 0 1.5px 6px rgba(0,0,0,0.08);
1809
+ cursor: pointer;
1810
+ transition: transform 0.18s cubic-bezier(.4,2,.6,1), box-shadow 0.18s;
1811
+ outline: none;
1812
+ position: relative;
1813
+ z-index: 2;
1814
  }
1815
 
1816
+ .modern-round-btn:active {
1817
+ transform: scale(0.93);
1818
+ box-shadow: 0 2px 8px rgba(0, 212, 255, 0.12);
1819
  }
1820
 
1821
+ .modern-round-btn:disabled {
1822
+ opacity: 0.5;
1823
+ cursor: not-allowed;
1824
  }
1825
 
1826
+ .next-btn-animated {
1827
+ animation: bounceRight 1.2s infinite alternate;
 
 
 
1828
  }
1829
 
1830
+ @keyframes bounceRight {
1831
+ 0% { transform: translateX(0); }
1832
+ 60% { transform: translateX(6px) scale(1.08); }
1833
+ 100% { transform: translateX(0); }
1834
  }
1835
 
1836
+ .submit-btn-animated {
1837
+ animation: pulseSend 1.5s infinite alternate;
 
 
1838
  }
1839
 
1840
+ @keyframes pulseSend {
1841
+ 0% { box-shadow: 0 4px 24px rgba(0, 212, 255, 0.18); }
1842
+ 60% { box-shadow: 0 8px 32px rgba(255, 0, 110, 0.22); }
1843
+ 100% { box-shadow: 0 4px 24px rgba(0, 212, 255, 0.18); }
1844
  }
1845
 
1846
+ .modern-round-btn i {
1847
+ font-size: 2rem;
1848
+ pointer-events: none;
1849
  }
1850
 
1851
+ /* ===== FLOATING NAVIGATION BUTTON ===== */
1852
+ .modern-floating-nav-btn {
1853
+ position: fixed;
1854
+ bottom: 32px;
1855
+ right: 32px;
1856
+ z-index: 2000;
1857
+ display: flex;
1858
+ flex-direction: column;
1859
+ gap: 16px;
1860
+ pointer-events: none;
1861
+ }
1862
+
1863
+ .modern-floating-nav-btn .modern-round-btn {
1864
+ pointer-events: auto;
1865
+ }
1866
+
1867
+ @media (max-width: 600px) {
1868
+ .modern-floating-nav-btn {
1869
+ bottom: 16px;
1870
+ right: 16px;
1871
+ }
1872
  }
1873
 
1874
+ /* Compact Card Header */
1875
+ .compact-card-header {
1876
  display: flex;
1877
+ align-items: center;
1878
  justify-content: space-between;
1879
+ padding: 8px 16px !important;
1880
+ min-height: 0;
1881
+ background: linear-gradient(135deg, rgba(0, 212, 255, 0.06), rgba(255, 0, 110, 0.04)) !important;
1882
+ border-bottom: 1px solid rgba(0, 212, 255, 0.12) !important;
1883
+ margin: 0 -24px !important;
1884
+ }
1885
+
1886
+ .card-header-main {
1887
+ display: flex;
1888
  align-items: center;
1889
+ gap: 8px;
1890
  }
1891
 
1892
+ .compact-title {
1893
+ font-size: 1.1rem !important;
1894
+ font-weight: 700 !important;
1895
+ margin: 0 !important;
1896
  display: flex;
1897
+ align-items: center;
1898
  gap: 8px;
1899
+ color: #00d4ff !important;
1900
+ background: none !important;
1901
+ }
1902
+
1903
+ .compact-title i {
1904
+ font-size: 1.1em;
1905
+ color: #00d4ff;
1906
+ }
1907
+
1908
+ .field-counter.compact-field-counter {
1909
+ font-size: 0.95em !important;
1910
+ font-weight: 600 !important;
1911
+ color: #222 !important;
1912
+ background: none !important;
1913
+ padding: 0 0 0 8px !important;
1914
+ box-shadow: none !important;
1915
+ border-radius: 0 !important;
1916
+ text-shadow: none !important;
1917
+ }
1918
+
1919
+ .modern-field-selector-btn.compact-selector-btn {
1920
+ margin-left: 16px;
1921
+ display: flex;
1922
+ align-items: center;
1923
+ height: 32px;
1924
+ }
1925
+
1926
+ .field-selector-btn.modern-selector-animated.compact-selector-btn-inner {
1927
+ font-size: 0.95rem;
1928
+ padding: 4px 14px;
1929
+ min-width: 0;
1930
+ height: 32px;
1931
+ border-radius: 16px;
1932
+ gap: 6px;
1933
+ }
1934
+
1935
+ .field-selector-btn.modern-selector-animated.compact-selector-btn-inner i {
1936
+ font-size: 1em;
1937
+ }
1938
+
1939
+ .field-selector-counter {
1940
+ font-size: 0.95em;
1941
+ font-weight: 600;
1942
+ margin-left: 2px;
1943
+ }
1944
+
1945
+ /* ===== MODERN FIELD SELECTOR POPUP ===== */
1946
+ .modern-field-selector-popup {
1947
+ position: absolute;
1948
+ top: 110%;
1949
+ right: 0;
1950
+ min-width: 260px;
1951
+ background: #fff;
1952
+ border-radius: 16px;
1953
+ box-shadow: 0 8px 32px rgba(0,212,255,0.12);
1954
+ z-index: 3000 !important;
1955
+ padding: 18px 20px 16px 20px;
1956
+ animation: popupFadeIn 0.25s cubic-bezier(.4,2,.6,1);
1957
+ }
1958
+
1959
+ .modern-field-selector-btn {
1960
+ position: relative;
1961
+ }
1962
+
1963
+ @keyframes popupFadeIn {
1964
+ from { opacity: 0; transform: translateY(-10px) scale(0.98); }
1965
+ to { opacity: 1; transform: translateY(0) scale(1); }
1966
  }
1967
 
1968
+ .popup-header {
1969
  display: flex;
1970
+ align-items: center;
1971
+ justify-content: space-between;
1972
+ margin-bottom: 12px;
1973
+ }
1974
+
1975
+ .popup-header span {
1976
+ font-weight: 700;
1977
+ font-size: 1.1em;
1978
+ display: flex;
1979
+ align-items: center;
1980
  gap: 8px;
1981
+ color: #00d4ff;
1982
+ }
1983
+
1984
+ .popup-header i {
1985
+ color: #00d4ff;
1986
  }
1987
 
1988
+ .popup-close-btn {
1989
  background: none;
1990
  border: none;
1991
+ font-size: 1.2em;
1992
+ color: #ff006e;
1993
  cursor: pointer;
1994
+ transition: color 0.18s;
1995
+ }
1996
+ .popup-close-btn:hover {
1997
+ color: #00d4ff;
1998
  }
1999
 
2000
+ .popup-fields-list {
2001
+ max-height: 220px;
2002
+ overflow: auto;
2003
+ margin-bottom: 16px;
2004
+ padding-right: 2px;
2005
+ }
2006
+
2007
+ .popup-field-row {
2008
+ margin-bottom: 8px;
2009
+ }
2010
+
2011
+ .popup-field-label {
2012
+ display: flex;
2013
+ align-items: center;
2014
+ gap: 8px;
2015
+ font-size: 1em;
2016
  cursor: pointer;
2017
+ user-select: none;
2018
+ font-weight: 500;
2019
+ color: #222;
2020
+ border-radius: 6px;
2021
+ padding: 2px 4px;
2022
+ transition: background 0.15s;
2023
+ }
2024
+ .popup-field-label:hover {
2025
+ background: #f0faff;
2026
+ }
2027
+
2028
+ .popup-field-label input[type="checkbox"] {
2029
+ accent-color: #00d4ff;
2030
+ width: 18px;
2031
+ height: 18px;
2032
+ border-radius: 4px;
2033
+ border: 1.5px solid #00d4ff;
2034
+ margin: 0;
2035
+ transition: box-shadow 0.15s;
2036
+ }
2037
+
2038
+ .popup-field-label input[type="checkbox"]:checked {
2039
+ box-shadow: 0 0 0 2px #06ffa5;
2040
  }
2041
 
2042
+ .popup-field-text {
2043
+ font-weight: 500;
2044
+ color: #222;
2045
  }
2046
 
2047
+ .popup-actions {
2048
  display: flex;
2049
+ justify-content: space-between;
2050
  gap: 8px;
2051
  }
2052
 
2053
+ .popup-action-btn {
2054
+ flex: 1;
2055
+ border: none;
2056
  border-radius: 8px;
2057
+ padding: 8px 0;
2058
+ font-weight: 600;
2059
+ font-size: 1em;
2060
  cursor: pointer;
2061
+ transition: background 0.18s, color 0.18s, box-shadow 0.18s;
2062
+ display: flex;
2063
+ align-items: center;
2064
+ justify-content: center;
2065
+ gap: 6px;
2066
  }
2067
 
2068
+ .popup-action-btn.clear-btn {
2069
+ background: #f8f8f8;
2070
+ color: #ff006e;
2071
+ }
2072
+ .popup-action-btn.clear-btn:hover {
2073
+ background: #ffe6f0;
2074
+ color: #d1005b;
2075
+ }
 
 
2076
 
2077
+ .popup-action-btn.selectall-btn {
2078
+ background: #f8f8f8;
2079
+ color: #06ffa5;
2080
+ }
2081
+ .popup-action-btn.selectall-btn:hover {
2082
+ background: #e6fff7;
2083
+ color: #00d4ff;
2084
  }
2085
 
2086
+ .popup-action-btn.save-btn {
2087
+ background: linear-gradient(135deg,#00d4ff,#06ffa5,#ff006e);
2088
+ color: #fff;
2089
+ box-shadow: 0 2px 8px rgba(0,212,255,0.10);
2090
+ }
2091
+ .popup-action-btn.save-btn:hover {
2092
+ background: linear-gradient(135deg,#06ffa5,#00d4ff,#ff006e);
2093
+ color: #fff;
2094
+ box-shadow: 0 4px 16px rgba(0,212,255,0.18);
2095
+ }
2096
 
2097
+ /* ===== SUBMIT SUCCESS POPUP ===== */
2098
+ .submit-popup-backdrop {
2099
+ position: fixed;
2100
+ inset: 0;
2101
+ background: rgba(0,0,0,0.35);
2102
+ z-index: 3000;
2103
+ display: flex;
2104
+ align-items: center;
2105
+ justify-content: center;
2106
+ }
2107
+ .submit-popup-modal {
2108
+ background: #fff;
2109
+ border-radius: 16px;
2110
+ box-shadow: 0 8px 32px #38bdf844, 0 0 24px #1e293b88;
2111
+ padding: 32px 36px 28px 36px;
2112
+ min-width: 320px;
2113
+ max-width: 90vw;
2114
+ text-align: center;
2115
+ z-index: 3001;
2116
+ display: flex;
2117
+ flex-direction: column;
2118
+ align-items: center;
2119
+ }
2120
+ .submit-popup-content h3 {
2121
+ color: #38bdf8;
2122
+ margin: 12px 0 8px 0;
2123
+ font-size: 1.4em;
2124
+ font-weight: 700;
2125
+ }
2126
+ .submit-popup-content p {
2127
+ color: #23272b;
2128
+ font-size: 1.08em;
2129
+ margin-bottom: 18px;
2130
+ }
2131
+ .submit-popup-btn {
2132
+ background: linear-gradient(90deg, #38bdf8 0%, #23272b 100%);
2133
+ color: #fff;
2134
+ font-size: 1em;
2135
+ font-weight: 600;
2136
+ border: none;
2137
+ border-radius: 8px;
2138
+ padding: 10px 32px;
2139
+ box-shadow: 0 2px 12px #38bdf888;
2140
+ cursor: pointer;
2141
+ transition: background 0.3s, color 0.3s, box-shadow 0.3s;
2142
  }
2143
+ .submit-popup-btn:hover {
2144
+ background: linear-gradient(90deg, #23272b 0%, #38bdf8 100%);
2145
+ color: #bae6fd;
2146
+ box-shadow: 0 2px 24px #bae6fd88;
2147
+ }
2148
+ .submit-popup-icon {
2149
+ color: #38bdf8;
2150
+ font-size: 2.6em;
2151
+ margin-bottom: 8px;
2152
+ }
2153
+
src/app/infopage/infopage.component.html CHANGED
@@ -1,245 +1,296 @@
1
- <!-- Add gap below title row -->
2
- <div style="height:128px;"></div>
3
-
4
- <!-- Header (in normal flow so content appears below it) -->
5
- <div class="logo-title-row">
6
- <img src="/assets/pykara-logo.png" alt="PyDetect Logo" class="logo-img" (click)="navigateHome()" style="cursor:pointer;" />
7
- <div class="py-detect-title">
8
- <span class="py-letter p">P</span>
9
- <span class="py-letter y">Y</span>
10
- <span class="py-shape"></span>
11
- <span class="py-letter d">D</span>
12
- <span class="py-letter e">E</span>
13
- <span class="py-letter t">T</span>
14
- <span class="py-letter e2">E</span>
15
- <span class="py-letter c">C</span>
16
- <span class="py-letter t2">T</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  </div>
18
  </div>
19
 
20
- <!-- Main content starts below header -->
21
- <main class="content" role="main">
22
- <div class="wrapper">
23
- <h1 class="page-title">Investigation Form</h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- <!-- Progress -->
26
- <div class="steps">
27
- <div class="step" [class.active]="step === 1">1. Crime</div>
28
- <div class="step" [class.active]="step === 2">2. Suspect</div>
29
- <div class="step" [class.active]="step === 3">3. Notes & Attachments</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  </div>
 
 
 
31
 
32
- <form [formGroup]="form" novalidate>
33
- <!-- Step 1: Crime -->
34
- <section *ngIf="step === 1" class="card" [formGroup]="crimeGroup">
35
- <h2>Crime Information</h2>
36
-
37
- <div class="grid">
38
- <label>
39
- <span class="label-main">Case ID</span>
40
- <input formControlName="caseId" placeholder="Auto or manual" />
41
- </label>
42
-
43
- <label>
44
- <span class="label-main">Crime Type <span class="req">*</span></span>
45
- <select formControlName="crimeType">
46
- <option value="" disabled>Select</option>
47
- <option *ngFor="let t of crimeTypes" [value]="t">{{ t }}</option>
48
- </select>
49
- <small class="error" *ngIf="crimeGroup.get('crimeType')?.touched && crimeGroup.get('crimeType')?.invalid">
50
- Crime type is required.
51
- </small>
52
- </label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- <label>
55
- <span class="label-main">Date & Time <span class="req">*</span></span>
56
- <input type="datetime-local" formControlName="dateTime" />
57
- <small class="error" *ngIf="crimeGroup.get('dateTime')?.touched && crimeGroup.get('dateTime')?.invalid">
58
- Date & time is required.
59
- </small>
60
- </label>
61
 
62
- <label>
63
- <span class="label-main">Location <span class="req">*</span></span>
64
- <input formControlName="location" placeholder="Address / Area" />
65
- <small class="error" *ngIf="crimeGroup.get('location')?.touched && crimeGroup.get('location')?.invalid">
66
- Location is required.
67
- </small>
68
- </label>
69
 
70
- <label>
71
- <span class="label-main">Reported By (Name)</span>
72
- <input formControlName="reportedByName" />
73
- </label>
 
 
74
 
75
- <label>
76
- <span class="label-main">Reported By (Contact Number)</span>
77
- <input formControlName="reportedByContact" />
 
 
 
 
 
 
78
  </label>
79
 
80
- <label class="full">
81
- <span class="label-main">Brief Description <span class="req">*</span></span>
82
- <textarea rows="4" formControlName="description" placeholder="Short summary"></textarea>
83
- <small class="error" *ngIf="crimeGroup.get('description')?.touched && crimeGroup.get('description')?.invalid">
84
- Description (min 10 characters) is required.
85
- </small>
86
- </label>
87
- </div>
88
- </section>
89
-
90
- <!-- Step 2: Suspect -->
91
- <section *ngIf="step === 2" class="card" [formGroup]="suspectGroup">
92
- <h2>Suspect Details</h2>
93
-
94
- <div class="scrollable-suspect-form">
95
- <div class="grid">
96
- <label>
97
- <span class="label-main">Suspect ID</span>
98
- <input formControlName="suspectId" />
99
- </label>
100
-
101
- <label>
102
- <span class="label-main">Full Name <span class="req">*</span></span>
103
- <input formControlName="fullName" />
104
- <small class="error" *ngIf="suspectGroup.get('fullName')?.touched && suspectGroup.get('fullName')?.invalid">
105
- Full name is required.
106
- </small>
107
- </label>
108
-
109
- <label>
110
- <span class="label-main">Alias / Nickname</span>
111
- <input formControlName="alias" />
112
- </label>
113
-
114
- <label>
115
- <span class="label-main">Date of Birth</span>
116
- <input type="date" formControlName="dob" />
117
- </label>
118
-
119
- <label>
120
- <span class="label-main">Gender <span class="req">*</span></span>
121
- <select formControlName="gender">
122
- <option *ngFor="let g of genders" [value]="g">{{ g }}</option>
123
- </select>
124
- </label>
125
-
126
- <label class="full">
127
- <span class="label-main">Address / Known Address</span>
128
- <input formControlName="address" />
129
- </label>
130
-
131
- <div class="full">
132
- <div class="row-between">
133
- <strong>Known Locations</strong>
134
- <button type="button" class="link" (click)="addKnownLocation()">+ Add location</button>
135
  </div>
136
- <div formArrayName="knownLocations" class="list">
137
- <div class="list-item" *ngFor="let ctrl of knownLocations.controls; let i = index">
138
- <input [formControlName]="i" placeholder="e.g., Workplace, Frequent area" />
139
- <button type="button" class="danger" (click)="removeKnownLocation(i)">Remove</button>
 
 
 
140
  </div>
141
  </div>
142
  </div>
143
 
144
- <fieldset class="full" formGroupName="contact">
145
- <legend>Contact</legend>
146
- <div class="grid">
147
- <label>
148
- <span class="label-main">Phone</span>
149
- <input formControlName="phone" />
150
- </label>
151
- <label>
152
- <span class="label-main">Email</span>
153
- <input formControlName="email" />
154
- <small class="error" *ngIf="suspectGroup.get('contact.email')?.touched && suspectGroup.get('contact.email')?.invalid">
155
- Enter a valid email.
156
- </small>
157
- </label>
158
- </div>
159
- </fieldset>
160
-
161
- <fieldset class="full" formGroupName="physical">
162
- <legend>Physical Description</legend>
163
- <div class="grid">
164
- <label>
165
- <span class="label-main">Height (cm)</span>
166
- <input type="number" formControlName="heightCm" />
167
- </label>
168
- <label>
169
- <span class="label-main">Weight (kg)</span>
170
- <input type="number" formControlName="weightKg" />
171
- </label>
172
- <label>
173
- <span class="label-main">Build</span>
174
- <input formControlName="build" placeholder="Slim / Average / Heavy" />
175
- </label>
176
- <label>
177
- <span class="label-main">Hair Color</span>
178
- <input formControlName="hairColor" />
179
- </label>
180
- <label>
181
- <span class="label-main">Eye Color</span>
182
- <input formControlName="eyeColor" />
183
- </label>
184
- <label class="full">
185
- <span class="label-main">Distinguishing Marks</span>
186
- <input formControlName="marks" placeholder="Tattoos, scars, etc." />
187
- </label>
188
- </div>
189
- </fieldset>
190
 
191
- <div class="full">
192
- <label>
193
- <span class="label-main">Photo Upload</span>
194
- <input type="file" accept="image/*" (change)="onPhotoChange($event)" />
195
- </label>
196
- </div>
197
- </div>
198
- </div>
199
- </section>
200
 
201
- <!-- Step 3: Notes & Attachments -->
202
- <section *ngIf="step === 3" class="card" [formGroup]="notesGroup">
203
- <h2>Investigation Notes</h2>
 
 
 
 
 
 
 
 
204
 
205
- <div class="grid">
206
- <label class="full">
207
- <span class="label-main">Initial Findings</span>
208
- <textarea rows="4" formControlName="initialFindings"></textarea>
209
- </label>
 
 
 
 
 
210
 
211
- <label>
212
- <span class="label-main">Investigation Officer</span>
213
- <input formControlName="Investigation Officer" placeholder="Select/Type officer name" />
214
- </label>
 
 
 
 
 
 
215
 
216
- <label>
217
- <span class="label-main">Status <span class="req">*</span></span>
218
- <select formControlName="status">
219
- <option *ngFor="let s of statuses" [value]="s">{{ s }}</option>
220
- </select>
221
- </label>
 
 
 
 
 
 
 
 
 
222
 
223
- <div class="full">
224
- <label>
225
- <span class="label-main">Attachments (CCTV, documents, etc.)</span>
226
- <input type="file" multiple (change)="onAttachmentsChange($event)" />
227
- </label>
228
- <ul class="files">
229
- <li *ngFor="let c of attachments.controls; let i = index">
230
- {{ c.value?.name || ('Attachment ' + (i + 1)) }}
231
- </li>
232
- </ul>
233
  </div>
234
  </div>
235
- </section>
236
-
237
- <!-- Actions -->
238
- <div class="actions">
239
- <button type="button" (click)="back()" [disabled]="step===1">Back</button>
240
- <button type="button" (click)="next()" *ngIf="step<3">Next</button>
241
- <button type="button" class="primary" (click)="submit()" *ngIf="step===3">Submit Case</button>
242
  </div>
243
- </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  </div>
245
- </main>
 
 
1
+ <!-- AI-Enhanced Remote Tech Jobs Style Header -->
2
+ <div class="site-header">
3
+ <div class="header-inner">
4
+ <div class="logo-cluster">
5
+ <span [routerLink]="'/'" 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-progress">
21
+ <span class="pykara-analysis-label">Progress:</span>
22
+ <div class="pykara-progress-bar">
23
+ <div class="pykara-progress-bar-inner" [style.width.%]="progressPercentage"></div>
24
+ </div>
25
+ <span class="pykara-progress-percentage">{{ progressPercentage }}%</span>
26
+ </div>
27
+ <div class="header-actions autosave-right">
28
+ <div class="autosave-indicator" [class.saving]="isAutoSaving">
29
+ <i class="fas fa-save"></i>
30
+ <span>{{ autoSaveStatus }}</span>
31
+ </div>
32
+ </div>
33
  </div>
34
  </div>
35
 
36
+ <!-- AI-Enhanced Tech Jobs Style Section Navigation -->
37
+ <div class="section-navigation" [@fadeIn]>
38
+ <div class="ai-neural-bg"></div>
39
+ <div class="section-pills">
40
+ <button class="section-pill main-section-pill ai-enhanced"
41
+ *ngFor="let section of sectionKeys; let i = index"
42
+ [class.active]="currentSection === section"
43
+ [class.completed]="isSectionCompleted(section)"
44
+ [attr.data-step]="i + 1"
45
+ (click)="showSection(section)"
46
+ [attr.tabindex]="0">
47
+ <div class="pill-neural-core"></div>
48
+ <i [class]="sectionIcons[section]"></i>
49
+ <span>{{ sections[section].title }}</span>
50
+ <div class="ai-completion-orb" *ngIf="isSectionCompleted(section)">
51
+ <div class="orb-pulse"></div>
52
+ <i class="fas fa-check"></i>
53
+ </div>
54
+ <div class="pill-scanner"></div>
55
+ </button>
56
+ </div>
57
+ <div class="section-ai-grid"></div>
58
+ </div>
59
 
60
+ <!-- AI-Enhanced Job Category Style Subgroup Navigation with Section-Specific Styling -->
61
+ <div class="subgroup-pills"
62
+ [class.crime-section]="currentSection === 'crime'"
63
+ [class.suspect-section]="currentSection === 'suspect'"
64
+ [class.notes-section]="currentSection === 'notes'"
65
+ [@fadeIn]>
66
+ <div class="neural-mesh"></div>
67
+ <div class="pill ai-subgroup-pill"
68
+ *ngFor="let key of getSubgroups(); let i = index"
69
+ [class.active]="key === currentSubgroup"
70
+ [class.completed]="isSubgroupCompleted(key)"
71
+ (click)="setSubgroup(key)"
72
+ [attr.tabindex]="0">
73
+ <div class="pill-ai-core"></div>
74
+ <i [class]="subgroupIcons[key] || 'fas fa-circle'"></i>
75
+ <span>{{ key }}</span>
76
+ <div class="ai-completion-badge" *ngIf="isSubgroupCompleted(key)">
77
+ <div class="badge-glow"></div>
78
+ <i class="fas fa-check-circle"></i>
79
  </div>
80
+ <div class="pill-ai-scanner"></div>
81
+ </div>
82
+ </div>
83
 
84
+ <!-- Job Cards Style Layout - Universal Single Card Layout for All Pages -->
85
+ <div class="investigation-container">
86
+
87
+ <!-- Universal Single Investigation Card -->
88
+ <div class="form-card primary-card" [@cardSlide] #formCard1>
89
+ <div class="card-header compact-card-header">
90
+ <div class="card-header-main">
91
+ <h2 class="compact-title">
92
+ <i [class]="subgroupIcons[currentSubgroup] || 'fas fa-circle'"></i>
93
+ {{ currentSubgroup }}
94
+ <span class="field-counter compact-field-counter">{{ getSelectedFieldCount() }} of {{ getTotalAvailableFieldsCount() }}</span>
95
+ </h2>
96
+ </div>
97
+ <div class="modern-field-selector-btn compact-selector-btn" style="position:relative;">
98
+ <button class="field-selector-btn modern-selector-animated compact-selector-btn-inner"
99
+ [class.active]="showFieldSelector === (currentSection + '-' + currentSubgroup)"
100
+ (click)="toggleFieldSelector($event)"
101
+ type="button"
102
+ title="Select fields to display">
103
+ <i class="fas fa-list-check"></i>
104
+ <span class="field-selector-counter">{{ getSelectedFieldCount() }}/{{ getDynamicMaxSelectable() }}</span>
105
+ </button>
106
+ <!-- Field Selector Popup: floating, anchored to button -->
107
+ <div class="modern-field-selector-popup"
108
+ *ngIf="showFieldSelector === (currentSection + '-' + currentSubgroup)">
109
+ <div class="popup-header">
110
+ <span><i class="fas fa-list-check"></i> Select Fields to Display</span>
111
+ <button class="popup-close-btn" (click)="closeFieldSelector()" type="button">
112
+ <i class="fas fa-times"></i>
113
+ </button>
114
+ </div>
115
+ <div class="popup-fields-list">
116
+ <div *ngFor="let field of getAvailableFields(); trackBy: trackByField" class="popup-field-row">
117
+ <label class="popup-field-label">
118
+ <input type="checkbox"
119
+ [checked]="isFieldSelected(field)"
120
+ (change)="toggleFieldSelection(field, $event)" />
121
+ <span class="popup-field-text">{{ field }}</span>
122
+ </label>
123
+ </div>
124
+ </div>
125
+ <div class="popup-actions">
126
+ <button class="popup-action-btn clear-btn" (click)="resetFieldSelection($event)" type="button">
127
+ <i class="fas fa-times-circle"></i> Clear All
128
+ </button>
129
+ <button class="popup-action-btn selectall-btn" (click)="selectAllFields($event)" type="button">
130
+ <i class="fas fa-check-double"></i> Select All
131
+ </button>
132
+ <button class="popup-action-btn save-btn" (click)="closeFieldSelector()" type="button">
133
+ <i class="fas fa-save"></i> Save
134
+ </button>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ </div>
139
 
 
 
 
 
 
 
 
140
 
141
+ <div class="card-content" [class.minimized]="isCardMinimized.primary">
142
+ <div class="section-description" [@fadeIn]>
143
+ <strong>{{ sections[currentSection].title }} - {{ currentSubgroup }}:</strong> {{ getSectionDescription(currentSection) }}
144
+ </div>
 
 
 
145
 
146
+ <div class="fields-grid">
147
+ <div *ngFor="let field of getPrimaryFields(); let i = index; trackBy: trackByField"
148
+ class="field-container"
149
+ [class.filled]="formData[field] && formData[field].toString().trim() !== ''"
150
+ [attr.data-field]="field"
151
+ [@fieldAnimation]>
152
 
153
+ <label class="field-label">
154
+ {{ field }}
155
+ <span class="required-indicator" *ngIf="isFieldRequired(field)">*</span>
156
+ <button type="button"
157
+ class="info-btn glossy-info-btn"
158
+ (click)="toggleFieldInfo(field, $event)"
159
+ [attr.aria-label]="'Info for ' + field">
160
+ <i class="fas fa-info"></i>
161
+ </button>
162
  </label>
163
 
164
+ <!-- Dynamic Field Rendering -->
165
+ <div class="input-container">
166
+ <!-- File Upload Fields -->
167
+ <div *ngIf="fileFields.has(field); else nonFileField" class="file-upload-block">
168
+ <div class="file-drop-zone"
169
+ (dragover)="onDragOver($event)"
170
+ (dragleave)="onDragLeave($event)"
171
+ (drop)="onFileDrop(field, $event)"
172
+ [class.drag-over]="isDragOver">
173
+ <input type="file"
174
+ (change)="onFileChange(field, $event)"
175
+ multiple
176
+ [accept]="getAcceptedFileTypes(field)"
177
+ class="file-input" />
178
+ <div class="drop-zone-content">
179
+ <i class="fas fa-cloud-upload-alt"></i>
180
+ <p>Drop files here or click to browse</p>
181
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  </div>
183
+ <div class="file-list" *ngIf="uploadedFiles[field]?.length">
184
+ <div *ngFor="let f of uploadedFiles[field]" class="file-chip">
185
+ <i [class]="getFileIcon(f.name)"></i>
186
+ <span [title]="f.name">{{ f.name | slice:0:15 }}{{ f.name.length > 15 ? '...' : '' }}</span>
187
+ <button class="remove-file" (click)="removeFile(field, f)">
188
+ <i class="fas fa-times"></i>
189
+ </button>
190
  </div>
191
  </div>
192
  </div>
193
 
194
+ <ng-template #nonFileField>
195
+ <!-- Date/Time Fields -->
196
+ <ng-container *ngIf="dateTimeFields.has(field); else nonDateTime">
197
+ <input type="datetime-local"
198
+ class="field-input"
199
+ [class.compact]="isCompactField(field)"
200
+ [(ngModel)]="formData[field]"
201
+ (input)="onFieldChange(field)" />
202
+ </ng-container>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
+ <ng-template #nonDateTime>
205
+ <ng-container *ngIf="dateFields.has(field); else nonDate">
206
+ <input type="date"
207
+ class="field-input"
208
+ [class.compact]="isCompactField(field)"
209
+ [(ngModel)]="formData[field]"
210
+ (input)="onFieldChange(field)" />
211
+ </ng-container>
 
212
 
213
+ <ng-template #nonDate>
214
+ <!-- Cascading Dropdowns -->
215
+ <ng-container *ngIf="field === 'Country' || field === 'State' || field === 'District'; else standardField">
216
+ <select class="field-input"
217
+ [class.compact]="isCompactField(field)"
218
+ [(ngModel)]="formData[field]"
219
+ (change)="onSelectChange(field, $event)">
220
+ <option value="">-- Select {{ field }} --</option>
221
+ <option *ngFor="let opt of getOptions(field)" [value]="opt">{{ opt }}</option>
222
+ </select>
223
+ </ng-container>
224
 
225
+ <ng-template #standardField>
226
+ <!-- Standard Select or Text Input -->
227
+ <select *ngIf="getOptions(field)?.length; else textInput"
228
+ class="field-input"
229
+ [class.compact]="isCompactField(field)"
230
+ [(ngModel)]="formData[field]"
231
+ (change)="onSelectChange(field, $event)">
232
+ <option value="">-- Select Option --</option>
233
+ <option *ngFor="let opt of getOptions(field)" [value]="opt">{{ opt }}</option>
234
+ </select>
235
 
236
+ <ng-template #textInput>
237
+ <!-- Description fields as textarea -->
238
+ <textarea *ngIf="field.toLowerCase().includes('description'); else regularInput"
239
+ class="field-input"
240
+ [class.compact]="isCompactField(field)"
241
+ [(ngModel)]="formData[field]"
242
+ (input)="onFieldChange(field)"
243
+ [placeholder]="getFieldPlaceholder(field)"
244
+ [maxlength]="getMaxLength(field)"
245
+ rows="3"></textarea>
246
 
247
+ <ng-template #regularInput>
248
+ <input [type]="getInputType(field)"
249
+ class="field-input"
250
+ [class.compact]="isCompactField(field)"
251
+ [(ngModel)]="formData[field]"
252
+ (input)="onFieldChange(field)"
253
+ [placeholder]="getFieldPlaceholder(field)"
254
+ [maxlength]="getMaxLength(field)" />
255
+ </ng-template>
256
+ </ng-template>
257
+ </ng-template>
258
+ </ng-template>
259
+ </ng-template>
260
+ </ng-template>
261
+ </div>
262
 
263
+ <!-- Field Help Popover -->
264
+ <div class="field-help" *ngIf="showHelpFor === field" [@helpAnimation]>
265
+ <strong>Field Information:</strong><br>
266
+ {{ fieldDescriptions[field] || 'Additional details for this field are being prepared.' }}
 
 
 
 
 
 
267
  </div>
268
  </div>
 
 
 
 
 
 
 
269
  </div>
270
+ </div>
271
+ <!-- Removed modern-nav-btns from here to prevent scrolls -->
272
+ </div>
273
+ </div>
274
+
275
+ <!-- Floating Modern Navigation Button (fixed, no scroll) -->
276
+ <div class="modern-floating-nav-btn">
277
+ <button *ngIf="!isLastSubgroup()" type="button" class="modern-round-btn next-btn-animated" (click)="nextSubgroup()" [disabled]="!canNextSubgroup()" title="Next">
278
+ <i class="fas fa-arrow-right"></i>
279
+ </button>
280
+ <button *ngIf="isLastSubgroup()" type="button" class="modern-round-btn submit-btn-animated" (click)="submitCurrentSection()" title="Submit">
281
+ <i class="fas fa-paper-plane"></i>
282
+ </button>
283
+ </div>
284
+
285
+ <!-- Submission Success Popup - Enhanced with Clickable Background and Navigation on Close -->
286
+ <div *ngIf="showSubmitPopup" class="submit-popup-backdrop">
287
+ <div class="submit-popup-modal">
288
+ <div class="submit-popup-content" (click)="onSubmitPopupClose()" style="cursor:pointer;">
289
+ <i class="fas fa-check-circle submit-popup-icon"></i>
290
+ <h3>Submission Successful!</h3>
291
+ <p>Your information has been submitted.</p>
292
+ <button class="submit-popup-btn">OK</button>
293
+ </div>
294
  </div>
295
+ </div>
296
+
src/app/infopage/infopage.component.ts CHANGED
@@ -1,223 +1,1145 @@
1
- import { Component, OnInit } from '@angular/core';
2
- import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
3
- import { Router, ActivatedRoute } from '@angular/router';
4
- import { CaseStoreService, PoliceCase } from '../case-store.service';
5
-
6
- type CrimeType = 'Theft' | 'Assault' | 'Fraud' | 'Murder' | 'Cybercrime' | 'Other';
7
- type Gender = 'Male' | 'Female' | 'Other';
8
- type CaseStatus = 'Open' | 'Under Investigation' | 'Closed';
9
 
10
  @Component({
11
  selector: 'app-infopage',
12
  templateUrl: './infopage.component.html',
13
- styleUrls: ['./infopage.component.css']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  })
15
- export class InfopageComponent implements OnInit {
16
- step = 1;
17
-
18
- crimeTypes: CrimeType[] = ['Theft', 'Assault', 'Fraud', 'Murder', 'Cybercrime', 'Other'];
19
- genders: Gender[] = ['Male', 'Female', 'Other'];
20
- statuses: CaseStatus[] = ['Open', 'Under Investigation', 'Closed'];
21
-
22
- // If navigating here via Edit, we store the index to update.
23
- private editIndex: number | null = null;
24
-
25
- form: FormGroup = this.fb.group({
26
- crime: this.fb.group({
27
- caseId: [''],
28
- crimeType: ['', Validators.required],
29
- dateTime: ['', Validators.required],
30
- location: ['', Validators.required],
31
- reportedByName: [''],
32
- reportedByContact: [''],
33
- description: ['', [Validators.required, Validators.minLength(10)]],
34
- }),
35
-
36
- suspect: this.fb.group({
37
- suspectId: [''],
38
- fullName: ['', Validators.required],
39
- alias: [''],
40
- dob: [''],
41
- age: ['', [Validators.min(0)]],
42
- gender: ['Male' as Gender, Validators.required],
43
- address: [''],
44
- knownLocations: this.fb.array([]),
45
- contact: this.fb.group({
46
- phone: [''],
47
- email: ['', Validators.email],
48
- }),
49
- physical: this.fb.group({
50
- heightCm: [''],
51
- weightKg: [''],
52
- build: [''],
53
- hairColor: [''],
54
- eyeColor: [''],
55
- marks: [''],
56
- }),
57
- photo: [null], // File
58
- }),
59
-
60
- notes: this.fb.group({
61
- initialFindings: [''],
62
- officerInCharge: [''],
63
- status: ['Open' as CaseStatus, Validators.required],
64
- attachments: this.fb.array([]), // Files
65
- }),
66
- });
67
-
68
- constructor(
69
- private fb: FormBuilder,
70
- private router: Router,
71
- private route: ActivatedRoute,
72
- private caseStore: CaseStoreService
73
- ) { }
74
-
75
- // ----------------- lifecycle -----------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  ngOnInit(): void {
77
- // If navigated with router state from Record page Edit action
78
- const state: any = history.state || {};
79
- if (typeof state.editIndex === 'number' && state.editCase) {
80
- this.editIndex = state.editIndex;
81
- this.patchFromCase(state.editCase as PoliceCase);
82
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  }
84
- // If navigated via /infopage/:id, load by caseId
85
- const caseId = this.route.snapshot.paramMap.get('id');
86
- if (caseId) {
87
- const cases = this.caseStore.getPoliceCases();
88
- const idx = cases.findIndex(c => c.caseId == caseId);
89
- if (idx !== -1) {
90
- this.editIndex = idx;
91
- this.patchFromCase(cases[idx]);
 
 
 
 
 
 
92
  }
93
  }
 
 
 
94
  }
95
 
96
- // ----------------- getters -----------------
97
- get crimeGroup() { return this.form.get('crime') as FormGroup; }
98
- get suspectGroup() { return this.form.get('suspect') as FormGroup; }
99
- get notesGroup() { return this.form.get('notes') as FormGroup; }
100
-
101
- get knownLocations(): FormArray {
102
- return this.suspectGroup.get('knownLocations') as FormArray;
103
- }
104
- get attachments(): FormArray {
105
- return this.notesGroup.get('attachments') as FormArray;
106
- }
107
-
108
- // ----------------- helpers (arrays) -----------------
109
- addKnownLocation() { this.knownLocations.push(new FormControl('')); }
110
- removeKnownLocation(index: number) { this.knownLocations.removeAt(index); }
111
-
112
- // ----------------- file helpers -----------------
113
- setPhoto(file: File | null) { this.suspectGroup.get('photo')?.setValue(file || null); }
114
- addAttachment(file: File) { this.attachments.push(new FormControl(file)); }
115
-
116
- onPhotoChange(event: Event): void {
117
- const input = event.target as HTMLInputElement | null;
118
- let file: File | null = null;
119
- if (input && input.files && input.files.length > 0) file = input.files[0];
120
- this.setPhoto(file);
121
- if (input) input.value = '';
122
- }
123
-
124
- onAttachmentsChange(event: Event): void {
125
- const input = event.target as HTMLInputElement | null;
126
- if (!input || !input.files || input.files.length === 0) return;
127
- for (let i = 0; i < input.files.length; i++) {
128
- const f = input.files.item(i);
129
- if (f) this.addAttachment(f);
130
- }
131
- input.value = '';
132
- }
133
-
134
- // ----------------- wizard -----------------
135
- next() {
136
- if (this.step === 1 && this.crimeGroup.invalid) { this.crimeGroup.markAllAsTouched(); return; }
137
- if (this.step === 2 && this.suspectGroup.invalid) { this.suspectGroup.markAllAsTouched(); return; }
138
- this.step = Math.min(3, this.step + 1);
139
- }
140
- back() { this.step = Math.max(1, this.step - 1); }
141
-
142
- // ----------------- edit support -----------------
143
- private patchFromCase(c: PoliceCase): void {
144
- // Patch only fields guaranteed by your current PoliceCase type
145
- // (do not read c.caseId / c.dateTime here to avoid TS errors)
146
- this.form.patchValue({
147
- crime: {
148
- caseId: '', // optional: leave as is or derive from elsewhere
149
- crimeType: c.crime || '',
150
- dateTime: '', // optional: leave as is
151
- location: c.police?.address || '',
152
- reportedByName: '',
153
- reportedByContact: '',
154
- description: c.police?.information || ''
155
- },
156
- suspect: {
157
- suspectId: '',
158
- fullName: c.accused?.name || '',
159
- alias: c.accused?.occupation || '',
160
- dob: '',
161
- age: c.accused?.age || '',
162
- gender: c.accused?.gender || '',
163
- address: c.accused?.address || ''
164
- },
165
- notes: {
166
- initialFindings: c.police?.information || '',
167
- officerInCharge: c.police?.name || '',
168
- status: 'Open',
169
  }
170
- });
171
- // Reset arrays (optional)
172
- this.knownLocations.clear();
173
- this.attachments.clear();
174
- this.step = 1;
175
- }
176
-
177
- private mapToPoliceCase(): PoliceCase {
178
- const v: any = this.form.value;
179
- // Build an object that matches your current PoliceCase type (no caseId/dateTime)
180
- return {
181
- crime: v.crime?.crimeType || 'Unknown',
182
- police: {
183
- name: v.notes?.officerInCharge || '—',
184
- station: '—',
185
- address: v.crime?.location || '—',
186
- pincode: '',
187
- dutyPerson: v.notes?.officerInCharge || '—',
188
- modeOfCrime: v.crime?.crimeType || '—',
189
- information: v.notes?.initialFindings || ''
190
- },
191
- accused: {
192
- name: v.suspect?.fullName || '—',
193
- age: v.suspect?.age || '—',
194
- gender: v.suspect?.gender || '—',
195
- address: v.suspect?.address || '—',
196
- occupation: v.suspect?.alias || ''
197
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  }
200
 
201
- // ----------------- submit -----------------
202
- submit() {
203
- if (this.form.invalid) {
204
- this.form.markAllAsTouched();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  return;
206
  }
207
 
208
- if (this.editIndex !== null) {
209
- // Update existing record (service must have updatePoliceCaseAt; see earlier message)
210
- this.caseStore.updatePoliceCaseAt(this.editIndex, this.mapToPoliceCase() as PoliceCase);
211
- } else {
212
- // Add new record using your existing service mapping
213
- this.caseStore.addFromInfoForm(this.form.value);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  }
 
215
 
216
- // Navigate to record page
217
- this.router.navigate(['/record']);
 
 
 
 
 
 
 
 
 
218
  }
219
 
220
- navigateHome(): void {
221
- this.router.navigate(['/']);
 
222
  }
223
  }
 
1
+ import { Component, HostListener, ElementRef, ViewChild, AfterViewInit, OnInit, OnDestroy } from '@angular/core';
2
+ import { trigger, state, style, transition, animate } from '@angular/animations';
3
+ import { Subject, debounceTime, takeUntil } from 'rxjs';
4
+ import { Router } from '@angular/router';
5
+ import { CaseStoreService } from '../case-store.service';
 
 
 
6
 
7
  @Component({
8
  selector: 'app-infopage',
9
  templateUrl: './infopage.component.html',
10
+ styleUrls: ['./infopage.component.css'],
11
+ animations: [
12
+ // Simple card animation
13
+ trigger('cardSlide', [
14
+ transition(':enter', [
15
+ style({ transform: 'translateY(20px)', opacity: 0 }),
16
+ animate('300ms ease-out',
17
+ style({ transform: 'translateY(0)', opacity: 1 }))
18
+ ])
19
+ ]),
20
+ // Field animation
21
+ trigger('fieldAnimation', [
22
+ transition(':enter', [
23
+ style({ opacity: 0, transform: 'translateY(10px)' }),
24
+ animate('200ms ease-out',
25
+ style({ opacity: 1, transform: 'translateY(0)' }))
26
+ ])
27
+ ]),
28
+ // Simple fade animation
29
+ trigger('fadeIn', [
30
+ transition(':enter', [
31
+ style({ opacity: 0 }),
32
+ animate('200ms ease-in', style({ opacity: 1 }))
33
+ ]),
34
+ transition(':leave', [
35
+ animate('150ms ease-out', style({ opacity: 0 }))
36
+ ])
37
+ ]),
38
+ // Help animation
39
+ trigger('helpAnimation', [
40
+ transition(':enter', [
41
+ style({ opacity: 0, transform: 'translateY(-10px)' }),
42
+ animate('200ms ease-out',
43
+ style({ opacity: 1, transform: 'translateY(0)' }))
44
+ ]),
45
+ transition(':leave', [
46
+ animate('150ms ease-in',
47
+ style({ opacity: 0, transform: 'translateY(-10px)' }))
48
+ ])
49
+ ])
50
+ ]
51
  })
52
+ export class InfopageComponent implements OnInit, AfterViewInit, OnDestroy {
53
+ showRemarkModal: boolean = false;
54
+ showSubmitPopup: boolean = false;
55
+ constructor(private router: Router, private caseStore: CaseStoreService) {}
56
+ // Core state
57
+ currentSection: 'crime' | 'suspect' | 'notes' = 'crime';
58
+ currentSubgroup: string = 'Identification & Timing';
59
+ showHelpFor: string | null = null;
60
+
61
+ // UI state
62
+ isAutoSaving: boolean = false;
63
+ autoSaveStatus: string = 'Saved';
64
+ isDragOver: boolean = false;
65
+
66
+ // Card state
67
+ isCardMinimized = {
68
+ primary: false,
69
+ secondary: false,
70
+ tertiary: false
71
+ };
72
+
73
+ // Form data and validation
74
+ formData: Record<string, any> = {};
75
+ fieldValidation: Record<string, { hasError: boolean, isValid: boolean, message: string }> = {};
76
+ completedFields: Set<string> = new Set();
77
+ completedSubgroups: Set<string> = new Set();
78
+ completedSections: Set<string> = new Set();
79
+
80
+ // Subjects for reactive programming
81
+ private destroy$ = new Subject<void>();
82
+ private autoSave$ = new Subject<void>();
83
+
84
+ // Constants - Reverted to original values except for Identification & Timing
85
+ readonly sectionKeys: ('crime' | 'suspect' | 'notes')[] = ['crime', 'suspect', 'notes'];
86
+ readonly maxFieldsPerCard = 8; // Reverted to original
87
+ readonly maxFieldsPerSecondaryCard = 8; // Reverted to original
88
+ readonly maxFieldsPerCardIdentificationTiming = 6; // Special for Identification & Timing
89
+ readonly maxFieldsPerSecondaryCardIdentificationTiming = 6; // Special for Identification & Timing
90
+
91
+ @ViewChild('formCard1') formCard1!: ElementRef<HTMLDivElement>;
92
+ @ViewChild('formCard2') formCard2!: ElementRef<HTMLDivElement>;
93
+ @ViewChild('formCard3') formCard3!: ElementRef<HTMLDivElement>;
94
+
95
+ // Enhanced field definitions with validation rules
96
+ readonly requiredFields = new Set<string>([
97
+ 'Case ID', 'Crime Type', 'Date & Time (Entry)', 'Location', 'Suspect Name', 'Age', 'Gender',
98
+ 'FIR / Ref #', 'Case Category', 'Occurred From', 'Country', 'State', 'District',
99
+ 'Jurisdiction / PS', 'Scene Type', 'Reported By', 'Case Status', 'Investigating Officer'
100
+ ]);
101
+
102
+ readonly compactFields = new Set<string>([
103
+ 'Age', 'Gender', 'Height (cm)', 'Weight (kg)', 'Build', 'Hair Color', 'Eye Color',
104
+ 'Number of Victims', 'Witness Count', 'Prior Arrests', 'arrest Count', 'Case Priority',
105
+ 'Photos / Video?', 'CCTV Present?', 'Arrest Made', 'risk Level', 'Confidentiality'
106
+ ]);
107
+
108
+ readonly numericFields = new Set<string>([
109
+ 'Age', 'Height (cm)', 'Weight (kg)', 'Number of Victims', 'Witness Count', 'Prior Arrests', 'arrest Count'
110
+ ]);
111
+
112
+ // File type configurations
113
+ readonly fileTypeConfig: Record<string, string> = {
114
+ 'Photo Upload': 'image/*',
115
+ 'Evidence Photos': 'image/*',
116
+ 'Evidence Videos': 'video/*',
117
+ 'Evidence Documents': '.pdf,.doc,.docx,.txt',
118
+ 'Evidence Files': '*',
119
+ 'Upload Evidence Files': '*',
120
+ 'Digital Evidence': '*'
121
+ };
122
+
123
+ // Track selected values (cascading dropdown logic)
124
+ selectedValues: Record<string, string> = {};
125
+
126
+ // Date field groups
127
+ dateTimeFields = new Set<string>(['Date & Time (Entry)', 'Occurred From', 'Occurred To', 'Time Reported', 'Time Discovered']);
128
+ dateFields = new Set<string>(['Follow-up Date', 'Next Hearing Date']);
129
+
130
+ // Country/State/District data
131
+ countries = ['India'];
132
+ indiaStates = [
133
+ 'Andhra Pradesh', 'Arunachal Pradesh', 'Assam', 'Bihar', 'Chhattisgarh', 'Goa', 'Gujarat',
134
+ 'Haryana', 'Himachal Pradesh', 'Jharkhand', 'Karnataka', 'Kerala', 'Madhya Pradesh',
135
+ 'Maharashtra', 'Manipur', 'Meghalaya', 'Mizoram', 'Nagaland', 'Odisha', 'Punjab',
136
+ 'Rajasthan', 'Sikkim', 'Tamil Nadu', 'Telangana', 'Tripura', 'Uttar Pradesh',
137
+ 'Uttarakhand', 'West Bengal'
138
+ ];
139
+
140
+ tamilNaduDistricts = [
141
+ 'Ariyalur', 'Chengalpattu', 'Chennai', 'Coimbatore', 'Cuddalore', 'Dharmapuri', 'Dindigul',
142
+ 'Erode', 'Kallakurichi', 'Kanchipuram', 'Kanyakumari', 'Karur', 'Krishnagiri', 'Madurai',
143
+ 'Mayiladuthurai', 'Nagapattinam', 'Namakkal', 'Nilgiris', 'Perambalur', 'Pudukkottai',
144
+ 'Ramanathapuram', 'Ranipet', 'Salem', 'Sivaganga', 'Tenkasi', 'Thanjavur', 'Theni',
145
+ 'Thoothukudi (Tuticorin)', 'Tiruchirappalli', 'Tirunelveli', 'Tirupathur', 'Tiruppur',
146
+ 'Tiruvallur', 'Tiruvannamalai', 'Tiruvarur', 'Vellore', 'Viluppuram', 'Virudhunagar'
147
+ ];
148
+
149
+ // Enhanced select options - Added missing field options
150
+ selectOptions: Record<string, string[]> = {
151
+ 'Crime Type': ['Theft', 'Assault', 'Homicide', 'Cybercrime', 'Fraud', 'Narcotics', 'Arson', 'Kidnapping', 'General', 'Other'],
152
+ 'Case Category': ['Property', 'Violent', 'Cyber', 'Financial', 'Public Order', 'Narcotics', 'Organized', 'General', 'Other'],
153
+ 'Number of Victims': ['0', '1', '2', '3', '4', '5+'],
154
+ 'Jurisdiction / PS': ['Central PS', 'East Division', 'West Division', 'Rural Unit', 'Cyber Cell', 'General'],
155
+ 'Scene Type': ['Residential', 'Commercial', 'Public Space', 'Vehicle', 'Rural', 'Online', 'General', 'Other'],
156
+ 'Witness Count': ['0', '1', '2', '3', '4', '5+'],
157
+ 'Victim Summary': ['Stable', 'Injured', 'Critical', 'Deceased', 'Unknown'],
158
+ 'Suspected Offender Known?': ['Yes', 'No', 'Unknown'],
159
+ 'Offence Category': ['Minor', 'Serious', 'Organized', 'Cyber', 'Financial', 'Violent', 'General', 'Other'],
160
+ 'Suspected Motive': ['Financial Gain', 'Revenge', 'Jealousy', 'Ideological', 'Political', 'Personal Dispute', 'Unknown', 'General', 'Other'],
161
+ 'Confirmed Motive': ['Financial Gain', 'Revenge', 'Jealousy', 'Ideological', 'Political', 'Personal Dispute', 'Unknown', 'General', 'Other'],
162
+ 'Weapon Involved': ['None', 'Knife', 'Firearm', 'Blunt Object', 'Explosive', 'Chemical', 'Other', 'Unknown', 'General'],
163
+ 'Property Loss / Damage': ['None', 'Minor', 'Moderate', 'Major', 'Severe', 'Unknown'],
164
+ 'Photos / Video?': ['Yes', 'No'],
165
+ 'CCTV Present?': ['Yes', 'No'],
166
+ 'Scene Condition': ['Intact', 'Disturbed', 'Contaminated', 'Secured', 'Compromised', 'General'],
167
+ 'Chain of Custody?': ['Initiated', 'Ongoing', 'Complete', 'Not Started'],
168
+ 'Forensic Tests Required': ['None', 'DNA', 'Fingerprints', 'Ballistics', 'Toxicology', 'Digital Forensics', 'Trace', 'General', 'Other'],
169
+ 'Arrest Made': ['Yes', 'No'],
170
+ 'riskLevel': ['Low', 'Medium', 'High', 'Critical'],
171
+ 'Confidentiality': ['Internal', 'Restricted', 'Sensitive', 'Sealed'],
172
+ 'Initial Actions Taken': ['Scene Secured', 'Medical Aid', 'Evidence Logged', 'Witness Statements', 'Suspect Detained', 'General', 'Other'],
173
+ 'Case Status': ['Open', 'Active', 'Suspended', 'Closed', 'Archived'],
174
+ 'Case Priority': ['Low', 'Normal', 'High', 'Urgent', 'Critical'],
175
+ 'Gender': ['Male', 'Female', 'Other'],
176
+ 'Nationality': ['India'],
177
+ 'Languages': ['English', 'Hindi', 'Tamil', 'Telugu', 'Kannada', 'Malayalam', 'Bengali', 'Marathi', 'Gujarati', 'Other'],
178
+ 'Build': ['Slim', 'Average', 'Athletic', 'Heavy', 'Obese'],
179
+ 'Hair Color': ['Black', 'Brown', 'Blonde', 'Red', 'Grey', 'White', 'Dyed / Other', 'Unknown'],
180
+ 'Eye Color': ['Brown', 'Blue', 'Green', 'Hazel', 'Grey', 'Black', 'Unknown'],
181
+ 'Employment': ['Employed', 'Unemployed', 'Self-Employed', 'Student', 'Retired', 'Unknown'],
182
+ 'Education': ['None', 'Primary', 'Secondary', 'Diploma', 'Bachelor', 'Master', 'Doctorate', 'Other'],
183
+ 'Marital Status': ['Single', 'Married', 'Divorced', 'Separated', 'Widowed', 'Unknown'],
184
+ 'Known Habits': ['Smoking', 'Alcohol', 'Substance Use', 'Gambling', 'None', 'Unknown'],
185
+ 'Occupation': ['Unskilled', 'Skilled Labour', 'Professional', 'Executive', 'Military', 'Law Enforcement', 'IT', 'Healthcare', 'Education', 'Finance', 'Other'],
186
+ 'Known Financial Details': ['None', 'Low Income', 'Moderate Income', 'High Income', 'Wealthy', 'Unknown'],
187
+ 'Gang Affiliation': ['None', 'Local', 'Regional', 'International', 'Unknown'],
188
+ 'Criminal History': ['None', 'Minor', 'Multiple', 'Serious'],
189
+ 'Prior Arrests': ['0', '1', '2', '3', '4', '5+'],
190
+ 'Probation/Parole Status': ['None', 'On Probation', 'On Parole', 'Completed', 'Unknown'],
191
+ 'Status': ['Draft', 'In Progress', 'Completed', 'Archived'],
192
+ // Additional field options for complete coverage
193
+ 'arrestCount': ['0', '1', '2', '3', '4', '5+'],
194
+ 'Linked Cases': [], // Will be populated dynamically with existing case IDs
195
+ 'Suspect Link': [], // Will be populated dynamically with existing suspect IDs
196
+ 'Government ID': ['Aadhaar Card', 'PAN Card', 'Driving License', 'Passport', 'Voter ID', 'Other'],
197
+ 'Family Connections': ['Spouse', 'Parent', 'Child', 'Sibling', 'Relative', 'Friend', 'Other'],
198
+ 'Social Media Handles': [], // Text input field for multiple handles
199
+ 'Version History / Updates': [] // Text area for version tracking
200
+ };
201
+
202
+ // File upload fields
203
+ fileFields = new Set<string>([
204
+ 'Photo Upload', 'Evidence Photos', 'Evidence Videos', 'Evidence Documents',
205
+ 'Evidence Files', 'Upload Evidence Files', 'Digital Evidence'
206
+ ]);
207
+
208
+ uploadedFiles: Record<string, File[]> = {};
209
+
210
+ // Section icons
211
+ sectionIcons = {
212
+ crime: 'fas fa-gavel',
213
+ suspect: 'fas fa-user-secret',
214
+ notes: 'fas fa-sticky-note'
215
+ };
216
+
217
+ sections: any = {
218
+ crime: {
219
+ title: 'Crime Details',
220
+ subgroups: {
221
+ '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'],
222
+ 'Location & People': ['Location', 'Jurisdiction / PS', 'Scene Type', 'Reported By', 'Reported Contact', 'Witness Count', 'Victim Name', 'Victim Contact', 'Victim Summary', 'Suspected Offender Known?', 'Suspect Link'],
223
+ 'Offence & Context': ['Legal Sections / Charges', 'Offence Category', 'Offence Description', 'Suspected Motive', 'Confirmed Motive', 'Weapon Involved', 'Property Loss / Damage'],
224
+ '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'],
225
+ 'Operational Notes': ['Investigating Officer', 'Duty Person', 'Supervising Officer', 'Patrol Notes', 'Arrest Made', 'Arrest Location', 'Initial Actions Taken', 'riskLevel', 'Confidentiality'],
226
+ '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'],
227
+ 'Remark': ['Remark']
228
+ }
229
+ },
230
+ suspect: {
231
+ title: 'Suspect Details',
232
+ subgroups: {
233
+ 'Identity': ['Suspect ID', 'Suspect Name', 'Alias / Nickname', 'Age', 'Gender', 'Nationality', 'Nationality ID / Passport Number', 'Languages', 'Address', 'Known Aliases', 'Government ID'],
234
+ 'Physical Description': ['Height (cm)', 'Weight (kg)', 'Build', 'Hair Color', 'Eye Color', 'Distinguishing Marks', 'Tattoo Details', 'Scar Details', 'Photo Upload'],
235
+ 'Background': ['Employment', 'Education', 'Occupation', 'Company', 'Workplace Address', 'Marital Status', 'Known Habits', 'Known Financial Details'],
236
+ 'Known Associates': ['Associate Names', 'Gang Affiliation', 'Family Connections', 'Social Media Handles'],
237
+ 'Prior Records': ['Criminal History', 'Prior Arrests', 'Probation/Parole Status'],
238
+ 'Remark': ['Remark']
239
+ }
240
+ },
241
+ notes: {
242
+ title: 'Evidence and Documents',
243
+ subgroups: {
244
+ 'Investigation Notes': ['Initial Findings', 'Detailed Notes', 'Status', 'Version History / Updates'],
245
+ 'Evidence Files': ['Evidence Photos', 'Evidence Videos', 'Evidence Documents'],
246
+ 'Links and Recommendation': ['Links to Evidence', 'Final Recommendations'],
247
+ 'Remark': ['Remark']
248
+ }
249
+ }
250
+ };
251
+
252
+ // Complete field descriptions
253
+ fieldDescriptions: Record<string, string> = {
254
+ // Crime: Identification & Timing
255
+ 'Case ID': 'Unique internal tracking identifier for this case.',
256
+ 'FIR / Ref #': 'Official First Information Report or reference number.',
257
+ 'Crime Type': 'Primary legal / investigative classification of the offence.',
258
+ 'Case Category': 'Broader grouping used for analytics and reporting.',
259
+ 'Date & Time (Entry)': 'Timestamp when the case was first registered in the system.',
260
+ 'Occurred From': 'Start of the known / suspected offence time window.',
261
+ 'Occurred To': 'End of the known / suspected offence time window.',
262
+ 'Time Reported': 'When it was first reported to authorities.',
263
+ 'Time Discovered': 'When the incident was first discovered (may differ from reported).',
264
+ 'Country': 'Country where the offence occurred.',
265
+ 'State': 'State / province of occurrence.',
266
+ 'District': 'Administrative district of occurrence (Tamil Nadu districts supported).',
267
+ 'Number of Victims': 'Total count of direct victims involved.',
268
+ 'Brief Description': 'Short narrative summary for quick reference.',
269
+
270
+ // Crime: Location & People
271
+ 'Location': 'Exact address / geo description of the scene.',
272
+ 'Jurisdiction / PS': 'Police Station or jurisdiction handling the investigation.',
273
+ 'Scene Type': 'Type of environment where the offence occurred.',
274
+ 'Reported By': 'Name of the reporting individual / entity.',
275
+ 'Reported Contact': 'Contact details for the reporting party.',
276
+ 'Witness Count': 'Number of identified witnesses so far.',
277
+ 'Victim Name': 'Primary victim name (or placeholder if protected).',
278
+ 'Victim Contact': 'Phone / email / other contact channel for victim.',
279
+ 'Victim Summary': 'Short summary of victim condition or status.',
280
+ 'Suspected Offender Known?': 'Whether victim / witnesses know the offender.',
281
+ 'Suspect Link': 'Internal reference to related suspect record.',
282
+
283
+ // Crime: Offence & Context
284
+ 'Legal Sections / Charges': 'Applicable statutory sections / penal codes.',
285
+ 'Offence Category': 'Higher level grouping (e.g., violent, cyber).',
286
+ 'Offence Description': 'Detailed narrative of what occurred.',
287
+ 'Suspected Motive': 'Preliminary perceived motive (subject to change).',
288
+ 'Confirmed Motive': 'Validated motive after evidence review.',
289
+ 'Weapon Involved': 'Weapon(s) used or suspected; choose Unknown if unclear.',
290
+ 'Property Loss / Damage': 'Summary / valuation of property loss or damage.',
291
+
292
+ // Crime: Evidence & Scene
293
+ 'Evidence Collected': 'General list of all evidentiary items gathered.',
294
+ 'Forensic Tests Required': 'Pending or requested forensic examinations.',
295
+ 'Scene Condition': 'Condition of scene upon first secure entry.',
296
+ 'Photos / Video?': 'Whether any media was captured.',
297
+ 'CCTV Present?': 'If relevant CCTV sources exist.',
298
+ 'CCTV Sources / IDs': 'Identifiers / locations for each CCTV source.',
299
+ 'Physical Evidence (list)': 'Individual tangible exhibits (bagged / tagged).',
300
+ 'Chain of Custody?': 'Status of formal evidence transfer logging.',
301
+ 'Digital Evidence': 'Electronic sources: phones, email dumps, logs, socials.',
302
+ 'Evidence Storage Reference': 'Locker / repository / digital vault reference ID.',
303
+
304
+ // Crime: Operational Notes
305
+ 'Investigating Officer': 'Lead officer responsible for case progress.',
306
+ 'Duty Person': 'Officer / staff who received the report.',
307
+ 'Supervising Officer': 'Oversight / escalation point for the case.',
308
+ 'Patrol Notes': 'First responder observations / scene notes.',
309
+ 'Arrest Made': 'Indicates whether an arrest has occurred.',
310
+ 'Arrest Location': 'Location at which arrest was executed.',
311
+ 'Initial Actions Taken': 'Immediate remedial or containment actions.',
312
+ 'riskLevel': 'Risk classification influencing priority.',
313
+ 'Confidentiality': 'Access / visibility level of case records.',
314
+
315
+ // Crime: Status & Linkage
316
+ 'Biometric / Forensic IDs': 'External forensic system identifiers (AFIS, DNA DB).',
317
+ 'DNA Ref ID': 'Laboratory DNA reference identifier.',
318
+ 'Fingerprint ID': 'Fingerprint database reference.',
319
+ 'Case Status': 'Lifecycle status (Open / Active / Closed etc.).',
320
+ 'Linked Cases': 'Related or associated case identifiers.',
321
+ 'arrestCount': 'Total arrests associated with this case.',
322
+ 'Case Priority': 'Operational prioritisation level.',
323
+ 'Follow-up Date': 'Next scheduled investigative review date.',
324
+ 'Court Case ID': 'Judicial / docket identifier once filed.',
325
+ 'Next Hearing Date': 'Date of next scheduled court proceeding.',
326
+ 'Final Summary': 'Closure narrative entered at completion.',
327
+
328
+ // Suspect: Identity
329
+ 'Suspect ID': 'Internal unique suspect identifier.',
330
+ 'Suspect Name': 'Full legal or recorded name.',
331
+ 'Alias / Nickname': 'Commonly used alternative names.',
332
+ 'Age': 'Approximate or confirmed age.',
333
+ 'Gender': 'Recorded gender descriptor.',
334
+ 'Nationality': 'Country of citizenship.',
335
+ 'Nationality ID / Passport Number': 'Official national ID / passport number.',
336
+ 'Languages': 'Languages spoken or understood by suspect.',
337
+ 'Address': 'Primary last known address.',
338
+ 'Known Aliases': 'Additional identity variations.',
339
+ 'Government ID': 'Government issued identification (license / ID card).',
340
+
341
+ // Suspect: Physical Description
342
+ 'Height (cm)': 'Height in centimetres measured or estimated.',
343
+ 'Weight (kg)': 'Weight in kilograms measured or estimated.',
344
+ 'Build': 'General body build classification.',
345
+ 'Hair Color': 'Observed or recorded hair colour.',
346
+ 'Eye Color': 'Observed or recorded eye colour.',
347
+ 'Distinguishing Marks': 'Unique visible physical markers.',
348
+ 'Tattoo Details': 'Location and description of tattoos.',
349
+ 'Scar Details': 'Location and description of scars.',
350
+ 'Photo Upload': 'Most recent or relevant facial photograph.',
351
+
352
+ // Suspect: Background
353
+ 'Employment': 'Current employment status.',
354
+ 'Education': 'Highest completed education level.',
355
+ 'Occupation': 'Primary occupation / role.',
356
+ 'Company': 'Employer / organisation name.',
357
+ 'Workplace Address': 'Physical address of workplace.',
358
+ 'Marital Status': 'Current marital / relationship status.',
359
+ 'Known Habits': 'Behavioural patterns (substances, gambling, etc.).',
360
+ 'Known Financial Details': 'Financial profile relevant to investigation.',
361
+
362
+ // Suspect: Known Associates
363
+ 'Associate Names': 'Key associate individuals linked to suspect.',
364
+ 'Gang Affiliation': 'Known gang or group membership.',
365
+ 'Family Connections': 'Notable family relational links.',
366
+ 'Social Media Handles': 'Identifiers used on social platforms.',
367
+
368
+ // Suspect: Prior Records
369
+ 'Criminal History': 'Summary of prior criminal involvement.',
370
+ 'Prior Arrests': 'Number / list of previous arrests.',
371
+ 'Probation/Parole Status': 'Current supervision / release status.',
372
+
373
+ // Notes: Investigation Notes
374
+ 'Initial Findings': 'Early observations at investigation start.',
375
+ 'Detailed Notes': 'Progressive narrative & analytical details.',
376
+ 'Status': 'Progress state category for notes.',
377
+ 'Version History / Updates': 'Chronological changes & authorship log.',
378
+
379
+ // Notes: Evidence Files
380
+ 'Evidence Photos': 'Photographic evidence references.',
381
+ 'Evidence Videos': 'Video evidence references.',
382
+ 'Evidence Documents': 'Document / PDF evidence references.',
383
+
384
+ // Notes: Links and Recommendation
385
+ 'Links to Evidence': 'External or internal reference links to sources.',
386
+ 'Final Recommendations': 'Closing recommendations / actions summary.'
387
+ };
388
+
389
+ subgroupIcons: any = {
390
+ 'Identification & Timing': 'fas fa-clock',
391
+ 'Location & People': 'fas fa-map-marker-alt',
392
+ 'Offence & Context': 'fas fa-gavel',
393
+ 'Evidence & Scene': 'fas fa-search',
394
+ 'Operational Notes': 'fas fa-clipboard',
395
+ 'Status & Linkage': 'fas fa-link',
396
+ 'Identity': 'fas fa-id-card',
397
+ 'Physical Description': 'fas fa-user',
398
+ 'Background': 'fas fa-user-graduate',
399
+ 'Known Associates': 'fas fa-users',
400
+ 'Prior Records': 'fas fa-file-alt',
401
+ 'Investigation Notes': 'fas fa-sticky-note',
402
+ 'Evidence Files': 'fas fa-folder',
403
+ 'Links and Recommendation': 'fas fa-link',
404
+ 'Recommendations': 'fas fa-thumbs-up'
405
+ };
406
+
407
  ngOnInit(): void {
408
+ // Set up autosave
409
+ this.autoSave$.pipe(
410
+ debounceTime(2000),
411
+ takeUntil(this.destroy$)
412
+ ).subscribe(() => {
413
+ this.performAutoSave();
414
+ });
415
+
416
+ // Load saved form data
417
+ this.loadFormData();
418
+
419
+ // Load field selections
420
+ this.loadFieldSelections();
421
+ }
422
+
423
+ ngAfterViewInit(): void {
424
+ // No special scroll handling needed anymore
425
+ }
426
+
427
+ ngOnDestroy(): void {
428
+ this.destroy$.next();
429
+ this.destroy$.complete();
430
+ }
431
+
432
+ // Progress Calculation
433
+ get progressPercentage(): number {
434
+ const totalFields = this.getAllFields().length;
435
+ const completedFields = this.completedFields.size;
436
+ return totalFields > 0 ? Math.round((completedFields / totalFields) * 100) : 0;
437
+ }
438
+
439
+ private getAllFields(): string[] {
440
+ let allFields: string[] = [];
441
+ for (const section of this.sectionKeys) {
442
+ for (const subgroup of Object.keys(this.sections[section].subgroups)) {
443
+ allFields = allFields.concat(this.sections[section].subgroups[subgroup]);
444
+ }
445
+ }
446
+ return allFields;
447
+ }
448
+
449
+ // Helper method to check if we're in Identification & Timing
450
+ private isIdentificationAndTimingPage(): boolean {
451
+ return this.currentSubgroup === 'Identification & Timing';
452
+ }
453
+
454
+ // Helper method to check if we're in Location & People
455
+ private isLocationAndPeoplePage(): boolean {
456
+ return this.currentSubgroup === 'Location & People';
457
+ }
458
+
459
+ // Helper method to check if we need compact layout (applies to all pages now)
460
+ private needsCompactLayout(): boolean {
461
+ return true; // Apply compact layout to all pages to prevent main page scroll
462
+ }
463
+
464
+ // Card Management - Updated for single card layout across ALL pages
465
+ showSecondaryCard(): boolean {
466
+ return false; // No secondary card for any page - single card layout for all
467
+ }
468
+
469
+ showTertiaryCard(): boolean {
470
+ return false; // No tertiary card for any page - single card layout for all
471
+ }
472
+
473
+ getPrimaryFields(): string[] {
474
+ // Return selected fields for display instead of all fields
475
+ return this.getSelectedFieldsForDisplay();
476
+ }
477
+
478
+ getSecondaryFields(): string[] {
479
+ // Return empty array for single card layout across all pages
480
+ return [];
481
+ }
482
+
483
+ getTertiaryFields(): string[] {
484
+ // Return empty array for single card layout across all pages
485
+ return [];
486
+ }
487
+
488
+ private getCurrentFields(): string[] {
489
+ return this.sections[this.currentSection].subgroups[this.currentSubgroup] || [];
490
+ }
491
+
492
+ toggleCardMinimize(card: 'primary' | 'secondary' | 'tertiary'): void {
493
+ this.isCardMinimized[card] = !this.isCardMinimized[card];
494
+ }
495
+
496
+ // Field Management
497
+ isFieldRequired(field: string): boolean {
498
+ return this.requiredFields.has(field);
499
+ }
500
+
501
+ isCompactField(field: string): boolean {
502
+ return this.compactFields.has(field);
503
+ }
504
+
505
+ getInputType(field: string): string {
506
+ if (this.numericFields.has(field)) return 'number';
507
+ if (this.dateTimeFields.has(field)) return 'datetime-local';
508
+ if (this.dateFields.has(field)) return 'date';
509
+ if (field.toLowerCase().includes('email')) return 'email';
510
+ if (field.toLowerCase().includes('phone') || field.toLowerCase().includes('contact')) return 'tel';
511
+ if (field.toLowerCase().includes('url') || field.toLowerCase().includes('link')) return 'url';
512
+ if (field.toLowerCase().includes('description')) return 'textarea';
513
+ return 'text';
514
+ }
515
+
516
+ getFieldPlaceholder(field: string): string {
517
+ if (field === 'Age') return 'Enter age (18-99)';
518
+ if (field === 'Height (cm)') return 'Height in cm';
519
+ if (field === 'Weight (kg)') return 'Weight in kg';
520
+ if (this.dateTimeFields.has(field)) return 'dd-mm-yyyy --:--';
521
+ if (this.dateFields.has(field)) return 'dd-mm-yyyy';
522
+ if (field.toLowerCase().includes('email')) return 'Enter email address';
523
+ if (field.toLowerCase().includes('phone')) return 'Enter phone number';
524
+ if (field.toLowerCase().includes('description')) return 'Enter detailed description...';
525
+ return `Enter ${field.toLowerCase()}`;
526
+ }
527
+
528
+ getMaxLength(field: string): number {
529
+ if (field === 'Age') return 2;
530
+ if (field === 'Gender') return 10;
531
+ if (field === 'Height (cm)') return 3;
532
+ if (field === 'Weight (kg)') return 3;
533
+ if (this.compactFields.has(field)) return 20;
534
+ return 500;
535
+ }
536
+
537
+ // Validation
538
+ validateField(field: string): void {
539
+ const value = this.formData[field];
540
+ let hasError = false;
541
+ let isValid = false;
542
+ let message = '';
543
+
544
+ if (this.isFieldRequired(field) && (!value || value.toString().trim() === '')) {
545
+ hasError = true;
546
+ message = `${field} is required`;
547
+ } else if (value && value.toString().trim() !== '') {
548
+ // Field-specific validation
549
+ if (field === 'Age') {
550
+ const age = parseInt(value);
551
+ if (isNaN(age) || age < 1 || age > 120) {
552
+ hasError = true;
553
+ message = 'Age must be a valid number between 1 and 120';
554
+ } else {
555
+ isValid = true;
556
+ }
557
+ } else if (field === 'Email') {
558
+ // Basic email pattern; improve with regex if needed
559
+ const emailPattern = /\S+@\S+\.\S+/;
560
+ isValid = emailPattern.test(value);
561
+ if (!isValid) {
562
+ hasError = true;
563
+ message = 'Invalid email address format';
564
+ }
565
+ } else {
566
+ // Generic validation for other fields (extend as needed)
567
+ isValid = true;
568
+ }
569
  }
570
+
571
+ // Update field validation state
572
+ this.fieldValidation[field] = { hasError, isValid, message };
573
+
574
+ // Update overall form completion status
575
+ this.updateCompletionStatus();
576
+ }
577
+
578
+ private updateCompletionStatus(): void {
579
+ this.completedFields.clear();
580
+
581
+ for (const field of Object.keys(this.formData)) {
582
+ if (this.formData[field] !== null && this.formData[field] !== undefined && this.formData[field] !== '') {
583
+ this.completedFields.add(field);
584
  }
585
  }
586
+
587
+ // Update completed subgroups and sections
588
+ this.updateCompletedGroupsAndSections();
589
  }
590
 
591
+ private updateCompletedGroupsAndSections(): void {
592
+ this.completedSubgroups.clear();
593
+ this.completedSections.clear();
594
+
595
+ for (const section of this.sectionKeys) {
596
+ const subgroups = Object.keys(this.sections[section].subgroups);
597
+ for (const subgroup of subgroups) {
598
+ const fields = this.sections[section].subgroups[subgroup];
599
+ const allFieldsCompleted = fields.every((field: string) => this.completedFields.has(field));
600
+
601
+ if (allFieldsCompleted) {
602
+ this.completedSubgroups.add(subgroup);
603
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
604
  }
605
+
606
+ if (this.completedSubgroups.size === subgroups.length) {
607
+ this.completedSections.add(section);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
608
  }
609
+ }
610
+ }
611
+
612
+ // Field selection functionality - Updated to allow all fields by default
613
+ selectedFields: Record<string, string[]> = {}; // Store selected fields per subgroup
614
+ showFieldSelector: string | null = null; // Track which field selector is open
615
+ readonly maxSelectableFields = 50; // Increased limit to allow more fields
616
+
617
+ // Get all available fields for current subgroup
618
+ getAvailableFields(): string[] {
619
+ return this.getCurrentFields();
620
+ }
621
+
622
+ // Get total available fields count dynamically
623
+ getTotalAvailableFieldsCount(): number {
624
+ return this.getAvailableFields().length;
625
+ }
626
+
627
+ // Get currently selected fields for display (default to ALL fields if none selected)
628
+ getSelectedFieldsForDisplay(): string[] {
629
+ const subgroupKey = `${this.currentSection}-${this.currentSubgroup}`;
630
+ if (this.selectedFields[subgroupKey] && this.selectedFields[subgroupKey].length >= 0) {
631
+ return this.selectedFields[subgroupKey];
632
+ }
633
+ // Default to ALL fields if no selection made
634
+ return this.getCurrentFields();
635
+ }
636
+
637
+ // Toggle field selection with enhanced debugging
638
+ toggleFieldSelection(field: string, event?: Event): void {
639
+ if (event) {
640
+ event.preventDefault();
641
+ event.stopPropagation();
642
+ }
643
+
644
+ console.log('Toggling field selection for:', field);
645
+
646
+ const subgroupKey = `${this.currentSection}-${this.currentSubgroup}`;
647
+ if (!this.selectedFields[subgroupKey]) {
648
+ // Initialize with all fields selected by default
649
+ this.selectedFields[subgroupKey] = [...this.getCurrentFields()];
650
+ }
651
+
652
+ const currentSelection = this.selectedFields[subgroupKey];
653
+ const fieldIndex = currentSelection.indexOf(field);
654
+
655
+ console.log('Current selection:', currentSelection);
656
+ console.log('Field index:', fieldIndex);
657
+
658
+ if (fieldIndex > -1) {
659
+ // Remove field if already selected
660
+ currentSelection.splice(fieldIndex, 1);
661
+ console.log('Removed field:', field);
662
+ } else {
663
+ // Add field if not selected and under limit
664
+ if (currentSelection.length < this.maxSelectableFields) {
665
+ currentSelection.push(field);
666
+ console.log('Added field:', field);
667
+ } else {
668
+ console.log('Selection limit reached, cannot add:', field);
669
+ }
670
+ }
671
+
672
+ // Trigger change detection
673
+ this.selectedFields = { ...this.selectedFields };
674
+ this.saveFieldSelections();
675
+ // Do NOT close the popup here; just update selection and save
676
+ }
677
+
678
+ // Check if field is currently selected
679
+ isFieldSelected(field: string): boolean {
680
+ const subgroupKey = `${this.currentSection}-${this.currentSubgroup}`;
681
+ const selections = this.selectedFields[subgroupKey];
682
+ if (!selections) {
683
+ // If no selections made, all fields are selected by default
684
+ return true;
685
+ }
686
+ return selections.includes(field);
687
+ }
688
+
689
+ // Get count of selected fields
690
+ getSelectedFieldCount(): number {
691
+ const subgroupKey = `${this.currentSection}-${this.currentSubgroup}`;
692
+ const selections = this.selectedFields[subgroupKey];
693
+ if (!selections) {
694
+ return this.getCurrentFields().length; // All fields selected by default
695
+ }
696
+ return selections.length;
697
+ }
698
+
699
+ // Check if selection limit is reached
700
+ isSelectionLimitReached(): boolean {
701
+ return this.getSelectedFieldCount() >= this.maxSelectableFields;
702
+ }
703
+
704
+ // Reset field selection for current subgroup (clear all)
705
+ resetFieldSelection(event?: Event): void {
706
+ if (event) {
707
+ event.preventDefault();
708
+ event.stopPropagation();
709
+ }
710
+
711
+ const subgroupKey = `${this.currentSection}-${this.currentSubgroup}`;
712
+ this.selectedFields[subgroupKey] = [];
713
+ this.selectedFields = { ...this.selectedFields };
714
+ this.saveFieldSelections();
715
+ // Do NOT close the popup here; keep it open for further selection
716
+ }
717
+
718
+ // Select all fields
719
+ selectAllFields(event?: Event): void {
720
+ if (event) {
721
+ event.preventDefault();
722
+ event.stopPropagation();
723
+ }
724
+
725
+ const subgroupKey = `${this.currentSection}-${this.currentSubgroup}`;
726
+ const allFields = this.getCurrentFields();
727
+ this.selectedFields[subgroupKey] = [...allFields];
728
+ this.selectedFields = { ...this.selectedFields };
729
+ this.saveFieldSelections();
730
+ }
731
+
732
+ // Select default fields (first 10 or all if less than 10) - Renamed for clarity
733
+ selectDefaultFields(event?: Event): void {
734
+ if (event) {
735
+ event.preventDefault();
736
+ event.stopPropagation();
737
+ }
738
+
739
+ const subgroupKey = `${this.currentSection}-${this.currentSubgroup}`;
740
+ const allFields = this.getCurrentFields();
741
+ this.selectedFields[subgroupKey] = allFields.slice(0, Math.min(10, allFields.length));
742
+ this.selectedFields = { ...this.selectedFields };
743
+ this.saveFieldSelections();
744
+ }
745
+
746
+ // Get dynamic max selectable based on available fields
747
+ getDynamicMaxSelectable(): number {
748
+ const totalFields = this.getTotalAvailableFieldsCount();
749
+ return Math.min(this.maxSelectableFields, totalFields);
750
+ }
751
+
752
+ // Check if all fields are selected
753
+ areAllFieldsSelected(): boolean {
754
+ const subgroupKey = `${this.currentSection}-${this.currentSubgroup}`;
755
+ const selections = this.selectedFields[subgroupKey];
756
+ const totalFields = this.getCurrentFields().length;
757
+
758
+ if (!selections) {
759
+ return true; // All fields selected by default
760
+ }
761
+
762
+ return selections.length === totalFields;
763
+ }
764
+
765
+ // Check if no fields are selected
766
+ areNoFieldsSelected(): boolean {
767
+ const subgroupKey = `${this.currentSection}-${this.currentSubgroup}`;
768
+ const selections = this.selectedFields[subgroupKey];
769
+
770
+ if (!selections) {
771
+ return false; // All fields selected by default
772
+ }
773
+
774
+ return selections.length === 0;
775
+ }
776
+
777
+ // Navigation and section management methods
778
+ getSubgroups(): string[] {
779
+ return Object.keys(this.sections[this.currentSection].subgroups);
780
+ }
781
+
782
+ showSection(section: 'crime' | 'suspect' | 'notes'): void {
783
+ // Close field selector when changing sections
784
+ this.closeFieldSelector();
785
+ this.currentSection = section;
786
+ this.currentSubgroup = Object.keys(this.sections[this.currentSection].subgroups)[0];
787
+ this.showHelpFor = null;
788
+ this.triggerAutoSave();
789
+ }
790
+
791
+ // Enhanced document click handler
792
+ @HostListener('document:click', ['$event'])
793
+ handleDoc(event: Event): void {
794
+ this.showHelpFor = null;
795
+ // Only close field selector if click is outside the popup
796
+ const target = event.target as HTMLElement;
797
+ if (this.showFieldSelector && !target.closest('.field-selector-container')) {
798
+ this.closeFieldSelector();
799
+ }
800
+ }
801
+
802
+ // Handle section/subgroup changes to refresh field selector
803
+ setSubgroup(key: string): void {
804
+ // Close field selector when changing subgroups
805
+ this.closeFieldSelector();
806
+ this.currentSubgroup = key;
807
+ this.showHelpFor = null;
808
+ this.triggerAutoSave();
809
+ }
810
+
811
+ // Section and subgroup completion status
812
+ isSectionCompleted(section: string): boolean {
813
+ return this.completedSections.has(section);
814
+ }
815
+
816
+ isSubgroupCompleted(subgroup: string): boolean {
817
+ return this.completedSubgroups.has(subgroup);
818
+ }
819
+
820
+ // Section descriptions
821
+ getSectionDescription(section: string): string {
822
+ const descriptions = {
823
+ crime: 'Capture complete crime intelligence: timing, location, evidence, and operational context. Use dropdown values for consistency and upload supporting materials.',
824
+ suspect: 'Document comprehensive suspect profile: identity, physical characteristics, background, associations, and criminal history. Include recent photographs where available.',
825
+ notes: 'Maintain detailed investigative records: findings, evidence files, reference materials, and final recommendations with proper version control.'
826
+ };
827
+ return descriptions[section as keyof typeof descriptions] || '';
828
+ }
829
+
830
+ // Field handling and validation
831
+ onFieldChange(field: string): void {
832
+ this.validateField(field);
833
+ this.triggerAutoSave();
834
+ }
835
+
836
+ // Auto-save functionality
837
+ private triggerAutoSave(): void {
838
+ this.autoSave$.next();
839
+ }
840
+
841
+ private performAutoSave(): void {
842
+ this.isAutoSaving = true;
843
+ this.autoSaveStatus = 'Saving...';
844
+
845
+ // Save to localStorage
846
+ this.saveFormData();
847
+
848
+ // Simulate save delay
849
+ setTimeout(() => {
850
+ this.isAutoSaving = false;
851
+ this.autoSaveStatus = 'Saved';
852
+
853
+ // Reset status after a moment
854
+ setTimeout(() => {
855
+ this.autoSaveStatus = 'Auto-save enabled';
856
+ }, 2000);
857
+ }, 500);
858
+ }
859
+
860
+ private saveFormData(): void {
861
+ const saveData = {
862
+ formData: this.formData,
863
+ completedFields: Array.from(this.completedFields),
864
+ completedSubgroups: Array.from(this.completedSubgroups),
865
+ completedSections: Array.from(this.completedSections),
866
+ currentSection: this.currentSection,
867
+ currentSubgroup: this.currentSubgroup
868
  };
869
+ localStorage.setItem('pydetect-form-data', JSON.stringify(saveData));
870
+ }
871
+
872
+ private loadFormData(): void {
873
+ const savedData = localStorage.getItem('pydetect-form-data');
874
+ if (savedData) {
875
+ const data = JSON.parse(savedData);
876
+ this.formData = data.formData || {};
877
+ this.completedFields = new Set(data.completedFields || []);
878
+ this.completedSubgroups = new Set(data.completedSubgroups || []);
879
+ this.completedSections = new Set(data.completedSections || []);
880
+ this.currentSection = data.currentSection || 'crime';
881
+ this.currentSubgroup = data.currentSubgroup || 'Identification & Timing';
882
+ }
883
+ }
884
+
885
+ // Dropdown options and cascading logic
886
+ getOptions(field: string): string[] | undefined {
887
+ if (field === 'Country') return this.countries;
888
+ if (field === 'State') return (this.selectedValues['Country'] === 'India' || !this.selectedValues['Country']) ? this.indiaStates : [];
889
+ if (field === 'District') return this.selectedValues['State'] === 'Tamil Nadu' ? this.tamilNaduDistricts : [];
890
+ return this.selectOptions[field];
891
+ }
892
+
893
+ onSelectChange(field: string, event: Event): void {
894
+ const value = (event.target as HTMLSelectElement).value;
895
+ this.selectedValues[field] = value;
896
+ this.formData[field] = value;
897
+
898
+ // Clear dependent fields
899
+ if (field === 'Country') {
900
+ delete this.selectedValues['State'];
901
+ delete this.selectedValues['District'];
902
+ delete this.formData['State'];
903
+ delete this.formData['District'];
904
+ }
905
+ if (field === 'State') {
906
+ delete this.selectedValues['District'];
907
+ delete this.formData['District'];
908
+ }
909
+
910
+ this.validateField(field);
911
+ this.triggerAutoSave();
912
+ }
913
+
914
+ // Field help functionality
915
+ toggleFieldInfo(field: string, ev: MouseEvent): void {
916
+ ev.stopPropagation();
917
+ this.showHelpFor = this.showHelpFor === field ? null : field;
918
  }
919
 
920
+ closeFieldInfo(): void {
921
+ this.showHelpFor = null;
922
+ }
923
+
924
+ // File upload functionality
925
+ onFileChange(field: string, event: Event): void {
926
+ const input = event.target as HTMLInputElement;
927
+ const files = input.files ? Array.from(input.files) : [];
928
+ if (files.length) {
929
+ this.uploadedFiles[field] = (this.uploadedFiles[field] || []).concat(files);
930
+ this.validateField(field);
931
+ this.triggerAutoSave();
932
+ }
933
+ }
934
+
935
+ onDragOver(event: DragEvent): void {
936
+ event.preventDefault();
937
+ this.isDragOver = true;
938
+ }
939
+
940
+ onDragLeave(event: DragEvent): void {
941
+ event.preventDefault();
942
+ this.isDragOver = false;
943
+ }
944
+
945
+ onFileDrop(field: string, event: DragEvent): void {
946
+ event.preventDefault();
947
+ this.isDragOver = false;
948
+
949
+ const files = event.dataTransfer?.files ? Array.from(event.dataTransfer.files) : [];
950
+ if (files.length) {
951
+ this.uploadedFiles[field] = (this.uploadedFiles[field] || []).concat(files);
952
+ this.validateField(field);
953
+ this.triggerAutoSave();
954
+ }
955
+ }
956
+
957
+ removeFile(field: string, file: File): void {
958
+ if (this.uploadedFiles[field]) {
959
+ this.uploadedFiles[field] = this.uploadedFiles[field].filter(f => f !== file);
960
+ this.triggerAutoSave();
961
+ }
962
+ }
963
+
964
+ getAcceptedFileTypes(field: string): string {
965
+ return this.fileTypeConfig[field] || '*';
966
+ }
967
+
968
+ getFileIcon(filename: string): string {
969
+ const ext = filename.split('.').pop()?.toLowerCase();
970
+ switch (ext) {
971
+ case 'pdf': return 'fas fa-file-pdf';
972
+ case 'doc':
973
+ case 'docx': return 'fas fa-file-word';
974
+ case 'jpg':
975
+ case 'jpeg':
976
+ case 'png':
977
+ case 'gif': return 'fas fa-file-image';
978
+ case 'mp4':
979
+ case 'avi':
980
+ case 'mov': return 'fas fa-file-video';
981
+ default: return 'fas fa-file';
982
+ }
983
+ }
984
+
985
+ // Navigation helper methods for floating buttons
986
+ getPreviousSubgroup(): string {
987
+ const list = this.getSubgroups();
988
+ const currentIndex = list.indexOf(this.currentSubgroup);
989
+ return currentIndex > 0 ? list[currentIndex - 1] : '';
990
+ }
991
+
992
+ getNextSubgroup(): string {
993
+ const list = this.getSubgroups();
994
+ const currentIndex = list.indexOf(this.currentSubgroup);
995
+ return currentIndex < list.length - 1 ? list[currentIndex + 1] : '';
996
+ }
997
+
998
+ isLastSubgroup(): boolean {
999
+ const list = this.getSubgroups();
1000
+ return list.indexOf(this.currentSubgroup) === list.length - 1;
1001
+ }
1002
+
1003
+ isFirstSubgroup(): boolean {
1004
+ return this.getSubgroups().indexOf(this.currentSubgroup) === 0;
1005
+ }
1006
+
1007
+ canPrevSubgroup(): boolean {
1008
+ return !this.isFirstSubgroup();
1009
+ }
1010
+
1011
+ canNextSubgroup(): boolean {
1012
+ return !this.isLastSubgroup();
1013
+ }
1014
+
1015
+ prevSubgroup(): void {
1016
+ if (!this.canPrevSubgroup()) return;
1017
+ const list = this.getSubgroups();
1018
+ const i = list.indexOf(this.currentSubgroup);
1019
+ this.setSubgroup(list[i - 1]);
1020
+ }
1021
+
1022
+ nextSubgroup(): void {
1023
+ if (!this.canNextSubgroup()) return;
1024
+ const list = this.getSubgroups();
1025
+ const i = list.indexOf(this.currentSubgroup);
1026
+ this.setSubgroup(list[i + 1]);
1027
+ }
1028
+
1029
+ submitCurrentSection(): void {
1030
+ // Perform final validation
1031
+ const currentFields = this.getCurrentFields();
1032
+ const requiredFields = currentFields.filter(f => this.isFieldRequired(f));
1033
+ const missingFields = requiredFields.filter(f => !this.completedFields.has(f));
1034
+
1035
+ if (missingFields.length > 0) {
1036
+ alert(`Please complete the following required fields: ${missingFields.join(', ')}`);
1037
  return;
1038
  }
1039
 
1040
+ this.performAutoSave();
1041
+
1042
+ // Map flat formData to nested structure expected by addFromInfoForm
1043
+ const crime = {
1044
+ caseId: this.formData['Case ID'] || '',
1045
+ dateTime: this.formData['Date & Time (Entry)'] || '',
1046
+ crimeType: this.formData['Crime Type'] || '',
1047
+ location: this.formData['Location'] || '',
1048
+ victimName: this.formData['Victim Name'] || '',
1049
+ caseCategory: this.formData['Case Category'] || '',
1050
+ reportedBy: this.formData['Reported By'] || '',
1051
+ 'FIR / Ref #': this.formData['FIR / Ref #'] || '',
1052
+ 'Occurred From': this.formData['Occurred From'] || '',
1053
+ 'Occurred To': this.formData['Occurred To'] || '',
1054
+ 'Jurisdiction / PS': this.formData['Jurisdiction / PS'] || '',
1055
+ 'Scene Type': this.formData['Scene Type'] || ''
1056
+ };
1057
+ const suspect = {
1058
+ fullName: this.formData['Suspect Name'] || '',
1059
+ age: this.formData['Age'] || '',
1060
+ gender: this.formData['Gender'] || '',
1061
+ address: this.formData['Address'] || '',
1062
+ alias: this.formData['Alias / Nickname'] || ''
1063
+ };
1064
+ const notes = {
1065
+ status: this.formData['Case Status'] || this.formData['Status'] || 'Open',
1066
+ officerInCharge: this.formData['Investigating Officer'] || '',
1067
+ initialFindings: this.formData['Initial Findings'] || ''
1068
+ };
1069
+ const legal = {
1070
+ witnessStatements: this.formData['Witness Statements'] || '',
1071
+ confessions: this.formData['Confessions'] || '',
1072
+ evidence: this.uploadedFiles['Evidence Files'] || []
1073
+ };
1074
+
1075
+ this.caseStore.addFromInfoForm({ crime, suspect, notes, legal });
1076
+ // Show popup first
1077
+ this.showSubmitPopup = true;
1078
+ }
1079
+
1080
+ onSubmitPopupClose(): void {
1081
+ this.showSubmitPopup = false;
1082
+ this.router.navigate(['/record'], { state: { formData: this.formData } });
1083
+ }
1084
+
1085
+ // Keyboard navigation
1086
+ @HostListener('document:keydown', ['$event'])
1087
+ handleKeydown(event: KeyboardEvent): void {
1088
+ // Keyboard navigation
1089
+ if (event.ctrlKey && event.key === 'ArrowRight') {
1090
+ event.preventDefault();
1091
+ this.nextSubgroup();
1092
+ } else if (event.ctrlKey && event.key === 'ArrowLeft') {
1093
+ event.preventDefault();
1094
+ this.prevSubgroup();
1095
+ } else if (event.ctrlKey && event.key === 's') {
1096
+ event.preventDefault();
1097
+ this.performAutoSave();
1098
+ } else if (event.key === 'Escape') {
1099
+ this.closeFieldInfo();
1100
+ }
1101
+ }
1102
+
1103
+ // Toggle field selector visibility
1104
+ toggleFieldSelector(event?: Event): void {
1105
+ if (event) {
1106
+ event.preventDefault();
1107
+ event.stopPropagation();
1108
+ }
1109
+
1110
+ const subgroupKey = `${this.currentSection}-${this.currentSubgroup}`;
1111
+ this.showFieldSelector = this.showFieldSelector === subgroupKey ? null : subgroupKey;
1112
+ }
1113
+
1114
+ // Close field selector
1115
+ closeFieldSelector(): void {
1116
+ this.showFieldSelector = null;
1117
+ }
1118
+
1119
+ // Save field selections to localStorage
1120
+ private saveFieldSelections(): void {
1121
+ try {
1122
+ localStorage.setItem('pydetect-field-selections', JSON.stringify(this.selectedFields));
1123
+ } catch (error) {
1124
+ console.warn('Could not save field selections to localStorage:', error);
1125
  }
1126
+ }
1127
 
1128
+ // Load field selections from localStorage
1129
+ private loadFieldSelections(): void {
1130
+ try {
1131
+ const savedSelections = localStorage.getItem('pydetect-field-selections');
1132
+ if (savedSelections) {
1133
+ this.selectedFields = JSON.parse(savedSelections);
1134
+ }
1135
+ } catch (error) {
1136
+ console.warn('Could not load field selections from localStorage:', error);
1137
+ this.selectedFields = {};
1138
+ }
1139
  }
1140
 
1141
+ // Track by function for ngFor optimization
1142
+ trackByField(index: number, field: string): string {
1143
+ return field;
1144
  }
1145
  }
src/app/recordpage/recordpage.component.css CHANGED
@@ -38,16 +38,22 @@
38
  /* Add gap between logo and browser border */
39
  .logo-img {
40
  width: 6vw;
41
- height: 6vw;
42
- border-radius: 50%;
43
- box-shadow: 0 0 15px rgba(255, 255, 255, 0.8);
44
  position: fixed;
45
- top: 18px; /* Add gap from top */
46
- left: 18px; /* Add gap from left */
47
  z-index: 300;
48
  margin: 0;
 
 
 
 
 
 
 
 
49
  }
50
 
 
51
  .logo-title-row {
52
  display: flex;
53
  flex-direction: row;
@@ -63,10 +69,10 @@
63
 
64
  .py-detect-title {
65
  position: fixed;
66
- margin-left: 132px;
67
- margin-top: -208px;
68
  text-align: left;
69
- font-size: 4vw;
70
  color: #38bdf8;
71
  font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
72
  font-weight: 900;
@@ -97,35 +103,48 @@
97
  border: 2px solid #23272b;
98
  }
99
 
100
- .py-detect-title .py-letter.d {
101
- color: #e3f6ff;
102
- text-shadow: 0 0 6px #38bdf8;
103
- }
 
 
 
 
 
 
 
104
 
105
- .py-detect-title .py-letter.e {
106
- color: #38bdf8;
107
- text-shadow: 0 0 6px #38bdf8;
108
- }
109
 
110
- .py-detect-title .py-letter.t {
111
- color: #e3f6ff;
112
- text-shadow: 0 0 6px #38bdf8;
113
- }
114
 
115
- .py-detect-title .py-letter.e2 {
116
- color: #38bdf8;
117
- text-shadow: 0 0 6px #38bdf8;
118
- }
119
 
120
- .py-detect-title .py-letter.c {
121
- color: #e3f6ff;
122
- text-shadow: 0 0 6px #38bdf8;
123
- }
 
 
 
 
 
 
 
 
 
 
124
 
125
- .py-detect-title .py-letter.t2 {
126
- color: #38bdf8;
127
- text-shadow: 0 0 6px #38bdf8;
128
- }
129
 
130
  .py-shape {
131
  display: inline-block;
@@ -201,11 +220,32 @@
201
  margin: 0 auto;
202
  }
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  @media (max-width: 1800px) {
205
  .records {
206
  max-width: 100vw;
207
  min-width: 1000px;
208
  }
 
209
  .table-wrap {
210
  max-width: 100vw;
211
  min-height: 400px;
@@ -218,13 +258,16 @@
218
  width: 100%;
219
  max-width: 100vw;
220
  }
 
221
  .table-wrap {
222
  min-height: 200px;
223
  }
 
224
  .padded-table-wrap {
225
  padding-left: 4px;
226
  padding-right: 4px;
227
  }
 
228
  .records-header-row {
229
  flex-direction: column;
230
  align-items: stretch;
@@ -232,10 +275,12 @@
232
  padding-left: 4px;
233
  padding-right: 4px;
234
  }
 
235
  .page-title {
236
  text-align: center;
237
  margin-right: 0;
238
  }
 
239
  .toolbar {
240
  position: static;
241
  transform: none;
@@ -250,6 +295,21 @@
250
  border-bottom: 1px solid #edf2f7;
251
  text-align: left;
252
  white-space: nowrap;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  }
254
 
255
  .records thead th {
@@ -270,6 +330,11 @@
270
 
271
  .records th.actions-col, .records td.actions {
272
  color: #fff;
 
 
 
 
 
273
  }
274
 
275
  .records tbody tr:last-child td {
@@ -341,6 +406,34 @@
341
  color: #991b1b;
342
  }
343
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  .mono {
345
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
346
  }
@@ -377,6 +470,86 @@
377
  border-color: #805ad5;
378
  }
379
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  .empty {
381
  text-align: center;
382
  color: #718096;
@@ -392,47 +565,104 @@
392
 
393
  .modal {
394
  position: fixed;
395
- top: 50%;
396
- left: 50%;
397
- transform: translate(-50%,-50%);
398
- width: min(720px, 94vw);
 
 
 
 
399
  background: #fff;
400
- border-radius: 12px;
401
- box-shadow: 0 20px 60px rgba(0,0,0,.2);
402
- overflow: hidden;
 
 
 
 
 
 
403
  }
404
 
405
  .modal-header, .modal-footer {
406
- padding: 12px 16px;
407
- background: #f7fafc;
 
 
 
408
  }
409
 
410
  .modal-body {
411
- padding: 16px;
 
 
 
 
 
412
  }
413
 
414
-
415
-
416
  .detail-row {
417
  display: grid;
418
- grid-template-columns: 160px 1fr;
419
- gap: 8px;
420
- padding: 6px 0;
 
421
  }
422
 
 
 
 
 
423
  .detail-row span {
424
- color: #4a5568;
 
 
425
  }
426
 
427
- .detail-block .label {
428
- font-weight: 700;
429
- margin-bottom: 6px;
 
 
 
 
 
 
430
  }
431
 
 
 
 
 
 
 
 
432
  .explain {
433
  white-space: pre-wrap;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  }
435
 
 
 
 
 
436
  .records-center {
437
  display: flex;
438
  flex-direction: column;
@@ -530,11 +760,11 @@
530
  z-index: 10;
531
  }
532
 
533
- .modern-searchbar-container.compact {
534
- width: auto;
535
- margin: 0 0 0 24px;
536
- align-items: center;
537
- }
538
 
539
  .modern-searchbar-form {
540
  display: flex;
@@ -548,12 +778,12 @@
548
  position: relative;
549
  }
550
 
551
- .modern-searchbar-form.compact {
552
- height: 44px;
553
- min-width: 260px;
554
- width: 320px;
555
- padding: 0 8px 0 10px;
556
- }
557
 
558
  .modern-searchbar-icon {
559
  display: flex;
@@ -574,11 +804,11 @@
574
  height: 32px;
575
  }
576
 
577
- .modern-searchbar-input::placeholder {
578
- color: #e0e7ef;
579
- opacity: 1;
580
- font-weight: 400;
581
- }
582
 
583
  .modern-searchbar-btn {
584
  background: #fff;
@@ -595,10 +825,10 @@
595
  transition: background 0.18s, color 0.18s;
596
  }
597
 
598
- .modern-searchbar-btn:hover {
599
- background: #ede9fe;
600
- color: #4f46e5;
601
- }
602
 
603
  .modern-searchbar-form.white-bg {
604
  background: #fff !important;
@@ -610,10 +840,10 @@
610
  color: #23272b !important;
611
  }
612
 
613
- .modern-searchbar-input.white-bg::placeholder {
614
- color: #64748b !important;
615
- opacity: 1;
616
- }
617
 
618
  .modern-searchbar-icon svg {
619
  stroke: #64748b !important;
@@ -634,21 +864,35 @@
634
  transition: background 0.18s, color 0.18s;
635
  }
636
 
637
- .modern-searchbar-btn:hover {
638
- background: #38bdf8;
639
- color: #fff;
640
- }
641
 
 
 
 
 
 
 
 
642
  /* Search bar at top right corner */
643
  .searchbar-topright {
644
  position: fixed;
645
- top: 54px;
646
  right: 48px;
647
  z-index: 1001;
648
  display: flex;
649
  align-items: center;
650
  }
651
 
 
 
 
 
 
 
 
652
  @media (max-width: 900px) {
653
  .searchbar-topright {
654
  top: 12px;
@@ -656,10 +900,559 @@
656
  width: 95vw;
657
  justify-content: flex-end;
658
  }
 
659
  .modern-searchbar-form.compact {
660
  width: 100%;
661
  min-width: 0;
662
  }
663
  }
664
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
665
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  /* Add gap between logo and browser border */
39
  .logo-img {
40
  width: 6vw;
 
 
 
41
  position: fixed;
42
+ top: 21px;
43
+ left: 36px;
44
  z-index: 300;
45
  margin: 0;
46
+ background: #ffffff;
47
+ max-width: 4.2vw;
48
+ min-width: 56px;
49
+ height: auto;
50
+ border-radius: 50%;
51
+ padding: 4px;
52
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25);
53
+ transition: transform .25s ease;
54
  }
55
 
56
+ /* Move Py-Detect text further down by increasing top value of .logo-title-row */
57
  .logo-title-row {
58
  display: flex;
59
  flex-direction: row;
 
69
 
70
  .py-detect-title {
71
  position: fixed;
72
+ margin-left: 97px;
73
+ margin-top: -228px;
74
  text-align: left;
75
+ font-size: 3vw;
76
  color: #38bdf8;
77
  font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
78
  font-weight: 900;
 
103
  border: 2px solid #23272b;
104
  }
105
 
106
+ .py-shape {
107
+ display: inline-block;
108
+ width: 18px;
109
+ height: 4px;
110
+ background: #38bdf8;
111
+ margin: 0 8px;
112
+ vertical-align: middle;
113
+ border-radius: 2px;
114
+ box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff;
115
+ border: 2px solid #23272b;
116
+ }
117
 
118
+ .py-detect-title .py-letter.d {
119
+ color: #e3f6ff;
120
+ text-shadow: 0 0 6px #38bdf8;
121
+ }
122
 
123
+ .py-detect-title .py-letter.e {
124
+ color: #38bdf8;
125
+ text-shadow: 0 0 6px #38bdf8;
126
+ }
127
 
128
+ .py-detect-title .py-letter.t {
129
+ color: #e3f6ff;
130
+ text-shadow: 0 0 6px #38bdf8;
131
+ }
132
 
133
+ .py-detect-title .py-letter.e2 {
134
+ color: #38bdf8;
135
+ text-shadow: 0 0 6px #38bdf8;
136
+ }
137
+
138
+ .py-detect-title .py-letter.c {
139
+ color: #e3f6ff;
140
+ text-shadow: 0 0 6px #38bdf8;
141
+ }
142
+
143
+ .py-detect-title .py-letter.t2 {
144
+ color: #38bdf8;
145
+ text-shadow: 0 0 6px #38bdf8;
146
+ }
147
 
 
 
 
 
148
 
149
  .py-shape {
150
  display: inline-block;
 
220
  margin: 0 auto;
221
  }
222
 
223
+ .record-table th,
224
+ .record-table td {
225
+ white-space: nowrap;
226
+ min-width: 90px;
227
+ }
228
+
229
+ .record-table td {
230
+ vertical-align: middle;
231
+ }
232
+
233
+ .record-table th {
234
+ vertical-align: middle;
235
+ }
236
+
237
+ .record-table td a {
238
+ white-space: nowrap;
239
+ display: inline-block;
240
+ min-width: 80px;
241
+ }
242
+
243
  @media (max-width: 1800px) {
244
  .records {
245
  max-width: 100vw;
246
  min-width: 1000px;
247
  }
248
+
249
  .table-wrap {
250
  max-width: 100vw;
251
  min-height: 400px;
 
258
  width: 100%;
259
  max-width: 100vw;
260
  }
261
+
262
  .table-wrap {
263
  min-height: 200px;
264
  }
265
+
266
  .padded-table-wrap {
267
  padding-left: 4px;
268
  padding-right: 4px;
269
  }
270
+
271
  .records-header-row {
272
  flex-direction: column;
273
  align-items: stretch;
 
275
  padding-left: 4px;
276
  padding-right: 4px;
277
  }
278
+
279
  .page-title {
280
  text-align: center;
281
  margin-right: 0;
282
  }
283
+
284
  .toolbar {
285
  position: static;
286
  transform: none;
 
295
  border-bottom: 1px solid #edf2f7;
296
  text-align: left;
297
  white-space: nowrap;
298
+ min-width: 90px;
299
+ }
300
+
301
+ .records td {
302
+ vertical-align: middle;
303
+ }
304
+
305
+ .records th {
306
+ vertical-align: middle;
307
+ }
308
+
309
+ .records td a {
310
+ white-space: nowrap;
311
+ display: inline-block;
312
+ min-width: 80px;
313
  }
314
 
315
  .records thead th {
 
330
 
331
  .records th.actions-col, .records td.actions {
332
  color: #fff;
333
+ text-align: left;
334
+ padding-left: 18px;
335
+ padding-right: 18px;
336
+ width: 1%;
337
+ white-space: nowrap;
338
  }
339
 
340
  .records tbody tr:last-child td {
 
406
  color: #991b1b;
407
  }
408
 
409
+ .status-label {
410
+ display: inline-block;
411
+ padding: 3px 16px;
412
+ border-radius: 12px;
413
+ font-weight: 700;
414
+ font-size: 1em;
415
+ letter-spacing: 0.5px;
416
+ min-width: 70px;
417
+ text-align: center;
418
+ background: #e5e7eb;
419
+ color: #22223b;
420
+ }
421
+
422
+ .status-open {
423
+ background: #d1fae5;
424
+ color: #059669;
425
+ }
426
+
427
+ .status-under {
428
+ background: #dbeafe;
429
+ color: #2563eb;
430
+ }
431
+
432
+ .status-closed {
433
+ background: #fee2e2;
434
+ color: #dc2626;
435
+ }
436
+
437
  .mono {
438
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
439
  }
 
470
  border-color: #805ad5;
471
  }
472
 
473
+ .btn.delete {
474
+ background: #ef4444;
475
+ color: #fff;
476
+ border-color: #ef4444;
477
+ box-shadow: 0 2px 8px #ef444422;
478
+ transition: background 0.18s, color 0.18s;
479
+ }
480
+
481
+ .btn.delete:hover {
482
+ background: #991b1b;
483
+ color: #fff;
484
+ }
485
+
486
+ /* Icon buttons */
487
+ .icon-btn {
488
+ background: none;
489
+ border: none;
490
+ padding: 6px 10px;
491
+ margin: 0 2px;
492
+ border-radius: 6px;
493
+ cursor: pointer;
494
+ font-size: 1.18em;
495
+ transition: background 0.15s, color 0.15s, box-shadow 0.15s;
496
+ vertical-align: middle;
497
+ outline: none;
498
+ display: inline-flex;
499
+ align-items: center;
500
+ justify-content: center;
501
+ }
502
+
503
+ .icon-btn.view {
504
+ margin: 0;
505
+ padding: 0 8px;
506
+ background: none;
507
+ border: none;
508
+ cursor: pointer;
509
+ color: #2563eb;
510
+ font-size: 1.2em;
511
+ display: inline-flex;
512
+ align-items: center;
513
+ justify-content: center;
514
+ }
515
+
516
+ .icon-btn.edit {
517
+ color: #7c3aed;
518
+ }
519
+
520
+ .icon-btn.delete {
521
+ color: #ef4444;
522
+ }
523
+
524
+ .icon-btn:hover {
525
+ background: #f0f7ff;
526
+ box-shadow: 0 2px 8px #2563eb22;
527
+ }
528
+
529
+ .icon-btn.delete:hover {
530
+ background: #fff0f0;
531
+ color: #b91c1c;
532
+ }
533
+
534
+ .icon-btn.edit:hover {
535
+ background: #f3e8ff;
536
+ color: #5b21b6;
537
+ }
538
+
539
+ .icon-btn.view:hover {
540
+ background: #e0f2fe;
541
+ color: #0ea5e9;
542
+ }
543
+
544
+ .icon-btn.verify {
545
+ color: #22c55e;
546
+ }
547
+
548
+ .icon-btn.verify:hover {
549
+ background: #e0ffe6;
550
+ color: #15803d;
551
+ }
552
+
553
  .empty {
554
  text-align: center;
555
  color: #718096;
 
565
 
566
  .modal {
567
  position: fixed;
568
+ top: 0;
569
+ left: 0;
570
+ width: 100vw;
571
+ height: 100vh;
572
+ min-width: 0;
573
+ min-height: 0;
574
+ max-width: 100vw;
575
+ max-height: 100vh;
576
  background: #fff;
577
+ border-radius: 0;
578
+ box-shadow: none;
579
+ overflow-y: auto;
580
+ z-index: 2000;
581
+ padding: 0;
582
+ font-size: 1.13rem;
583
+ animation: none;
584
+ display: flex;
585
+ flex-direction: column;
586
  }
587
 
588
  .modal-header, .modal-footer {
589
+ padding: 24px 40px 18px 40px;
590
+ background: #f8fafc;
591
+ font-weight: 700;
592
+ font-size: 1.18rem;
593
+ color: #23272b;
594
  }
595
 
596
  .modal-body {
597
+ flex: 1 1 auto;
598
+ padding: 32px 48px 32px 48px;
599
+ background: #fff;
600
+ color: #23272b;
601
+ font-size: 1.08rem;
602
+ overflow-y: auto;
603
  }
604
 
 
 
605
  .detail-row {
606
  display: grid;
607
+ grid-template-columns: 180px 1fr;
608
+ gap: 12px;
609
+ padding: 10px 0;
610
+ border-bottom: 1px solid #e5e7eb;
611
  }
612
 
613
+ .detail-row:last-child {
614
+ border-bottom: none;
615
+ }
616
+
617
  .detail-row span {
618
+ color: #2563eb;
619
+ font-weight: 600;
620
+ font-size: 1.07em;
621
  }
622
 
623
+ .detail-row b {
624
+ color: #23272b;
625
+ font-weight: 500;
626
+ font-size: 1.07em;
627
+ }
628
+
629
+ .detail-block {
630
+ margin-top: 18px;
631
+ margin-bottom: 8px;
632
  }
633
 
634
+ .detail-block .label {
635
+ font-weight: 700;
636
+ color: #2563eb;
637
+ margin-bottom: 6px;
638
+ font-size: 1.09em;
639
+ }
640
+
641
  .explain {
642
  white-space: pre-wrap;
643
+ color: #23272b;
644
+ font-size: 1.07em;
645
+ }
646
+
647
+ .modal-footer {
648
+ text-align: right;
649
+ }
650
+
651
+ .btn {
652
+ padding: 8px 18px;
653
+ border: 1px solid #cbd5e0;
654
+ border-radius: 8px;
655
+ background: #f7fafc;
656
+ cursor: pointer;
657
+ font-size: 1.05em;
658
+ font-weight: 600;
659
+ margin-left: 8px;
660
  }
661
 
662
+ .btn:hover {
663
+ background: #e0e7ef;
664
+ }
665
+
666
  .records-center {
667
  display: flex;
668
  flex-direction: column;
 
760
  z-index: 10;
761
  }
762
 
763
+ .modern-searchbar-container.compact {
764
+ width: auto;
765
+ margin: 0 0 0 24px;
766
+ align-items: center;
767
+ }
768
 
769
  .modern-searchbar-form {
770
  display: flex;
 
778
  position: relative;
779
  }
780
 
781
+ .modern-searchbar-form.compact {
782
+ height: 44px;
783
+ min-width: 260px;
784
+ width: 320px;
785
+ padding: 0 8px 0 10px;
786
+ }
787
 
788
  .modern-searchbar-icon {
789
  display: flex;
 
804
  height: 32px;
805
  }
806
 
807
+ .modern-searchbar-input::placeholder {
808
+ color: #e0e7ef;
809
+ opacity: 1;
810
+ font-weight: 400;
811
+ }
812
 
813
  .modern-searchbar-btn {
814
  background: #fff;
 
825
  transition: background 0.18s, color 0.18s;
826
  }
827
 
828
+ .modern-searchbar-btn:hover {
829
+ background: #ede9fe;
830
+ color: #4f46e5;
831
+ }
832
 
833
  .modern-searchbar-form.white-bg {
834
  background: #fff !important;
 
840
  color: #23272b !important;
841
  }
842
 
843
+ .modern-searchbar-input.white-bg::placeholder {
844
+ color: #64748b !important;
845
+ opacity: 1;
846
+ }
847
 
848
  .modern-searchbar-icon svg {
849
  stroke: #64748b !important;
 
864
  transition: background 0.18s, color 0.18s;
865
  }
866
 
867
+ .modern-searchbar-btn:hover {
868
+ background: #38bdf8;
869
+ color: #fff;
870
+ }
871
 
872
+
873
+ .back-colo {
874
+ background: #011329;
875
+ width: 100%;
876
+ height: 7vw;
877
+ position: fixed;
878
+ }
879
  /* Search bar at top right corner */
880
  .searchbar-topright {
881
  position: fixed;
882
+ top: 20px;
883
  right: 48px;
884
  z-index: 1001;
885
  display: flex;
886
  align-items: center;
887
  }
888
 
889
+ .back-colo {
890
+ background: #011329;
891
+ width: 100%;
892
+ height: 7vw;
893
+ position: fixed;
894
+ }
895
+
896
  @media (max-width: 900px) {
897
  .searchbar-topright {
898
  top: 12px;
 
900
  width: 95vw;
901
  justify-content: flex-end;
902
  }
903
+
904
  .modern-searchbar-form.compact {
905
  width: 100%;
906
  min-width: 0;
907
  }
908
  }
909
 
910
+ /* Modern UI header styles from infopage */
911
+ .site-header {
912
+ background: #011329;
913
+ box-shadow: 0 2px 12px #38bdf844;
914
+ margin-bottom: 0;
915
+ position: relative;
916
+ z-index: 10;
917
+ padding-bottom: 0;
918
+ }
919
+
920
+ .header-inner {
921
+ display: flex;
922
+ align-items: center;
923
+ justify-content: flex-start;
924
+ padding: 18px 32px 0 32px;
925
+ position: relative;
926
+ }
927
+
928
+ .logo-cluster {
929
+ display: flex;
930
+ align-items: center;
931
+ gap: 18px;
932
+ }
933
+
934
+ .logo-img-header {
935
+ width: 54px;
936
+ height: 54px;
937
+ border-radius: 50%;
938
+ background: #fff;
939
+ box-shadow: 0 2px 8px rgba(0,0,0,0.18);
940
+ padding: 4px;
941
+ margin-top: -6px;
942
+ margin-bottom: 1vh;
943
+ }
944
+
945
+ .py-detect-title-header {
946
+ font-size: 2.1rem;
947
+ font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
948
+ font-weight: 900;
949
+ letter-spacing: 6px;
950
+ color: #38bdf8;
951
+ display: flex;
952
+ align-items: center;
953
+ gap: 2px;
954
+ margin-bottom: 1.5vh;
955
+ }
956
+
957
+ .py-detect-title-header .py-letter.p {
958
+ color: #e3f6ff;
959
+ text-shadow: 0 0 6px #38bdf8;
960
+ }
961
+
962
+ .py-detect-title-header .py-letter.y {
963
+ color: #38bdf8;
964
+ text-shadow: 0 0 6px #38bdf8;
965
+ }
966
+
967
+ .py-detect-title-header .py-shape {
968
+ color: #e3f6ff;
969
+ background: #e3f6ff;
970
+ text-shadow: 0 0 6px #38bdf8;
971
+ box-shadow: 0 0 6px #38bdf8, 0 0 2px #fff;
972
+ border: 2px solid #23272b;
973
+ width: 18px;
974
+ height: 4px;
975
+ display: inline-block;
976
+ margin: 0 8px;
977
+ border-radius: 2px;
978
+ }
979
+
980
+ .py-detect-title-header .py-letter.d {
981
+ color: #e3f6ff;
982
+ text-shadow: 0 0 6px #38bdf8;
983
+ }
984
+
985
+ .py-detect-title-header .py-letter.e {
986
+ color: #38bdf8;
987
+ text-shadow: 0 0 6px #38bdf8;
988
+ }
989
+
990
+ .py-detect-title-header .py-letter.t {
991
+ color: #e3f6ff;
992
+ text-shadow: 0 0 6px #38bdf8;
993
+ }
994
+
995
+ .py-detect-title-header .py-letter.e2 {
996
+ color: #38bdf8;
997
+ text-shadow: 0 0 6px #38bdf8;
998
+ }
999
+
1000
+ .py-detect-title-header .py-letter.c {
1001
+ color: #e3f6ff;
1002
+ text-shadow: 0 0 6px #38bdf8;
1003
+ }
1004
+
1005
+ .py-detect-title-header .py-letter.t2 {
1006
+ color: #38bdf8;
1007
+ text-shadow: 0 0 6px #38bdf8;
1008
+ }
1009
+
1010
+ body, main.content {
1011
+ background: #f4f6fa;
1012
+ min-height: 100vh;
1013
+ margin: 0;
1014
+ padding: 0;
1015
+ font-family: 'Segoe UI', 'Arial', 'Roboto', sans-serif;
1016
+ }
1017
+
1018
+ .record-card {
1019
+ background: #fff;
1020
+ border-radius: 10px;
1021
+ box-shadow: 0 2px 8px #0001, 0 1.5px 0 #e5e7eb;
1022
+ border: 1.5px solid #e5e7eb;
1023
+ margin: 40px auto 0 auto;
1024
+ max-width: 98vw;
1025
+ width: 98vw;
1026
+ min-width: 320px;
1027
+ padding: 0;
1028
+ overflow-x: auto;
1029
+ }
1030
+
1031
+ .record-header {
1032
+ display: flex;
1033
+ align-items: center;
1034
+ justify-content: space-between;
1035
+ padding: 18px 24px 8px 24px;
1036
+ border-bottom: 1.5px solid #e5e7eb;
1037
+ background: #f8fafc;
1038
+ border-radius: 10px 10px 0 0;
1039
+ }
1040
+
1041
+ .record-title-group {
1042
+ display: flex;
1043
+ align-items: center;
1044
+ gap: 12px;
1045
+ }
1046
+
1047
+ .record-title {
1048
+ font-size: 1.25rem;
1049
+ font-weight: 700;
1050
+ color: #222b45;
1051
+ }
1052
+
1053
+ .record-dropdown {
1054
+ font-size: 1.05rem;
1055
+ color: #222b45;
1056
+ background: #fff;
1057
+ border: 1px solid #e5e7eb;
1058
+ border-radius: 6px;
1059
+ padding: 4px 12px;
1060
+ margin-left: 8px;
1061
+ }
1062
+
1063
+ .record-header-actions {
1064
+ display: flex;
1065
+ align-items: center;
1066
+ gap: 10px;
1067
+ }
1068
+
1069
+ .record-search {
1070
+ border: 1.5px solid #e5e7eb;
1071
+ border-radius: 6px;
1072
+ padding: 6px 12px;
1073
+ font-size: 1rem;
1074
+ background: #fff;
1075
+ margin-right: 8px;
1076
+ min-width: 220px;
1077
+ }
1078
 
1079
+ .record-new-btn {
1080
+ background: #2563eb;
1081
+ color: #fff;
1082
+ font-weight: 600;
1083
+ border: none;
1084
+ border-radius: 6px;
1085
+ padding: 7px 22px;
1086
+ font-size: 1rem;
1087
+ box-shadow: 0 2px 8px #2563eb22;
1088
+ cursor: pointer;
1089
+ transition: background 0.18s;
1090
+ }
1091
+
1092
+ .record-new-btn:hover {
1093
+ background: #1e40af;
1094
+ }
1095
+
1096
+ .record-table {
1097
+ width: 100%;
1098
+ border-collapse: separate;
1099
+ border-spacing: 0;
1100
+ background: #fff;
1101
+ }
1102
+
1103
+ .record-table th, .record-table td {
1104
+ border-bottom: 1.5px solid #e5e7eb;
1105
+ padding: 12px 16px;
1106
+ text-align: left;
1107
+ font-size: 1.05rem;
1108
+ }
1109
+
1110
+ .record-table th {
1111
+ background: #f4f6fa;
1112
+ color: #222b45;
1113
+ font-weight: 700;
1114
+ }
1115
+
1116
+ .record-table tr:last-child td {
1117
+ border-bottom: none;
1118
+ }
1119
+
1120
+ .record-table tr {
1121
+ transition: background 0.15s;
1122
+ }
1123
+
1124
+ .record-table tr:hover {
1125
+ background: #f1f5f9;
1126
+ }
1127
+
1128
+ .record-table a {
1129
+ color: #2563eb;
1130
+ text-decoration: underline;
1131
+ cursor: pointer;
1132
+ }
1133
+
1134
+ .record-table .record-status {
1135
+ font-weight: 500;
1136
+ border-radius: 6px;
1137
+ padding: 2px 10px;
1138
+ background: #f1f5f9;
1139
+ color: #2563eb;
1140
+ font-size: 0.98em;
1141
+ }
1142
+
1143
+ .vertical-sections {
1144
+ display: flex;
1145
+ flex-direction: column;
1146
+ gap: 32px;
1147
+ }
1148
+
1149
+ .horizontal-sections {
1150
+ display: flex;
1151
+ flex-direction: column;
1152
+ gap: 36px;
1153
+ background: linear-gradient(120deg, #f0f7ff 0%, #e0f2fe 100%);
1154
+ border-radius: 24px;
1155
+ box-shadow: 0 8px 32px #2563eb22;
1156
+ padding: 32px 0 32px 0;
1157
+ }
1158
+
1159
+ .section-block {
1160
+ background: rgba(255,255,255,0.98);
1161
+ border-radius: 18px;
1162
+ box-shadow: 0 4px 24px #38bdf822;
1163
+ padding: 28px 38px 18px 38px;
1164
+ margin-bottom: 0;
1165
+ display: flex;
1166
+ flex-direction: column;
1167
+ gap: 22px;
1168
+ border-left: 6px solid #38bdf8;
1169
+ transition: box-shadow 0.2s, border 0.2s;
1170
+ }
1171
+
1172
+ .section-block:hover {
1173
+ box-shadow: 0 8px 32px #2563eb33;
1174
+ border-left: 6px solid #2563eb;
1175
+ }
1176
+
1177
+ .section-title {
1178
+ font-size: 1.32em;
1179
+ font-weight: 900;
1180
+ color: #1d4ed8;
1181
+ margin-bottom: 12px;
1182
+ letter-spacing: 1px;
1183
+ text-shadow: 0 1px 0 #fff;
1184
+ display: flex;
1185
+ align-items: center;
1186
+ gap: 10px;
1187
+ }
1188
+
1189
+ .subgroup-row {
1190
+ display: flex;
1191
+ flex-direction: row;
1192
+ gap: 36px;
1193
+ overflow-x: auto;
1194
+ }
1195
+
1196
+ .subgroup-block {
1197
+ min-width: 260px;
1198
+ flex: 1 1 0;
1199
+ background: rgba(248,250,252,0.95);
1200
+ border-radius: 14px;
1201
+ box-shadow: 0 2px 8px #38bdf822;
1202
+ padding: 18px 20px 10px 20px;
1203
+ margin-bottom: 0;
1204
+ margin-top: 8px;
1205
+ display: flex;
1206
+ flex-direction: column;
1207
+ gap: 0;
1208
+ border: 1.5px solid #e0e7ef;
1209
+ transition: box-shadow 0.18s, border 0.18s, background 0.18s;
1210
+ }
1211
+
1212
+ .subgroup-block:hover {
1213
+ box-shadow: 0 4px 16px #38bdf855;
1214
+ border: 1.5px solid #38bdf8;
1215
+ background: #e0f2fe;
1216
+ }
1217
+
1218
+ .subgroup-title {
1219
+ font-size: 1.11em;
1220
+ font-weight: 700;
1221
+ color: #0ea5e9;
1222
+ margin-bottom: 12px;
1223
+ letter-spacing: 0.5px;
1224
+ text-shadow: 0 1px 0 #fff;
1225
+ display: flex;
1226
+ align-items: center;
1227
+ gap: 8px;
1228
+ }
1229
+
1230
+ .fields-table {
1231
+ display: flex;
1232
+ flex-direction: column;
1233
+ gap: 0;
1234
+ background: none;
1235
+ box-shadow: none;
1236
+ border-radius: 0;
1237
+ padding: 0;
1238
+ }
1239
+
1240
+ .field-row {
1241
+ display: flex;
1242
+ flex-direction: row;
1243
+ align-items: flex-start;
1244
+ padding: 14px 0 14px 0;
1245
+ border-bottom: 1px solid #e0e7ef;
1246
+ font-size: 1.09em;
1247
+ background: none;
1248
+ box-shadow: none;
1249
+ border-radius: 0;
1250
+ transition: background 0.18s;
1251
+ gap: 32px;
1252
+ }
1253
+
1254
+ .field-row:last-child {
1255
+ border-bottom: none;
1256
+ }
1257
+
1258
+ .field-row span {
1259
+ color: #1e293b;
1260
+ font-weight: 700;
1261
+ letter-spacing: 0.2px;
1262
+ min-width: 180px;
1263
+ font-size: 1.08em;
1264
+ margin-right: 32px;
1265
+ text-align: left;
1266
+ display: inline-block;
1267
+ }
1268
+
1269
+ .field-row b {
1270
+ color: #2563eb;
1271
+ font-weight: 700;
1272
+ word-break: break-word;
1273
+ letter-spacing: 0.1px;
1274
+ font-size: 1.13em;
1275
+ margin-left: 8px;
1276
+ text-align: left;
1277
+ display: inline-block;
1278
+ }
1279
+
1280
+ .field-row b:hover {
1281
+ color: #0ea5e9;
1282
+ }
1283
+
1284
+ /* Stylish pills for subgroups */
1285
+ .subgroup-pills {
1286
+ display: flex;
1287
+ flex-wrap: wrap;
1288
+ gap: 12px;
1289
+ margin-bottom: 18px;
1290
+ }
1291
+
1292
+ .subgroup-pills button {
1293
+ background: linear-gradient(90deg, #38bdf8 0%, #2563eb 100%);
1294
+ border: none;
1295
+ border-radius: 20px;
1296
+ padding: 7px 22px;
1297
+ font-size: 1em;
1298
+ color: #fff;
1299
+ font-weight: 700;
1300
+ cursor: pointer;
1301
+ box-shadow: 0 2px 8px #2563eb22;
1302
+ transition: background 0.18s, color 0.18s, box-shadow 0.18s;
1303
+ outline: none;
1304
+ }
1305
+
1306
+ .subgroup-pills button.active,
1307
+ .subgroup-pills button:focus {
1308
+ background: linear-gradient(90deg, #2563eb 0%, #38bdf8 100%);
1309
+ color: #fff;
1310
+ box-shadow: 0 4px 16px #38bdf855;
1311
+ }
1312
+
1313
+ .subgroup-pills button:hover {
1314
+ background: linear-gradient(90deg, #0ea5e9 0%, #2563eb 100%);
1315
+ color: #fff;
1316
+ }
1317
+
1318
+ .field-card {
1319
+ display: flex;
1320
+ width: 30%;
1321
+ gap: 9vw;
1322
+ justify-content: space-between;
1323
+ }
1324
+
1325
+ /* Responsive tweaks */
1326
+ @media (max-width: 900px) {
1327
+ .section-block {
1328
+ padding: 18px 8vw 12px 8vw;
1329
+ }
1330
+
1331
+ .subgroup-block {
1332
+ min-width: 180px;
1333
+ padding: 12px 8px 8px 8px;
1334
+ }
1335
+
1336
+ .field-row span {
1337
+ min-width: 120px;
1338
+ }
1339
+ }
1340
+
1341
+ @media (max-width: 600px) {
1342
+ .section-block {
1343
+ padding: 10px 2vw 8px 2vw;
1344
+ }
1345
+
1346
+ .subgroup-block {
1347
+ min-width: 120px;
1348
+ padding: 8px 2px 6px 2px;
1349
+ }
1350
+
1351
+ .field-row span {
1352
+ min-width: 80px;
1353
+ font-size: 0.98em;
1354
+ }
1355
+
1356
+ .field-row b {
1357
+ font-size: 1em;
1358
+ }
1359
+ }
1360
+
1361
+ /* Modern styles for the filter bar, dropdowns, and buttons */
1362
+ .filter-bar {
1363
+ display: flex;
1364
+ gap: 16px;
1365
+ align-items: center;
1366
+ margin: 24px 0 12px 0;
1367
+ padding: 12px 18px;
1368
+ background: #f8fafc;
1369
+ border-radius: 12px;
1370
+ box-shadow: 0 2px 8px #2563eb11;
1371
+ }
1372
+
1373
+ .filter-bar select {
1374
+ padding: 6px 18px;
1375
+ border-radius: 6px;
1376
+ border: 1.5px solid #cbd5e1;
1377
+ font-size: 1em;
1378
+ color: #2563eb;
1379
+ background: #fff;
1380
+ font-weight: 600;
1381
+ outline: none;
1382
+ transition: border 0.15s;
1383
+ }
1384
+
1385
+ .filter-bar select:focus {
1386
+ border: 1.5px solid #38bdf8;
1387
+ }
1388
+
1389
+ .filter-bar button {
1390
+ padding: 6px 18px;
1391
+ border-radius: 6px;
1392
+ border: none;
1393
+ font-size: 1em;
1394
+ font-weight: 700;
1395
+ cursor: pointer;
1396
+ background: #2563eb;
1397
+ color: #fff;
1398
+ transition: background 0.15s;
1399
+ }
1400
+
1401
+ .filter-bar button:hover {
1402
+ background: #0ea5e9;
1403
+ }
1404
+
1405
+ .filter-bar button:last-child {
1406
+ background: #f87171;
1407
+ color: #fff;
1408
+ margin-left: 4px;
1409
+ }
1410
+
1411
+ .filter-bar button:last-child:hover {
1412
+ background: #ef4444;
1413
+ }
1414
+
1415
+ .analytics-summary {
1416
+ display: flex;
1417
+ gap: 32px;
1418
+ margin: 18px 0 8px 0;
1419
+ }
1420
+
1421
+ .summary-card {
1422
+ background: #f8fafc;
1423
+ border-radius: 12px;
1424
+ box-shadow: 0 2px 8px #2563eb11;
1425
+ padding: 18px 32px;
1426
+ min-width: 120px;
1427
+ text-align: center;
1428
+ }
1429
+
1430
+ .summary-card .summary-label {
1431
+ font-size: 1em;
1432
+ color: #64748b;
1433
+ font-weight: 600;
1434
+ margin-bottom: 6px;
1435
+ }
1436
+
1437
+ .summary-card .summary-value {
1438
+ font-size: 2em;
1439
+ font-weight: 900;
1440
+ color: #2563eb;
1441
+ }
1442
+
1443
+ .summary-card.open .summary-value {
1444
+ color: #059669;
1445
+ }
1446
+
1447
+ .summary-card.closed .summary-value {
1448
+ color: #dc2626;
1449
+ }
1450
+
1451
+ .record-meta {
1452
+ color: #64748b;
1453
+ font-size: 0.98em;
1454
+ margin: 0 0 10px 0;
1455
+ padding-left: 2px;
1456
+ font-weight: 500;
1457
+ letter-spacing: 0.1px;
1458
+ }
src/app/recordpage/recordpage.component.html CHANGED
@@ -1,117 +1,179 @@
1
- <!-- Search bar at top right corner -->
2
- <div class="searchbar-topright" *ngIf="!showDetails">
3
- <form (submit)="onModernSearch()" class="modern-searchbar-form compact white-bg">
4
- <span class="modern-searchbar-icon">
5
- <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#64748b" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
6
- </span>
7
- <input type="text" [(ngModel)]="q" name="modernSearch" class="modern-searchbar-input white-bg" placeholder="Search..." autocomplete="off" />
8
- <!-- <button type="submit" class="modern-searchbar-btn">GO</button>-->
9
- </form>
10
- </div>
11
-
12
- <!-- Logo and Py-Detect Title with blur wrapper -->
13
- <div class="logo-title-row">
14
- <img src="/assets/pykara-logo.png" alt="PyDetect Logo" class="logo-img" (click)="navigateHome()" style="cursor:pointer;" />
15
- <div class="py-detect-title">
16
- <span class="py-letter p">P</span>
17
- <span class="py-letter y">Y</span>
18
- <span class="py-shape"></span>
19
- <span class="py-letter d">D</span>
20
- <span class="py-letter e">E</span>
21
- <span class="py-letter t">T</span>
22
- <span class="py-letter e2">E</span>
23
- <span class="py-letter c">C</span>
24
- <span class="py-letter t2">T</span>
25
  </div>
26
  </div>
27
 
28
- <div style="height:128px;"></div>
29
-
30
- <main class="content" role="main">
31
- <div class="wrapper records-center" [ngClass]="{'show-details': showDetails}">
32
- <!-- Centered heading above the table -->
33
- <h1 class="page-title">Police Investigation Records</h1>
34
- <div class="table-wrap padded-table-wrap">
35
- <table class="records">
36
- <thead>
37
- <tr>
38
- <th (click)="setSort('caseId')" [attr.aria-sort]="ariaSort('caseId')">Case ID <span class="sort" [class.asc]="isAsc('caseId')" [class.desc]="isDesc('caseId')"></span></th>
39
- <th (click)="setSort('crime')" [attr.aria-sort]="ariaSort('crime')">Crime <span class="sort" [class.asc]="isAsc('crime')" [class.desc]="isDesc('crime')"></span></th>
40
- <th (click)="setSort('dateTime')" [attr.aria-sort]="ariaSort('dateTime')">Date &amp; Time <span class="sort" [class.asc]="isAsc('dateTime')" [class.desc]="isDesc('dateTime')"></span></th>
41
- <th (click)="setSort('location')" [attr.aria-sort]="ariaSort('location')">Location <span class="sort" [class.asc]="isAsc('location')" [class.desc]="isDesc('location')"></span></th>
42
- <th (click)="setSort('status')" [attr.aria-sort]="ariaSort('status')">Status <span class="sort" [class.asc]="isAsc('status')" [class.desc]="isDesc('status')"></span></th>
43
- <th (click)="setSort('Investigation Officer')" [attr.aria-sort]="ariaSort('Investigation Officer')">Investigation Officer <span class="sort" [class.asc]="isAsc('Investigation Officer')" [class.desc]="isDesc('Investigation Officer')"></span></th>
44
- <th class="actions-col">Actions</th>
45
- </tr>
46
- </thead>
47
-
48
- <tbody>
49
- <tr *ngFor="let c of rows, let i = index">
50
- <td class="mono">{{ c.caseId || '—' }}</td>
51
- <td>{{ c.crime || '—' }}</td>
52
- <td>{{ c.dateTime ? (c.dateTime | date:'yyyy-MM-dd HH:mm') : '—' }}</td>
53
- <td class="ellipsis" [title]="c.police.address || ''">{{ c.police.address || '—' }}</td>
54
- <td>
55
- <span class="status"
56
- [class.open]="c.status==='Open'"
57
- [class.under]="c.status==='Under Investigation'"
58
- [class.closed]="c.status==='Closed'">
59
- {{ c.status || '—' }}
60
- </span>
61
- </td>
62
- <td>{{ c.police.name || '—' }}</td>
63
- <td class="actions">
64
- <button type="button" class="btn view" (click)="openDetails(c, i)">View Details</button>
65
- <button type="button" class="btn edit" (click)="editCase(c, i)">Edit</button>
66
- </td>
67
- </tr>
68
-
69
- <tr *ngIf="rows.length === 0">
70
- <td colspan="7" class="empty">No records found.</td>
71
- </tr>
72
- </tbody>
73
- </table>
74
  </div>
 
 
 
 
75
 
76
- <!-- Modal -->
77
- <!-- Modal white blur overlay for background -->
78
- <div class="modal-blur-overlay" *ngIf="showDetails"></div>
79
-
80
- <!-- Modal backdrop and modal as before -->
81
- <div class="modal-backdrop" *ngIf="showDetails" (click)="closeDetails()"></div>
82
- <div class="modal" *ngIf="showDetails" role="dialog" aria-modal="true" aria-labelledby="detailsTitle">
83
- <div class="modal-header">
84
- <h2 id="detailsTitle">Case Details</h2>
85
- </div>
86
- <div class="modal-body" *ngIf="selectedCase as sc">
87
- <div class="detail-row"><span>Case ID</span><b>{{ sc.caseId || '—' }}</b></div>
88
- <div class="detail-row"><span>Crime</span><b>{{ sc.crime || '—' }}</b></div>
89
- <div class="detail-row"><span>Date & Time</span><b>{{ sc.dateTime ? (sc.dateTime | date:'yyyy-MM-dd HH:mm') : '—' }}</b></div>
90
- <div class="detail-row"><span>Location</span><b>{{ sc.police.address || '—' }}</b></div>
91
-
92
- <hr />
93
 
94
- <div class="detail-row"><span>Investigation Officer</span><b>{{ sc.police.name || '—' }}</b></div>
95
- <div class="detail-row"><span>Duty Person</span><b>{{ sc.police.dutyPerson || '—' }}</b></div>
96
- <div class="detail-row"><span>Mode of Crime</span><b>{{ sc.police.modeOfCrime || '—' }}</b></div>
97
 
98
- <hr />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
- <div class="detail-row"><span>Accused Name</span><b>{{ sc.accused.name || '—' }}</b></div>
101
- <div class="detail-row"><span>Accused Gender</span><b>{{ sc.accused.gender || '—' }}</b></div>
102
- <div class="detail-row"><span>Accused Age</span><b>{{ sc.accused.age || '—' }}</b></div>
103
- <div class="detail-row"><span>Accused Address</span><b>{{ sc.accused.address || '—' }}</b></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
- <hr />
 
 
106
 
107
- <div class="detail-block">
108
- <div class="label">Notes / Explanation</div>
109
- <p class="explain">{{ sc.police.information || '—' }}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  </div>
111
- </div>
112
- <div class="modal-footer">
113
- <button type="button" class="btn" (click)="closeDetails()">Close</button>
114
- </div>
115
  </div>
116
  </div>
117
- </main>
 
 
 
 
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>
21
  </div>
22
 
23
+ <!-- Salesforce-style card/table content below the header -->
24
+ <div class="record-card">
25
+ <div class="record-header">
26
+ <div class="record-title-group">
27
+ <span class="record-title">Police Investigation Records</span>
28
+ <select class="record-dropdown">
29
+ <option>Recently Viewed</option>
30
+ <option>All Records</option>
31
+ </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  </div>
33
+ <div class="record-header-actions">
34
+ <input class="record-search" type="text" [(ngModel)]="q" placeholder="Search this list..." />
35
+ </div>
36
+ </div>
37
 
38
+ <!-- Analytics summary panel -->
39
+ <div class="analytics-summary">
40
+ <div class="summary-card total">
41
+ <div class="summary-label">Total Cases</div>
42
+ <div class="summary-value">{{ totalCases }}</div>
43
+ </div>
44
+ <div class="summary-card open">
45
+ <div class="summary-label">Open</div>
46
+ <div class="summary-value">{{ openCases }}</div>
47
+ </div>
48
+ <div class="summary-card closed">
49
+ <div class="summary-label">Closed</div>
50
+ <div class="summary-value">{{ closedCases }}</div>
51
+ </div>
52
+ </div>
 
 
53
 
54
+ <div class="record-meta" style="padding: 8px 24px 0 24px; color: #6b7280; font-size: 0.98em;">
55
+ {{ rows.length }} items • Updated a few seconds ago
56
+ </div>
57
 
58
+ <!-- Filter bar above the table -->
59
+ <div class="filter-bar">
60
+ <select [(ngModel)]="filterCrimeType">
61
+ <option value="">Crime Type</option>
62
+ <option *ngFor="let type of crimeTypes">{{ type }}</option>
63
+ </select>
64
+ <select [(ngModel)]="filterStatus">
65
+ <option value="">Status</option>
66
+ <option *ngFor="let status of statusTypes">{{ status }}</option>
67
+ </select>
68
+ <select [(ngModel)]="filterLocation">
69
+ <option value="">Location</option>
70
+ <option *ngFor="let loc of locations">{{ loc }}</option>
71
+ </select>
72
+ <select [(ngModel)]="filterOfficer">
73
+ <option value="">Officer</option>
74
+ <option *ngFor="let officer of officers">{{ officer }}</option>
75
+ </select>
76
+ <button (click)="applyFilters()">Apply</button>
77
+ <button (click)="resetFilters()">Reset</button>
78
+ </div>
79
 
80
+ <table class="record-table">
81
+ <thead>
82
+ <tr>
83
+ <th>#</th>
84
+ <th>Case ID</th>
85
+ <th>Status</th>
86
+ <th>Crime Type</th>
87
+ <th>Date &amp; Time</th>
88
+ <th>Location</th>
89
+ <th>Investigation Officer</th>
90
+ <th>Suspect Name</th>
91
+ <th>Reported By</th>
92
+ <th>Last Updated</th>
93
+ <th>Verified By</th>
94
+ <th>Actions</th>
95
+ </tr>
96
+ </thead>
97
+ <tbody>
98
+ <tr *ngFor="let c of rows, let i = index">
99
+ <td>{{ i + 1 }}</td>
100
+ <td><a (click)="openDetails(c, i)">{{ c.caseId || '—' }}</a></td>
101
+ <td>
102
+ <span class="status-label"
103
+ [ngClass]="{
104
+ 'status-open': c.status === 'Open',
105
+ 'status-under': c.status === 'Under Investigation',
106
+ 'status-closed': c.status === 'Closed'
107
+ }">
108
+ {{ c.status || '—' }}
109
+ </span>
110
+ </td>
111
+ <td>{{ c.crime || '—' }}</td>
112
+ <td>{{ c.dateTime ? (c.dateTime | date:'M/d/yyyy HH:mm') : '—' }}</td>
113
+ <td>{{ c.police?.address || '—' }}</td>
114
+ <td>{{ c.police?.name || '—' }}</td>
115
+ <td>{{ c.accused?.name || '—' }}</td>
116
+ <td>{{ c.reportedBy || '—' }}</td>
117
+ <td>{{ c.lastUpdated ? (c.lastUpdated | date:'M/d/yyyy HH:mm') : '—' }}</td>
118
+ <td>{{ c.verifiedBy || '���' }}</td>
119
+ <td>
120
+ <button class="icon-btn verify" (click)="verifyCase(i)" title="Verify">
121
+ <i class="fas fa-user-check"></i>
122
+ </button>
123
+ <button class="icon-btn view" (click)="openDetails(c, i)" title="View">
124
+ <i class="fas fa-eye"></i>
125
+ </button>
126
+ <button class="icon-btn edit" (click)="editCase(c, i)" title="Edit">
127
+ <i class="fas fa-edit"></i>
128
+ </button>
129
+ <button class="icon-btn delete" (click)="deleteCase(i)" title="Delete">
130
+ <i class="fas fa-trash"></i>
131
+ </button>
132
+ </td>
133
+ </tr>
134
+ <tr *ngIf="rows.length === 0">
135
+ <td colspan="12" class="empty">No records found.</td>
136
+ </tr>
137
+ </tbody>
138
+ </table>
139
+ </div>
140
 
141
+ <!-- Modal -->
142
+ <!-- Modal white blur overlay for background -->
143
+ <div class="modal-blur-overlay" *ngIf="showDetails"></div>
144
 
145
+ <!-- Modal backdrop and modal as before -->
146
+ <div class="modal-backdrop" *ngIf="showDetails" (click)="closeDetails()"></div>
147
+ <div class="modal" *ngIf="showDetails" role="dialog" aria-modal="true" aria-labelledby="detailsTitle">
148
+ <div class="modal-header">
149
+ <h2 id="detailsTitle">Case Details</h2>
150
+ </div>
151
+ <!-- View Modal: Show all subgroup pills and fields for each section -->
152
+ <div class="modal-body" *ngIf="selectedCase as sc">
153
+ <div class="modal-sections-grid">
154
+ <ng-container *ngFor="let sectionKey of ['crime', 'suspect', 'notes']">
155
+ <div class="section-block">
156
+ <div class="section-title">{{ sectionKey === 'crime' ? 'Crime Details' : sectionKey === 'suspect' ? 'Suspect Details' : 'Evidence and Documents' }}</div>
157
+ <div class="subgroup-pills">
158
+ <button *ngFor="let subgroup of getSubgroups(sectionKey)"
159
+ [class.active]="selectedSubgroup[sectionKey] === subgroup"
160
+ (click)="selectSubgroup(sectionKey, subgroup)">
161
+ {{ subgroup }}
162
+ </button>
163
+ </div>
164
+ <div class="fields-grid">
165
+ <ng-container *ngFor="let field of getFieldsForSubgroup(sectionKey, selectedSubgroup[sectionKey])">
166
+ <div class="field-card">
167
+ <span>{{ field }}</span>
168
+ <b>{{ getFieldValue(sc, sectionKey, field) }}</b>
169
+ </div>
170
+ </ng-container>
171
+ </div>
172
  </div>
173
+ </ng-container>
 
 
 
174
  </div>
175
  </div>
176
+ <div class="modal-footer">
177
+ <button type="button" class="btn" (click)="closeDetails()">Close</button>
178
+ </div>
179
+ </div>
src/app/recordpage/recordpage.component.ts CHANGED
@@ -1,6 +1,7 @@
1
  import { Component, OnInit } from '@angular/core';
2
  import { Router } from '@angular/router';
3
  import { CaseStoreService, PoliceCase } from '../case-store.service';
 
4
 
5
  @Component({
6
  selector: 'app-recordpage',
@@ -22,14 +23,218 @@ export class RecordpageComponent implements OnInit {
22
  sortKey: 'caseId' | 'crime' | 'dateTime' | 'location' | 'status' | 'Investigation Officer' = 'dateTime';
23
  sortDir: 'asc' | 'desc' = 'desc';
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  constructor(private caseStore: CaseStoreService, private router: Router) { }
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  ngOnInit(): void {
28
  this.load();
 
 
29
  }
30
 
31
  load(): void {
32
  this.cases = this.caseStore.getPoliceCases();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
 
35
  // search over visible columns (no accused)
@@ -71,27 +276,23 @@ export class RecordpageComponent implements OnInit {
71
  }
72
  }
73
 
 
74
  get rows(): PoliceCase[] {
75
- const out = this.filtered.slice();
76
- const key = this.sortKey, dir = this.sortDir === 'asc' ? 1 : -1;
77
- out.sort((a, b) => {
78
- const va = this.cell(a, key), vb = this.cell(b, key);
79
- if (typeof va === 'number' && typeof vb === 'number') return (va - vb) * dir;
80
- return va.toString().localeCompare(vb.toString()) * dir;
81
- });
82
- return out;
83
  }
84
 
85
  openDetails(c: PoliceCase, i: number): void {
86
  this.selectedCase = c;
87
  this.selectedIndex = i;
88
  this.showDetails = true;
 
89
  }
90
 
91
  closeDetails(): void {
92
  this.showDetails = false;
93
  this.selectedCase = null;
94
  this.selectedIndex = -1;
 
95
  }
96
 
97
  editCase(c: PoliceCase, i: number): void {
@@ -108,4 +309,33 @@ export class RecordpageComponent implements OnInit {
108
  navigateHome(): void {
109
  this.router.navigate(['/']);
110
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  }
 
1
  import { Component, OnInit } from '@angular/core';
2
  import { Router } from '@angular/router';
3
  import { CaseStoreService, PoliceCase } from '../case-store.service';
4
+ import { InfopageComponent } from '../infopage/infopage.component';
5
 
6
  @Component({
7
  selector: 'app-recordpage',
 
23
  sortKey: 'caseId' | 'crime' | 'dateTime' | 'location' | 'status' | 'Investigation Officer' = 'dateTime';
24
  sortDir: 'asc' | 'desc' = 'desc';
25
 
26
+ objectKeys = Object.keys;
27
+
28
+ // For modal subgroup pills
29
+ selectedSubgroup: any = {
30
+ crime: 'Identification & Timing',
31
+ suspect: 'Identity',
32
+ notes: 'Investigation Notes'
33
+ };
34
+
35
+ // Reference infopage section/subgroup/fields structure
36
+ sections = new InfopageComponent(null as any, null as any).sections;
37
+
38
+ getSubgroups(sectionKey: string): string[] {
39
+ return Object.keys(this.sections[sectionKey].subgroups);
40
+ }
41
+
42
+ getFieldsForSubgroup(sectionKey: string, subgroup: string): string[] {
43
+ return this.sections[sectionKey].subgroups[subgroup] || [];
44
+ }
45
+
46
+ selectSubgroup(sectionKey: string, subgroup: string) {
47
+ this.selectedSubgroup[sectionKey] = subgroup;
48
+ }
49
+
50
+ getFieldValue(sc: any, sectionKey: string, field: string): any {
51
+ // Map field labels to PoliceCase property paths
52
+ const fieldMap: { [key: string]: string | string[] } = {
53
+ // Crime Details
54
+ 'Case ID': 'caseId',
55
+ 'FIR / Ref #': 'firRef',
56
+ 'Crime Type': 'crime',
57
+ 'Case Category': 'caseCategory',
58
+ 'Date & Time (Entry)': 'dateTime',
59
+ 'Occurred From': 'occurredFrom',
60
+ 'Occurred To': 'occurredTo',
61
+ 'Time Reported': 'timeReported',
62
+ 'Time Discovered': 'timeDiscovered',
63
+ 'Country': 'country',
64
+ 'State': 'state',
65
+ 'District': 'district',
66
+ 'Number of Victims': 'numberOfVictims',
67
+ 'Brief Description': 'briefDescription',
68
+ 'Location': ['police', 'address'],
69
+ 'Jurisdiction / PS': 'jurisdiction',
70
+ 'Scene Type': 'sceneType',
71
+ 'Reported By': 'reportedBy',
72
+ 'Reported Contact': 'reportedContact',
73
+ 'Witness Count': 'witnessCount',
74
+ 'Victim Name': 'victimName',
75
+ 'Victim Contact': 'victimContact',
76
+ 'Victim Summary': 'victimSummary',
77
+ 'Suspected Offender Known?': 'suspectedOffenderKnown',
78
+ 'Suspect Link': 'suspectLink',
79
+ 'Legal Sections / Charges': 'legalSections',
80
+ 'Offence Category': 'offenceCategory',
81
+ 'Offence Description': 'offenceDescription',
82
+ 'Suspected Motive': 'suspectedMotive',
83
+ 'Confirmed Motive': 'confirmedMotive',
84
+ 'Weapon Involved': 'weaponInvolved',
85
+ 'Property Loss / Damage': 'propertyLoss',
86
+ 'Evidence Collected': 'evidenceCollected',
87
+ 'Forensic Tests Required': 'forensicTestsRequired',
88
+ 'Scene Condition': 'sceneCondition',
89
+ 'Photos / Video?': 'photosVideo',
90
+ 'CCTV Present?': 'cctvPresent',
91
+ 'CCTV Sources / IDs': 'cctvSources',
92
+ 'Physical Evidence (list)': 'physicalEvidence',
93
+ 'Chain of Custody?': 'chainOfCustody',
94
+ 'Digital Evidence': 'digitalEvidence',
95
+ 'Evidence Storage Reference': 'evidenceStorageReference',
96
+ 'Investigating Officer': ['police', 'name'],
97
+ 'Duty Person': ['police', 'dutyPerson'],
98
+ 'Supervising Officer': ['police', 'supervisingOfficer'],
99
+ 'Patrol Notes': ['police', 'patrolNotes'],
100
+ 'Arrest Made': 'arrestMade',
101
+ 'Arrest Location': 'arrestLocation',
102
+ 'Initial Actions Taken': 'initialActionsTaken',
103
+ 'riskLevel': 'riskLevel',
104
+ 'Confidentiality': 'confidentiality',
105
+ 'Biometric / Forensic IDs': 'biometricIds',
106
+ 'DNA Ref ID': 'dnaRefId',
107
+ 'Fingerprint ID': 'fingerprintId',
108
+ 'Case Status': 'status',
109
+ 'Linked Cases': 'linkedCases',
110
+ 'arrestCount': 'arrestCount',
111
+ 'Case Priority': 'casePriority',
112
+ 'Follow-up Date': 'followUpDate',
113
+ 'Court Case ID': 'courtCaseId',
114
+ 'Next Hearing Date': 'nextHearingDate',
115
+ 'Final Summary': 'finalSummary',
116
+ 'Remark': 'remark',
117
+ // Suspect Details
118
+ 'Suspect ID': ['accused', 'suspectId'],
119
+ 'Suspect Name': ['accused', 'name'],
120
+ 'Alias / Nickname': ['accused', 'alias'],
121
+ 'Age': ['accused', 'age'],
122
+ 'Gender': ['accused', 'gender'],
123
+ 'Nationality': ['accused', 'nationality'],
124
+ 'Nationality ID / Passport Number': ['accused', 'passportNumber'],
125
+ 'Languages': ['accused', 'languages'],
126
+ 'Address': ['accused', 'address'],
127
+ 'Known Aliases': ['accused', 'knownAliases'],
128
+ 'Government ID': ['accused', 'governmentId'],
129
+ 'Height (cm)': ['accused', 'height'],
130
+ 'Weight (kg)': ['accused', 'weight'],
131
+ 'Build': ['accused', 'build'],
132
+ 'Hair Color': ['accused', 'hairColor'],
133
+ 'Eye Color': ['accused', 'eyeColor'],
134
+ 'Distinguishing Marks': ['accused', 'distinguishingMarks'],
135
+ 'Tattoo Details': ['accused', 'tattooDetails'],
136
+ 'Scar Details': ['accused', 'scarDetails'],
137
+ 'Photo Upload': ['accused', 'photoUpload'],
138
+ 'Employment': ['accused', 'employment'],
139
+ 'Education': ['accused', 'education'],
140
+ 'Occupation': ['accused', 'occupation'],
141
+ 'Company': ['accused', 'company'],
142
+ 'Workplace Address': ['accused', 'workplaceAddress'],
143
+ 'Marital Status': ['accused', 'maritalStatus'],
144
+ 'Known Habits': ['accused', 'knownHabits'],
145
+ 'Known Financial Details': ['accused', 'knownFinancialDetails'],
146
+ 'Associate Names': ['accused', 'associateNames'],
147
+ 'Gang Affiliation': ['accused', 'gangAffiliation'],
148
+ 'Family Connections': ['accused', 'familyConnections'],
149
+ 'Social Media Handles': ['accused', 'socialMediaHandles'],
150
+ 'Criminal History': ['accused', 'criminalHistory'],
151
+ 'Prior Arrests': ['accused', 'priorArrests'],
152
+ 'Probation/Parole Status': ['accused', 'probationStatus'],
153
+ // Notes/Evidence
154
+ 'Initial Findings': ['police', 'information'],
155
+ 'Detailed Notes': ['notes', 'detailedNotes'],
156
+ 'Status': 'status',
157
+ 'Version History / Updates': ['notes', 'versionHistory'],
158
+ 'Evidence Photos': ['legal', 'evidencePhotos'],
159
+ 'Evidence Videos': ['legal', 'evidenceVideos'],
160
+ 'Evidence Documents': ['legal', 'evidenceDocuments'],
161
+ 'Links to Evidence': ['legal', 'linksToEvidence'],
162
+ 'Final Recommendations': ['legal', 'finalRecommendations'],
163
+ 'Witness Statements': ['legal', 'witnessStatements'],
164
+ 'Confessions': ['legal', 'confessions'],
165
+ // Audit Fields
166
+ 'Created By': 'createdBy',
167
+ 'Creation Date': 'creationDate',
168
+ 'Last Updated': 'lastUpdated',
169
+ 'Verified By': 'verifiedBy'
170
+ };
171
+
172
+ const path = fieldMap[field] || field;
173
+ if (Array.isArray(path)) {
174
+ let value = sc;
175
+ for (const p of path) {
176
+ if (value && value[p] !== undefined) value = value[p];
177
+ else return '—';
178
+ }
179
+ return value !== undefined && value !== null && value !== '' ? value : '—';
180
+ } else {
181
+ return sc[path] !== undefined && sc[path] !== null && sc[path] !== '' ? sc[path] : '—';
182
+ }
183
+ }
184
+
185
+ getValue(obj: any, key: string): any {
186
+ return obj && obj[key] !== undefined && obj[key] !== null && obj[key] !== '' ? obj[key] : '—';
187
+ }
188
+
189
  constructor(private caseStore: CaseStoreService, private router: Router) { }
190
 
191
+ // Filter state
192
+ filterCrimeType: string = '';
193
+ filterStatus: string = '';
194
+ filterLocation: string = '';
195
+ filterOfficer: string = '';
196
+
197
+ crimeTypes: string[] = [];
198
+ statusTypes: string[] = [];
199
+ locations: string[] = [];
200
+ officers: string[] = [];
201
+
202
+ filteredCases: PoliceCase[] = [];
203
+
204
  ngOnInit(): void {
205
  this.load();
206
+ this.populateFilterOptions();
207
+ this.applyFilters();
208
  }
209
 
210
  load(): void {
211
  this.cases = this.caseStore.getPoliceCases();
212
+ this.populateFilterOptions();
213
+ this.applyFilters();
214
+ }
215
+
216
+ populateFilterOptions() {
217
+ this.crimeTypes = [...new Set(this.cases.map(c => c.crime).filter(Boolean))] as string[];
218
+ this.statusTypes = [...new Set(this.cases.map(c => c.status).filter(Boolean))] as string[];
219
+ this.locations = [...new Set(this.cases.map(c => c.police?.address).filter(Boolean))] as string[];
220
+ this.officers = [...new Set(this.cases.map(c => c.police?.name).filter(Boolean))] as string[];
221
+ }
222
+
223
+ applyFilters() {
224
+ this.filteredCases = this.cases.filter(c =>
225
+ (!this.filterCrimeType || c.crime === this.filterCrimeType) &&
226
+ (!this.filterStatus || c.status === this.filterStatus) &&
227
+ (!this.filterLocation || c.police?.address === this.filterLocation) &&
228
+ (!this.filterOfficer || c.police?.name === this.filterOfficer)
229
+ );
230
+ }
231
+
232
+ resetFilters() {
233
+ this.filterCrimeType = '';
234
+ this.filterStatus = '';
235
+ this.filterLocation = '';
236
+ this.filterOfficer = '';
237
+ this.applyFilters();
238
  }
239
 
240
  // search over visible columns (no accused)
 
276
  }
277
  }
278
 
279
+ // Update your table to use filteredCases instead of rows
280
  get rows(): PoliceCase[] {
281
+ return this.filteredCases;
 
 
 
 
 
 
 
282
  }
283
 
284
  openDetails(c: PoliceCase, i: number): void {
285
  this.selectedCase = c;
286
  this.selectedIndex = i;
287
  this.showDetails = true;
288
+ document.body.classList.add('modal-open');
289
  }
290
 
291
  closeDetails(): void {
292
  this.showDetails = false;
293
  this.selectedCase = null;
294
  this.selectedIndex = -1;
295
+ document.body.classList.remove('modal-open');
296
  }
297
 
298
  editCase(c: PoliceCase, i: number): void {
 
309
  navigateHome(): void {
310
  this.router.navigate(['/']);
311
  }
312
+
313
+ onNewRecord(): void {
314
+ // Placeholder: implement record creation logic or modal here
315
+ alert('New record functionality coming soon!');
316
+ }
317
+
318
+ deleteCase(index: number): void {
319
+ if (confirm('Are you sure you want to delete this case?')) {
320
+ this.caseStore.deletePoliceCaseAt(index);
321
+ this.load();
322
+ }
323
+ }
324
+
325
+ verifyCase(index: number): void {
326
+ const adminName = 'Admin'; // Replace with actual admin name if available
327
+ const updated = { ...this.cases[index], verifiedBy: adminName, lastUpdated: new Date().toISOString() };
328
+ this.caseStore.updatePoliceCaseAt(index, updated);
329
+ this.load();
330
+ }
331
+
332
+ get totalCases(): number {
333
+ return this.cases.length;
334
+ }
335
+ get openCases(): number {
336
+ return this.cases.filter(c => c.status === 'Open').length;
337
+ }
338
+ get closedCases(): number {
339
+ return this.cases.filter(c => c.status === 'Closed').length;
340
+ }
341
  }
src/app/shared/case-store.service.ts CHANGED
@@ -22,6 +22,9 @@ export interface PoliceCase {
22
  address: string;
23
  occupation?: string;
24
  };
 
 
 
25
  }
26
 
27
  @Injectable({ providedIn: 'root' })
 
22
  address: string;
23
  occupation?: string;
24
  };
25
+ lastUpdated?: string;
26
+ nextAction?: string;
27
+ casePriority?: 'High' | 'Medium' | 'Low';
28
  }
29
 
30
  @Injectable({ providedIn: 'root' })
src/app/shared/truncate.pipe.ts ADDED
File without changes
src/assets/1-old.JPG ADDED

Git LFS Details

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

Git LFS Details

  • SHA256: 28a45e50f5d64d9b04d1dc4587cb2a3ed5b1a1ea160e3eb760679c4051050730
  • Pointer size: 130 Bytes
  • Size of remote file: 20.1 kB
src/assets/3.JPG ADDED

Git LFS Details

  • SHA256: d33bddc759b3afb011c827bfdba6d625a44bcf7913fc625715af52b393b3c2e0
  • Pointer size: 130 Bytes
  • Size of remote file: 25.9 kB
src/assets/4.JPG ADDED

Git LFS Details

  • SHA256: 0d86fed9a3d8d98d86473b595847b5125fb3116c1d2cfc20cc78d70496559961
  • Pointer size: 130 Bytes
  • Size of remote file: 17 kB
src/assets/5.JPG ADDED

Git LFS Details

  • SHA256: c49a044008ebac42ef09eb3eccc1a7ea3a5f30fbae6a68f805872df6a6fd8e29
  • Pointer size: 130 Bytes
  • Size of remote file: 27 kB
src/assets/6.JPG ADDED

Git LFS Details

  • SHA256: bad6b2ee1a997c3087464338a019051c2f9891042d9713d55b9d3e9b983b6a59
  • Pointer size: 130 Bytes
  • Size of remote file: 17.3 kB
src/assets/7.JPG ADDED

Git LFS Details

  • SHA256: c5e516318830ae7f14768744c6f68d088d94f094f65b7f773132bb76ab828da2
  • Pointer size: 130 Bytes
  • Size of remote file: 22 kB
src/assets/8.JPG ADDED

Git LFS Details

  • SHA256: 7bfe9727ff52a357f202e42d0099033f7a353d8dc4b4b516c2345baafd328340
  • Pointer size: 130 Bytes
  • Size of remote file: 16 kB
src/assets/9.JPG ADDED

Git LFS Details

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

Git LFS Details

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

Git LFS Details

  • SHA256: 92f913300dfd5ef30b16121cf273e13d43f96cbdb442d79ac450281e643456b9
  • Pointer size: 131 Bytes
  • Size of remote file: 587 kB

Git LFS Details

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

Git LFS Details

  • SHA256: 92f913300dfd5ef30b16121cf273e13d43f96cbdb442d79ac450281e643456b9
  • Pointer size: 131 Bytes
  • Size of remote file: 587 kB
src/assets/font/EBGaramond-Medium.ttf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8e2105ea6bbb11a408667c03e5f0fa19f69fffe417fffe9b915baee4bda9c77f
3
+ size 561508
src/assets/font/EBGaramond-Regular.ttf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a45a5040e1a328d056871d42aa70f10f7ca3bf5c447887b80720542feeead5f2
3
+ size 558640
src/assets/font/Roliana-Regular.otf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e1f3947d7128de625e8815bc66bd3ece11329410519e8c0a3eef04f9bf03ae41
3
+ size 110052
src/assets/hero-jpg.jpg ADDED

Git LFS Details

  • SHA256: 3a8f6e92b08b67f73540db6b998140eb666b2734668adb05612479748b46c79c
  • Pointer size: 131 Bytes
  • Size of remote file: 121 kB
src/assets/hero-old.png ADDED

Git LFS Details

  • SHA256: 21a7fb629c07d487a166c58d33145b2a5e525680deb33f977ba2a19ab0571bd3
  • Pointer size: 132 Bytes
  • Size of remote file: 1.22 MB
src/assets/hero.png ADDED

Git LFS Details

  • SHA256: b5a30310076faae7dc54e0c1f646956beb819c17e3cd620be7821908b39cbb45
  • Pointer size: 132 Bytes
  • Size of remote file: 1.48 MB
src/assets/home.png ADDED

Git LFS Details

  • SHA256: 743f3ffc55716e7e11aacae879790d9cdde1bf597257ca3f345cbd70eccd19a8
  • Pointer size: 132 Bytes
  • Size of remote file: 1.48 MB
src/favicon.ico ADDED
src/index.html CHANGED
@@ -5,11 +5,13 @@
5
  <title>Py-Detect</title>
6
  <base href="/">
7
  <meta name="viewport" content="width=device-width, initial-scale=1">
8
- <link rel="icon" type="image/x-icon" href="pykara-favicon.ico">
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
 
 
 
10
  </head>
11
  <body>
12
  <app-root></app-root>
13
  </body>
14
  </html>
15
-
 
5
  <title>Py-Detect</title>
6
  <base href="/">
7
  <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <link rel="icon" type="image/x-icon" href="favicon.ico">
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
10
+ <link rel="preconnect" href="https://fonts.googleapis.com">
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
13
  </head>
14
  <body>
15
  <app-root></app-root>
16
  </body>
17
  </html>
 
src/pykara-favicon.ico DELETED
Binary file (791 Bytes)