RajalashmiNagarajan commited on
Commit
ed79486
·
1 Parent(s): e9a0b07
Py-Detect.esproj.user CHANGED
@@ -2,7 +2,12 @@
2
  <Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
4
  <DebuggerFlavor>LaunchJsonDebugger</DebuggerFlavor>
5
- <LaunchJsonTarget>
6
- </LaunchJsonTarget>
 
 
 
 
 
7
  </PropertyGroup>
8
  </Project>
 
2
  <Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
4
  <DebuggerFlavor>LaunchJsonDebugger</DebuggerFlavor>
5
+ <LaunchJsonTarget>{
6
+ "name": "localhost (Chrome)",
7
+ "type": "chrome",
8
+ "request": "launch",
9
+ "url": "http://localhost:51272",
10
+ "webRoot": "E:\\Py-detect_wrk\\Py-detect"
11
+ }</LaunchJsonTarget>
12
  </PropertyGroup>
13
  </Project>
obj/Debug/Py-Detect.esproj.FileListAbsolute.txt CHANGED
@@ -1,2 +1,3 @@
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
 
 
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
3
+ E:\Py-detect_wrk\Py-detect\obj\Debug\Py-Detect.esproj.CoreCompileInputs.cache
obj/Debug/package.g.props CHANGED
@@ -19,15 +19,19 @@
19
  <PackageJsonDependenciesAngularPlatformBrowser Condition="$(PackageJsonDependenciesAngularPlatformBrowser) == ''">^16.1.0</PackageJsonDependenciesAngularPlatformBrowser>
20
  <PackageJsonDependenciesAngularPlatformBrowserDynamic Condition="$(PackageJsonDependenciesAngularPlatformBrowserDynamic) == ''">^16.1.0</PackageJsonDependenciesAngularPlatformBrowserDynamic>
21
  <PackageJsonDependenciesAngularRouter Condition="$(PackageJsonDependenciesAngularRouter) == ''">^16.1.0</PackageJsonDependenciesAngularRouter>
 
22
  <PackageJsonDependenciesForce Condition="$(PackageJsonDependenciesForce) == ''">^0.0.3</PackageJsonDependenciesForce>
23
  <PackageJsonDependenciesJestEditorSupport Condition="$(PackageJsonDependenciesJestEditorSupport) == ''">*</PackageJsonDependenciesJestEditorSupport>
24
  <PackageJsonDependenciesRxjs Condition="$(PackageJsonDependenciesRxjs) == ''">~7.8.0</PackageJsonDependenciesRxjs>
25
  <PackageJsonDependenciesTslib Condition="$(PackageJsonDependenciesTslib) == ''">^2.3.0</PackageJsonDependenciesTslib>
 
26
  <PackageJsonDependenciesZoneJs Condition="$(PackageJsonDependenciesZoneJs) == ''">~0.13.0</PackageJsonDependenciesZoneJs>
27
  <PackageJsonDevdependenciesAngularDevkitBuildAngular Condition="$(PackageJsonDevdependenciesAngularDevkitBuildAngular) == ''">^16.1.0</PackageJsonDevdependenciesAngularDevkitBuildAngular>
28
  <PackageJsonDevdependenciesAngularCli Condition="$(PackageJsonDevdependenciesAngularCli) == ''">~16.1.0</PackageJsonDevdependenciesAngularCli>
29
  <PackageJsonDevdependenciesAngularCompilerCli Condition="$(PackageJsonDevdependenciesAngularCompilerCli) == ''">^16.1.0</PackageJsonDevdependenciesAngularCompilerCli>
 
30
  <PackageJsonDevdependenciesTypesJasmine Condition="$(PackageJsonDevdependenciesTypesJasmine) == ''">~4.3.0</PackageJsonDevdependenciesTypesJasmine>
 
31
  <PackageJsonDevdependenciesAutoprefixer Condition="$(PackageJsonDevdependenciesAutoprefixer) == ''">^10.4.21</PackageJsonDevdependenciesAutoprefixer>
32
  <PackageJsonDevdependenciesJasmineCore Condition="$(PackageJsonDevdependenciesJasmineCore) == ''">~4.6.0</PackageJsonDevdependenciesJasmineCore>
33
  <PackageJsonDevdependenciesKarma Condition="$(PackageJsonDevdependenciesKarma) == ''">~6.4.0</PackageJsonDevdependenciesKarma>
 
19
  <PackageJsonDependenciesAngularPlatformBrowser Condition="$(PackageJsonDependenciesAngularPlatformBrowser) == ''">^16.1.0</PackageJsonDependenciesAngularPlatformBrowser>
20
  <PackageJsonDependenciesAngularPlatformBrowserDynamic Condition="$(PackageJsonDependenciesAngularPlatformBrowserDynamic) == ''">^16.1.0</PackageJsonDependenciesAngularPlatformBrowserDynamic>
21
  <PackageJsonDependenciesAngularRouter Condition="$(PackageJsonDependenciesAngularRouter) == ''">^16.1.0</PackageJsonDependenciesAngularRouter>
22
+ <PackageJsonDependenciesFileSaver Condition="$(PackageJsonDependenciesFileSaver) == ''">^2.0.5</PackageJsonDependenciesFileSaver>
23
  <PackageJsonDependenciesForce Condition="$(PackageJsonDependenciesForce) == ''">^0.0.3</PackageJsonDependenciesForce>
24
  <PackageJsonDependenciesJestEditorSupport Condition="$(PackageJsonDependenciesJestEditorSupport) == ''">*</PackageJsonDependenciesJestEditorSupport>
25
  <PackageJsonDependenciesRxjs Condition="$(PackageJsonDependenciesRxjs) == ''">~7.8.0</PackageJsonDependenciesRxjs>
26
  <PackageJsonDependenciesTslib Condition="$(PackageJsonDependenciesTslib) == ''">^2.3.0</PackageJsonDependenciesTslib>
27
+ <PackageJsonDependenciesXlsx Condition="$(PackageJsonDependenciesXlsx) == ''">^0.18.5</PackageJsonDependenciesXlsx>
28
  <PackageJsonDependenciesZoneJs Condition="$(PackageJsonDependenciesZoneJs) == ''">~0.13.0</PackageJsonDependenciesZoneJs>
29
  <PackageJsonDevdependenciesAngularDevkitBuildAngular Condition="$(PackageJsonDevdependenciesAngularDevkitBuildAngular) == ''">^16.1.0</PackageJsonDevdependenciesAngularDevkitBuildAngular>
30
  <PackageJsonDevdependenciesAngularCli Condition="$(PackageJsonDevdependenciesAngularCli) == ''">~16.1.0</PackageJsonDevdependenciesAngularCli>
31
  <PackageJsonDevdependenciesAngularCompilerCli Condition="$(PackageJsonDevdependenciesAngularCompilerCli) == ''">^16.1.0</PackageJsonDevdependenciesAngularCompilerCli>
32
+ <PackageJsonDevdependenciesTypesFileSaver Condition="$(PackageJsonDevdependenciesTypesFileSaver) == ''">^2.0.7</PackageJsonDevdependenciesTypesFileSaver>
33
  <PackageJsonDevdependenciesTypesJasmine Condition="$(PackageJsonDevdependenciesTypesJasmine) == ''">~4.3.0</PackageJsonDevdependenciesTypesJasmine>
34
+ <PackageJsonDevdependenciesTypesXlsx Condition="$(PackageJsonDevdependenciesTypesXlsx) == ''">^0.0.35</PackageJsonDevdependenciesTypesXlsx>
35
  <PackageJsonDevdependenciesAutoprefixer Condition="$(PackageJsonDevdependenciesAutoprefixer) == ''">^10.4.21</PackageJsonDevdependenciesAutoprefixer>
36
  <PackageJsonDevdependenciesJasmineCore Condition="$(PackageJsonDevdependenciesJasmineCore) == ''">~4.6.0</PackageJsonDevdependenciesJasmineCore>
37
  <PackageJsonDevdependenciesKarma Condition="$(PackageJsonDevdependenciesKarma) == ''">~6.4.0</PackageJsonDevdependenciesKarma>
package-lock.json CHANGED
@@ -270,7 +270,6 @@
270
  }
271
  ],
272
  "license": "MIT",
273
- "peer": true,
274
  "dependencies": {
275
  "nanoid": "^3.3.6",
276
  "picocolors": "^1.0.0",
@@ -429,7 +428,6 @@
429
  "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.12.tgz",
430
  "integrity": "sha512-MD0ElviEfAJY8qMOd6/jjSSvtqER2RDAi0lxe6EtUacC1DHCYkaPrKW4vLqY+tmZBg1yf+6n+uS77pXcHHcA3w==",
431
  "license": "MIT",
432
- "peer": true,
433
  "dependencies": {
434
  "tslib": "^2.3.0"
435
  },
@@ -604,7 +602,6 @@
604
  "version": "16.2.12",
605
  "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.12.tgz",
606
  "integrity": "sha512-B+WY/cT2VgEaz9HfJitBmgdk4I333XG/ybC98CMC4Wz8E49T8yzivmmxXB3OD6qvjcOB6ftuicl6WBqLbZNg2w==",
607
- "peer": true,
608
  "dependencies": {
609
  "tslib": "^2.3.0"
610
  },
@@ -620,7 +617,6 @@
620
  "version": "16.2.12",
621
  "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.12.tgz",
622
  "integrity": "sha512-6SMXUgSVekGM7R6l1Z9rCtUGtlg58GFmgbpMCsGf+VXxP468Njw8rjT2YZkf5aEPxEuRpSHhDYjqz7n14cwCXQ==",
623
- "peer": true,
624
  "dependencies": {
625
  "tslib": "^2.3.0"
626
  },
@@ -641,7 +637,6 @@
641
  "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.12.tgz",
642
  "integrity": "sha512-pWSrr152562ujh6lsFZR8NfNc5Ljj+zSTQO44DsuB0tZjwEpnRcjJEgzuhGXr+CoiBf+jTSPZKemtSktDk5aaA==",
643
  "dev": true,
644
- "peer": true,
645
  "dependencies": {
646
  "@babel/core": "7.23.2",
647
  "@jridgewell/sourcemap-codec": "^1.4.14",
@@ -756,7 +751,6 @@
756
  "version": "16.2.12",
757
  "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.12.tgz",
758
  "integrity": "sha512-GLLlDeke/NjroaLYOks0uyzFVo6HyLl7VOm0K1QpLXnYvW63W9Ql/T3yguRZa7tRkOAeFZ3jw+1wnBD4O8MoUA==",
759
- "peer": true,
760
  "dependencies": {
761
  "tslib": "^2.3.0"
762
  },
@@ -772,7 +766,6 @@
772
  "version": "16.2.12",
773
  "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.12.tgz",
774
  "integrity": "sha512-1Eao89hlBgLR3v8tU91vccn21BBKL06WWxl7zLpQmG6Hun+2jrThgOE4Pf3os4fkkbH4Apj0tWL2fNIWe/blbw==",
775
- "peer": true,
776
  "dependencies": {
777
  "tslib": "^2.3.0"
778
  },
@@ -855,7 +848,6 @@
855
  "version": "16.2.12",
856
  "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.12.tgz",
857
  "integrity": "sha512-NnH7ju1iirmVEsUq432DTm0nZBGQsBrU40M3ZeVHMQ2subnGiyUs3QyzDz8+VWLL/T5xTxWLt9BkDn65vgzlIQ==",
858
- "peer": true,
859
  "dependencies": {
860
  "tslib": "^2.3.0"
861
  },
@@ -938,7 +930,6 @@
938
  "version": "7.22.9",
939
  "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz",
940
  "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==",
941
- "peer": true,
942
  "dependencies": {
943
  "@ampproject/remapping": "^2.2.0",
944
  "@babel/code-frame": "^7.22.5",
@@ -5501,7 +5492,6 @@
5501
  "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
5502
  "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
5503
  "dev": true,
5504
- "peer": true,
5505
  "bin": {
5506
  "acorn": "bin/acorn"
5507
  },
@@ -5627,7 +5617,6 @@
5627
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
5628
  "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
5629
  "dev": true,
5630
- "peer": true,
5631
  "dependencies": {
5632
  "fast-deep-equal": "^3.1.1",
5633
  "json-schema-traverse": "^1.0.0",
@@ -6181,7 +6170,6 @@
6181
  "url": "https://github.com/sponsors/ai"
6182
  }
6183
  ],
6184
- "peer": true,
6185
  "dependencies": {
6186
  "caniuse-lite": "^1.0.30001737",
6187
  "electron-to-chromium": "^1.5.211",
@@ -6453,7 +6441,6 @@
6453
  "url": "https://paulmillr.com/funding/"
6454
  }
6455
  ],
6456
- "peer": true,
6457
  "dependencies": {
6458
  "anymatch": "~3.1.2",
6459
  "braces": "~3.0.2",
@@ -9626,8 +9613,7 @@
9626
  "version": "4.6.1",
9627
  "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz",
9628
  "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==",
9629
- "dev": true,
9630
- "peer": true
9631
  },
9632
  "node_modules/jest-diff": {
9633
  "version": "30.1.2",
@@ -10551,7 +10537,6 @@
10551
  "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz",
10552
  "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
10553
  "dev": true,
10554
- "peer": true,
10555
  "dependencies": {
10556
  "@colors/colors": "1.5.0",
10557
  "body-parser": "^1.19.0",
@@ -10616,7 +10601,6 @@
10616
  "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz",
10617
  "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==",
10618
  "dev": true,
10619
- "peer": true,
10620
  "dependencies": {
10621
  "jasmine-core": "^4.1.0"
10622
  },
@@ -10736,7 +10720,6 @@
10736
  "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz",
10737
  "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==",
10738
  "dev": true,
10739
- "peer": true,
10740
  "dependencies": {
10741
  "copy-anything": "^2.0.1",
10742
  "parse-node-version": "^1.0.1",
@@ -12684,7 +12667,6 @@
12684
  }
12685
  ],
12686
  "license": "MIT",
12687
- "peer": true,
12688
  "dependencies": {
12689
  "nanoid": "^3.3.11",
12690
  "picocolors": "^1.1.1",
@@ -13630,7 +13612,6 @@
13630
  "version": "7.8.2",
13631
  "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
13632
  "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
13633
- "peer": true,
13634
  "dependencies": {
13635
  "tslib": "^2.1.0"
13636
  }
@@ -13687,7 +13668,6 @@
13687
  "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz",
13688
  "integrity": "sha512-16rRACSOFEE8VN7SCgBu1MpYCyN7urj9At898tyzdXFhC+a+yOX5dXwAR7L8/IdPJ1NB8OYoXmD55DM30B2kEQ==",
13689
  "dev": true,
13690
- "peer": true,
13691
  "dependencies": {
13692
  "chokidar": ">=3.0.0 <4.0.0",
13693
  "immutable": "^4.0.0",
@@ -14858,7 +14838,6 @@
14858
  "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
14859
  "dev": true,
14860
  "license": "MIT",
14861
- "peer": true,
14862
  "dependencies": {
14863
  "@alloc/quick-lru": "^5.2.0",
14864
  "arg": "^5.0.2",
@@ -15084,7 +15063,6 @@
15084
  "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz",
15085
  "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==",
15086
  "dev": true,
15087
- "peer": true,
15088
  "dependencies": {
15089
  "@jridgewell/source-map": "^0.3.3",
15090
  "acorn": "^8.8.2",
@@ -15497,7 +15475,6 @@
15497
  "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
15498
  "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
15499
  "dev": true,
15500
- "peer": true,
15501
  "bin": {
15502
  "tsc": "bin/tsc",
15503
  "tsserver": "bin/tsserver"
@@ -15751,7 +15728,6 @@
15751
  "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz",
15752
  "integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==",
15753
  "dev": true,
15754
- "peer": true,
15755
  "dependencies": {
15756
  "esbuild": "^0.18.10",
15757
  "postcss": "^8.4.27",
@@ -15886,7 +15862,6 @@
15886
  "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
15887
  "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
15888
  "dev": true,
15889
- "peer": true,
15890
  "dependencies": {
15891
  "@types/estree": "^1.0.5",
15892
  "@webassemblyjs/ast": "^1.12.1",
@@ -15961,7 +15936,6 @@
15961
  "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz",
15962
  "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==",
15963
  "dev": true,
15964
- "peer": true,
15965
  "dependencies": {
15966
  "@types/bonjour": "^3.5.9",
15967
  "@types/connect-history-api-fallback": "^1.3.5",
@@ -16108,7 +16082,6 @@
16108
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
16109
  "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
16110
  "dev": true,
16111
- "peer": true,
16112
  "dependencies": {
16113
  "fast-deep-equal": "^3.1.1",
16114
  "fast-json-stable-stringify": "^2.0.0",
@@ -16435,7 +16408,6 @@
16435
  "version": "0.13.3",
16436
  "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.13.3.tgz",
16437
  "integrity": "sha512-MKPbmZie6fASC/ps4dkmIhaT5eonHkEt6eAy80K42tAm0G2W+AahLJjbfi6X9NPdciOE9GRFTTM8u2IiF6O3ww==",
16438
- "peer": true,
16439
  "dependencies": {
16440
  "tslib": "^2.3.0"
16441
  }
 
270
  }
271
  ],
272
  "license": "MIT",
 
273
  "dependencies": {
274
  "nanoid": "^3.3.6",
275
  "picocolors": "^1.0.0",
 
428
  "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.12.tgz",
429
  "integrity": "sha512-MD0ElviEfAJY8qMOd6/jjSSvtqER2RDAi0lxe6EtUacC1DHCYkaPrKW4vLqY+tmZBg1yf+6n+uS77pXcHHcA3w==",
430
  "license": "MIT",
 
431
  "dependencies": {
432
  "tslib": "^2.3.0"
433
  },
 
602
  "version": "16.2.12",
603
  "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.12.tgz",
604
  "integrity": "sha512-B+WY/cT2VgEaz9HfJitBmgdk4I333XG/ybC98CMC4Wz8E49T8yzivmmxXB3OD6qvjcOB6ftuicl6WBqLbZNg2w==",
 
605
  "dependencies": {
606
  "tslib": "^2.3.0"
607
  },
 
617
  "version": "16.2.12",
618
  "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.12.tgz",
619
  "integrity": "sha512-6SMXUgSVekGM7R6l1Z9rCtUGtlg58GFmgbpMCsGf+VXxP468Njw8rjT2YZkf5aEPxEuRpSHhDYjqz7n14cwCXQ==",
 
620
  "dependencies": {
621
  "tslib": "^2.3.0"
622
  },
 
637
  "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.12.tgz",
638
  "integrity": "sha512-pWSrr152562ujh6lsFZR8NfNc5Ljj+zSTQO44DsuB0tZjwEpnRcjJEgzuhGXr+CoiBf+jTSPZKemtSktDk5aaA==",
639
  "dev": true,
 
640
  "dependencies": {
641
  "@babel/core": "7.23.2",
642
  "@jridgewell/sourcemap-codec": "^1.4.14",
 
751
  "version": "16.2.12",
752
  "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.12.tgz",
753
  "integrity": "sha512-GLLlDeke/NjroaLYOks0uyzFVo6HyLl7VOm0K1QpLXnYvW63W9Ql/T3yguRZa7tRkOAeFZ3jw+1wnBD4O8MoUA==",
 
754
  "dependencies": {
755
  "tslib": "^2.3.0"
756
  },
 
766
  "version": "16.2.12",
767
  "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.12.tgz",
768
  "integrity": "sha512-1Eao89hlBgLR3v8tU91vccn21BBKL06WWxl7zLpQmG6Hun+2jrThgOE4Pf3os4fkkbH4Apj0tWL2fNIWe/blbw==",
 
769
  "dependencies": {
770
  "tslib": "^2.3.0"
771
  },
 
848
  "version": "16.2.12",
849
  "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.12.tgz",
850
  "integrity": "sha512-NnH7ju1iirmVEsUq432DTm0nZBGQsBrU40M3ZeVHMQ2subnGiyUs3QyzDz8+VWLL/T5xTxWLt9BkDn65vgzlIQ==",
 
851
  "dependencies": {
852
  "tslib": "^2.3.0"
853
  },
 
930
  "version": "7.22.9",
931
  "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz",
932
  "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==",
 
933
  "dependencies": {
934
  "@ampproject/remapping": "^2.2.0",
935
  "@babel/code-frame": "^7.22.5",
 
5492
  "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
5493
  "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
5494
  "dev": true,
 
5495
  "bin": {
5496
  "acorn": "bin/acorn"
5497
  },
 
5617
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
5618
  "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
5619
  "dev": true,
 
5620
  "dependencies": {
5621
  "fast-deep-equal": "^3.1.1",
5622
  "json-schema-traverse": "^1.0.0",
 
6170
  "url": "https://github.com/sponsors/ai"
6171
  }
6172
  ],
 
6173
  "dependencies": {
6174
  "caniuse-lite": "^1.0.30001737",
6175
  "electron-to-chromium": "^1.5.211",
 
6441
  "url": "https://paulmillr.com/funding/"
6442
  }
6443
  ],
 
6444
  "dependencies": {
6445
  "anymatch": "~3.1.2",
6446
  "braces": "~3.0.2",
 
9613
  "version": "4.6.1",
9614
  "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz",
9615
  "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==",
9616
+ "dev": true
 
9617
  },
9618
  "node_modules/jest-diff": {
9619
  "version": "30.1.2",
 
10537
  "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz",
10538
  "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
10539
  "dev": true,
 
10540
  "dependencies": {
10541
  "@colors/colors": "1.5.0",
10542
  "body-parser": "^1.19.0",
 
10601
  "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz",
10602
  "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==",
10603
  "dev": true,
 
10604
  "dependencies": {
10605
  "jasmine-core": "^4.1.0"
10606
  },
 
10720
  "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz",
10721
  "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==",
10722
  "dev": true,
 
10723
  "dependencies": {
10724
  "copy-anything": "^2.0.1",
10725
  "parse-node-version": "^1.0.1",
 
12667
  }
12668
  ],
12669
  "license": "MIT",
 
12670
  "dependencies": {
12671
  "nanoid": "^3.3.11",
12672
  "picocolors": "^1.1.1",
 
13612
  "version": "7.8.2",
13613
  "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
13614
  "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
 
13615
  "dependencies": {
13616
  "tslib": "^2.1.0"
13617
  }
 
13668
  "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz",
13669
  "integrity": "sha512-16rRACSOFEE8VN7SCgBu1MpYCyN7urj9At898tyzdXFhC+a+yOX5dXwAR7L8/IdPJ1NB8OYoXmD55DM30B2kEQ==",
13670
  "dev": true,
 
13671
  "dependencies": {
13672
  "chokidar": ">=3.0.0 <4.0.0",
13673
  "immutable": "^4.0.0",
 
14838
  "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
14839
  "dev": true,
14840
  "license": "MIT",
 
14841
  "dependencies": {
14842
  "@alloc/quick-lru": "^5.2.0",
14843
  "arg": "^5.0.2",
 
15063
  "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz",
15064
  "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==",
15065
  "dev": true,
 
15066
  "dependencies": {
15067
  "@jridgewell/source-map": "^0.3.3",
15068
  "acorn": "^8.8.2",
 
15475
  "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
15476
  "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
15477
  "dev": true,
 
15478
  "bin": {
15479
  "tsc": "bin/tsc",
15480
  "tsserver": "bin/tsserver"
 
15728
  "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz",
15729
  "integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==",
15730
  "dev": true,
 
15731
  "dependencies": {
15732
  "esbuild": "^0.18.10",
15733
  "postcss": "^8.4.27",
 
15862
  "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
15863
  "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
15864
  "dev": true,
 
15865
  "dependencies": {
15866
  "@types/estree": "^1.0.5",
15867
  "@webassemblyjs/ast": "^1.12.1",
 
15936
  "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz",
15937
  "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==",
15938
  "dev": true,
 
15939
  "dependencies": {
15940
  "@types/bonjour": "^3.5.9",
15941
  "@types/connect-history-api-fallback": "^1.3.5",
 
16082
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
16083
  "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
16084
  "dev": true,
 
16085
  "dependencies": {
16086
  "fast-deep-equal": "^3.1.1",
16087
  "fast-json-stable-stringify": "^2.0.0",
 
16408
  "version": "0.13.3",
16409
  "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.13.3.tgz",
16410
  "integrity": "sha512-MKPbmZie6fASC/ps4dkmIhaT5eonHkEt6eAy80K42tAm0G2W+AahLJjbfi6X9NPdciOE9GRFTTM8u2IiF6O3ww==",
 
16411
  "dependencies": {
16412
  "tslib": "^2.3.0"
16413
  }
src.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1eb45ebf23c16a409ed8f287fdfefe3435482492afdf4dcd2b6d1632174437f8
3
+ size 34011524
src/app/app-routing.module.ts CHANGED
@@ -38,6 +38,9 @@ const routes: Routes = [
38
  path: 'auth/signup', loadComponent: () =>
39
  import('./homepage/sign-up/sign-up.component').then(m => m.SignUpComponent)
40
  },
 
 
 
41
 
42
  { path: '**', redirectTo: '' }
43
  ];
 
38
  path: 'auth/signup', loadComponent: () =>
39
  import('./homepage/sign-up/sign-up.component').then(m => m.SignUpComponent)
40
  },
41
+ // Terms & Privacy
42
+ { path: 'legal/terms', loadComponent: () => import('./legal/terms.component').then(m => m.TermsComponent) },
43
+ { path: 'legal/privacy', loadComponent: () => import('./legal/privacy.component').then(m => m.PrivacyComponent) },
44
 
45
  { path: '**', redirectTo: '' }
46
  ];
src/app/app.component.css CHANGED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ /* Placeholder styles for AppComponent to fix missing file error */
2
+ :host {
3
+ display: block;
4
+ }
src/app/homepage/auth-card/auth-card.component.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Component, Input, Output, EventEmitter } from '@angular/core';
2
  import { CommonModule } from '@angular/common';
3
 
4
  @Component({
@@ -12,6 +12,10 @@ export class AuthCardComponent {
12
  @Input() isFlipped = false;
13
  @Output() flip = new EventEmitter<boolean>();
14
 
 
 
 
 
15
  toggleFlip() {
16
  this.isFlipped = !this.isFlipped;
17
  this.flip.emit(this.isFlipped);
 
1
+ import { Component, Input, Output, EventEmitter, ContentChild, TemplateRef } from '@angular/core';
2
  import { CommonModule } from '@angular/common';
3
 
4
  @Component({
 
12
  @Input() isFlipped = false;
13
  @Output() flip = new EventEmitter<boolean>();
14
 
15
+ // Nullable TemplateRef properties for ngTemplateOutlet
16
+ @ContentChild('signInTemplate', { read: TemplateRef }) signInTemplate: TemplateRef<any> | null = null;
17
+ @ContentChild('signUpTemplate', { read: TemplateRef }) signUpTemplate: TemplateRef<any> | null = null;
18
+
19
  toggleFlip() {
20
  this.isFlipped = !this.isFlipped;
21
  this.flip.emit(this.isFlipped);
src/app/homepage/auth-wrapper.component.ts CHANGED
@@ -1,8 +1,13 @@
1
  import { Component } from '@angular/core';
2
  import { trigger, state, style, animate, transition } from '@angular/animations';
 
 
 
3
 
4
  @Component({
5
  selector: 'app-auth-wrapper',
 
 
6
  template: `
7
  <div class="card-swipe-container" [@cardSwipe]="cardState">
8
  <app-sign-in
 
1
  import { Component } from '@angular/core';
2
  import { trigger, state, style, animate, transition } from '@angular/animations';
3
+ import { CommonModule } from '@angular/common';
4
+ import { SignInComponent } from './sign-in/sign-in.component';
5
+ import { SignUpComponent } from './sign-up/sign-up.component';
6
 
7
  @Component({
8
  selector: 'app-auth-wrapper',
9
+ standalone: true,
10
+ imports: [CommonModule, SignInComponent, SignUpComponent],
11
  template: `
12
  <div class="card-swipe-container" [@cardSwipe]="cardState">
13
  <app-sign-in
src/app/homepage/homepage.component.css CHANGED
@@ -889,41 +889,67 @@ footer .social-icons .si.ig:hover { filter:brightness(1.15); color:#fff; box-sha
889
  display: flex;
890
  justify-content: space-between;
891
  flex-wrap: wrap;
892
- gap: 25px;
893
  }
894
 
895
  .use-card {
896
  background: #fff;
897
  color: #000;
898
- flex: 1 1 calc(20% - 20px); /* 5 cards in a row */
899
- min-width: 220px;
900
  border-radius: 12px;
901
- padding: 25px 20px;
902
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
903
  transition: transform 0.3s;
904
  }
905
 
906
- .use-card:hover {
907
- transform: translateY(-6px);
908
- }
 
 
 
 
909
 
910
- .use-card .icon {
911
- font-size: 40px;
912
- margin-bottom: 15px;
913
- color: #3328c6; /* red accent */
914
- }
915
 
916
- .use-card h3 {
917
- font-size: 18px;
918
- margin-bottom: 10px;
919
- color: #3328c6;
920
- }
921
 
922
- .use-card p {
923
- font-size: 16px;
924
- line-height: 1.5;
925
- color: #000;
926
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
927
 
928
  .social-icons {
929
  display: flex;
@@ -1353,11 +1379,49 @@ footer .social-icons {
1353
  color: #0d9de3;
1354
  }
1355
 
1356
- @media (max-width:768px) {
1357
- .how-it-works::before { height:320px; border-radius:18px; }
1358
- .how-card { max-height:360px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1359
  }
1360
 
 
 
 
 
 
 
1361
 
1362
 
1363
 
 
889
  display: flex;
890
  justify-content: space-between;
891
  flex-wrap: wrap;
892
+ gap: 30px;
893
  }
894
 
895
  .use-card {
896
  background: #fff;
897
  color: #000;
898
+ flex: 11 calc(25% - 30px);
899
+ min-width: 260px;
900
  border-radius: 12px;
901
+ padding: 28px 24px;
902
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
903
  transition: transform 0.3s;
904
  }
905
 
906
+ /* Make the 5th card span entire row */
907
+ .use-card:nth-child(5) {
908
+ flex: 11 100%;
909
+ min-width: 100%;
910
+ text-align: center;
911
+ padding: 36px 32px;
912
+ }
913
 
914
+ .use-card:nth-child(5) .icon {
915
+ font-size: 46px;
916
+ color: #5a35d6;
917
+ }
 
918
 
919
+ .use-card:nth-child(5) h3 {
920
+ font-size: 22px;
921
+ color: #5a35d6;
922
+ margin-top: 6px;
923
+ }
924
 
925
+ .use-card:nth-child(5) p {
926
+ max-width: 1100px;
927
+ margin: 10px auto 0;
928
+ }
929
+
930
+ /* Keep icon/title styling for first row */
931
+ .use-card .icon {
932
+ font-size: 40px;
933
+ margin-bottom: 15px;
934
+ color: #3328c6;
935
+ }
936
+
937
+ .use-card h3 {
938
+ font-size: 18px;
939
+ margin-bottom: 10px;
940
+ color: #3328c6;
941
+ }
942
+
943
+ .use-card p {
944
+ font-size: 16px;
945
+ line-height: 1.5;
946
+ color: #000;
947
+ }
948
+
949
+ /* Hover lift */
950
+ .use-card:hover {
951
+ transform: translateY(-6px);
952
+ }
953
 
954
  .social-icons {
955
  display: flex;
 
1379
  color: #0d9de3;
1380
  }
1381
 
1382
+ /* How It Works: blue background card and2x3 grid */
1383
+ .how-wrapper { position: relative; max-width:1200px; margin:0 auto; padding:12px 12px 24px; }
1384
+ .how-bg {
1385
+ position:absolute; inset:0; margin:auto; border-radius:12px;
1386
+ background: linear-gradient(135deg, #0b3a6b0%, #0d5aa8100%);
1387
+ opacity:0.22; z-index:0; box-shadow:0 12px 40px rgba(0,0,0,.25);
1388
+ }
1389
+ .how-tiles { position:relative; z-index:1; display:grid; grid-template-columns: repeat(3,1fr); gap:22px; }
1390
+ .how-tile { background:#fff; color:#222; border-radius:8px; box-shadow:0 8px 22px rgba(0,0,0,.06); border:1px solid rgba(0,0,0,.06); overflow:hidden; padding-bottom:6px; }
1391
+ .how-tile:hover { transform: translateY(-3px); box-shadow:0 12px 26px rgba(0,0,0,.12); transition:transform .2s, box-shadow .2s; }
1392
+ .how-illustration {
1393
+ background: linear-gradient(180deg,#fcf7ea,#3aa6d9);
1394
+ height: 160px;
1395
+ display: flex;
1396
+ align-items: center;
1397
+ justify-content: center;
1398
+ }
1399
+ .how-illustration i { font-size:62px; }
1400
+ .how-tile-title {
1401
+ text-align: center;
1402
+ margin: 14px 6px 0;
1403
+ color: #3328c6;
1404
+ font-weight: 800;
1405
+ }
1406
+ .how-divider { height:3px; width:72%; margin:8px auto 8px; border-bottom:2px dotted #f59e9e; }
1407
+ .how-tile p, .how-tile ul { padding:0 18px 12px; margin:0; color:#333; }
1408
+ .how-tile ul { padding-left:34px; }
1409
+
1410
+ @media (max-width:1024px) { .how-tiles { grid-template-columns: repeat(2,1fr); } }
1411
+ @media (max-width:640px) { .how-tiles { grid-template-columns:1fr; } .how-illustration { height:130px; } }
1412
+
1413
+ /* Blur homepage content when auth modal is open */
1414
+ :host-context(.modal-open) .homepage-root {
1415
+ filter: blur(4px);
1416
+ transition: filter 160ms ease;
1417
  }
1418
 
1419
+ /* Ensure modal backdrop stays transparent to show blurred homepage */
1420
+ .modal-backdrop {
1421
+ background: rgba(0,0,0,0.2);
1422
+ }
1423
+
1424
+
1425
 
1426
 
1427
 
src/app/homepage/homepage.component.html CHANGED
@@ -1,3 +1,4 @@
 
1
  <!-- Modern UI header with logo and PyDetect title -->
2
  <div class="site-header">
3
  <div class="header-inner">
@@ -99,12 +100,12 @@
99
  <h2 class="section-title">OUR MISSION IS SIMPLE & IMPACTFUL</h2>
100
  <div class="mission-row">
101
  <div class="mission-left">
102
- <p>
103
  Our mission is to help investigators, recruiters, and security professionals identify truth more confidently, reduce deception, and improve decision-making. We aim to support safer communities, fairer recruitment practices, and stronger organisational security.
104
  </p>
105
  </div>
106
  <div class="mission-right">
107
- <p>
108
  Through advanced behavioural analysis—examining verbal responses, facial cues, and body language—the platform provides clear insight into consistency, credibility, and potential risks. This enables teams to act with accuracy, clarity, and confidence across a wide range of situations.
109
  </p>
110
  </div>
@@ -198,116 +199,83 @@
198
  </div>
199
  </section>
200
 
201
- <!-- How It Works Section (moved to main page) -->
202
- <section id="how-it-works" class="how-it-works how-expanded">
203
  <h2 class="section-title how-title">How It Works</h2>
204
- <div class="how-grid">
205
- <!-- Intro card -->
206
- <article class="how-card how-anim fade-in-up" role="article" aria-label="Intro">
207
- <h3 class="how-heading">Py-Detect Intelligent Behaviour Analysis Platform</h3>
208
- <p class="how-text">
209
- Py-Detect is an advanced digital platform designed to support investigations through structured questioning and automated behavioural analysis. It studies voice, face, and body-language patterns, and produces a clear summary that helps investigators make informed decisions.
 
 
 
 
210
  </p>
211
- </article>
212
-
213
- <!-- Context-Aware Questioning -->
214
- <article class="how-card how-anim fade-in-up" aria-label="Context-Aware Questioning">
215
- <h3 class="how-heading">1. Context-Aware Questioning</h3>
216
- <p class="how-text">The system generates questions by reading the case information entered by the admin. It adapts each follow-up question based on the accused person’s previous answers to maintain relevance and continuity.</p>
217
- </article>
218
 
219
- <!-- Voice Pattern Analysis -->
220
- <article class="how-card how-anim fade-in-up" aria-label="Voice Pattern Analysis">
221
- <h3 class="how-heading">2. Voice Pattern Analysis</h3>
222
- <ul class="how-list">
223
- <li>Tone variations</li>
224
- <li>Stress or pressure points</li>
225
- <li>Pace and hesitation</li>
 
226
  <li>Sudden pitch changes</li>
227
  </ul>
228
- <p class="how-text">These cues help identify emotional states and potential inconsistency.</p>
229
- </article>
230
-
231
- <!-- Facial and Micro-Expression Tracking -->
232
- <article class="how-card how-anim fade-in-up" aria-label="Facial and Micro-Expression Tracking">
233
- <h3 class="how-heading">3. Facial and Micro-Expression Tracking</h3>
234
- <ul class="how-list">
235
- <li>Eye shifts</li>
236
- <li>Lip compression</li>
237
- <li>Brow tension</li>
238
- <li>Nervous micro-gestures</li>
 
239
  </ul>
240
- <p class="how-text">These signals contribute to behavioural interpretation.</p>
241
- </article>
242
-
243
- <!-- Body-Language Pattern Detection -->
244
- <article class="how-card how-anim fade-in-up" aria-label="Body-Language Pattern Detection">
245
- <h3 class="how-heading">4. Body-Language Pattern Detection</h3>
246
- <p class="how-text">Posture, hand movements, head position, and response timing are evaluated to understand engagement, confidence, and anxiety levels.</p>
247
- </article>
248
-
249
- <!-- Combined Insight Summary -->
250
- <article class="how-card how-anim fade-in-up" aria-label="Combined Insight Summary">
251
- <h3 class="how-heading">5. Combined Insight Summary</h3>
252
- <ul class="how-list">
253
- <li>Structured summary</li>
254
- <li>Behavioural indicators</li>
255
- <li>Stress markers</li>
256
- <li>Consistency score</li>
257
- <li>Potential risk highlights</li>
258
- </ul>
259
- <p class="how-text">It helps investigators review the entire interview at a glance.</p>
260
- </article>
261
 
262
- <!-- Who Can Use -->
263
- <article class="how-card how-anim fade-in-up" aria-label="Who Can Use">
264
- <h3 class="how-heading">Who Can Use Py-Detect?</h3>
265
- <ul class="how-list">
266
- <li>Law Enforcement &amp; Investigation Teams</li>
267
- <li>Legal Professionals</li>
268
- <li>HR and Corporate Security</li>
269
- <li>Private Investigation Agencies</li>
270
- <li>Educational or Counselling Teams</li>
271
- </ul>
272
- </article>
273
 
274
- <!-- Why Choose -->
275
- <article class="how-card how-anim fade-in-up" aria-label="Why Choose">
276
- <h3 class="how-heading">Why Choose Py-Detect?</h3>
277
- <ul class="how-list">
278
- <li>Fast and Efficient</li>
279
- <li>Non-Invasive and Comfortable</li>
280
- <li>Reliable and Data-Driven</li>
281
- <li>Configurable for Different Scenarios</li>
282
- <li>Secure and Organised</li>
283
  </ul>
284
- </article>
285
-
286
- <!-- End-to-End Workflow -->
287
- <article class="how-card how-anim fade-in-up" aria-label="End-to-End Workflow">
288
- <h3 class="how-heading">End-to-End Workflow</h3>
289
- <ol class="how-steps">
290
- <li>Admin creates a case.</li>
291
- <li>Investigator receives assigned cases.</li>
292
- <li>Interview recorded (voice and video).</li>
293
- <li>AI analyses behaviour and cues.</li>
294
- <li>Final summary with indicators and scores.</li>
295
- <li>Dashboards for review.</li>
296
- </ol>
297
- </article>
298
 
299
- <!-- Key Features -->
300
- <article class="how-card how-anim fade-in-up" aria-label="Key Features">
301
- <h3 class="how-heading">Key Features</h3>
302
- <ul class="how-list">
303
- <li>Case Management</li>
304
- <li>Automated Question Generation</li>
305
- <li>Voice, Video, and Behaviour Analysis</li>
306
- <li>Truth Probability Score</li>
307
- <li>Investigator Panel</li>
308
- <li>Admin Panel</li>
309
  </ul>
310
- </article>
 
311
  </div>
312
  </section>
313
 
@@ -378,6 +346,7 @@
378
  </a>
379
  </div>
380
  </footer>
 
381
 
382
  <!-- ===== Auth Modals (unchanged) ===== -->
383
  <div class="modal-backdrop" *ngIf="showSignIn || showSignUp" (click)="closeModal()"></div>
 
1
+ <div class="homepage-root">
2
  <!-- Modern UI header with logo and PyDetect title -->
3
  <div class="site-header">
4
  <div class="header-inner">
 
100
  <h2 class="section-title">OUR MISSION IS SIMPLE & IMPACTFUL</h2>
101
  <div class="mission-row">
102
  <div class="mission-left">
103
+ <p>
104
  Our mission is to help investigators, recruiters, and security professionals identify truth more confidently, reduce deception, and improve decision-making. We aim to support safer communities, fairer recruitment practices, and stronger organisational security.
105
  </p>
106
  </div>
107
  <div class="mission-right">
108
+ <p>
109
  Through advanced behavioural analysis—examining verbal responses, facial cues, and body language—the platform provides clear insight into consistency, credibility, and potential risks. This enables teams to act with accuracy, clarity, and confidence across a wide range of situations.
110
  </p>
111
  </div>
 
199
  </div>
200
  </section>
201
 
202
+ <!-- How It Works Section (two-row tiles with blue background) -->
203
+ <section id="how-it-works" class="how-it-works">
204
  <h2 class="section-title how-title">How It Works</h2>
205
+ <div class="how-wrapper">
206
+ <div class="how-bg"></div>
207
+ <div class="how-tiles">
208
+ <!--1: Context-Aware Questioning (includes intro) -->
209
+ <div class="how-tile">
210
+ <div class="how-illustration"><i class="fas fa-comments"></i></div>
211
+ <h3 class="how-tile-title">Context‑Aware Questioning</h3>
212
+ <div class="how-divider"></div>
213
+ <p class="how-tile-text">
214
+ Py‑Detect reads case details and adapts follow‑up questions based on prior answers to keep the interview relevant and continuous.
215
  </p>
216
+ </div>
 
 
 
 
 
 
217
 
218
+ <!--2: Voice Pattern Analysis -->
219
+ <div class="how-tile">
220
+ <div class="how-illustration"><i class="fas fa-microphone-alt"></i></div>
221
+ <h3 class="how-tile-title">Voice Pattern Analysis</h3>
222
+ <div class="how-divider"></div>
223
+ <ul>
224
+ <li>Tone variations, stress points</li>
225
+ <li>Speech pace, hesitations</li>
226
  <li>Sudden pitch changes</li>
227
  </ul>
228
+ <p class="how-tile-text">These cues help identify emotional states and potential inconsistencies.</p>
229
+ </div>
230
+
231
+ <!--3: Facial & MicroExpression Tracking -->
232
+ <div class="how-tile">
233
+ <div class="how-illustration"><i class="fas fa-user-secret"></i></div>
234
+ <h3 class="how-tile-title">Facial & Micro‑Expressions</h3>
235
+ <div class="how-divider"></div>
236
+ <ul>
237
+ <li>Eye shifts and gaze changes</li>
238
+ <li>Lip compression, brow tension</li>
239
+ <li>Nervous micro‑gestures</li>
240
  </ul>
241
+ <p class="how-tile-text">Signals that contribute to behavioural interpretation.</p>
242
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
+ <!--4: Body‑Language Pattern Detection -->
245
+ <div class="how-tile">
246
+ <div class="how-illustration"><i class="fas fa-walking"></i></div>
247
+ <h3 class="how-tile-title">Body‑Language Detection</h3>
248
+ <div class="how-divider"></div>
249
+ <p class="how-tile-text">
250
+ Posture, hand movements, head position, and timing are evaluated to understand engagement, confidence, and anxiety levels.
251
+ </p>
252
+ </div>
 
 
253
 
254
+ <!--5: Combined Insight Summary -->
255
+ <div class="how-tile">
256
+ <div class="how-illustration"><i class="fas fa-clipboard-check"></i></div>
257
+ <h3 class="how-tile-title">Combined Insight Summary</h3>
258
+ <div class="how-divider"></div>
259
+ <ul>
260
+ <li>Behavioural indicators and stress markers</li>
261
+ <li>Consistency score and highlights</li>
262
+ <li>Clear, structured summary for review</li>
263
  </ul>
264
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
265
 
266
+ <!--6: Workflow, Who, Why & Key Features (condensed) -->
267
+ <div class="how-tile">
268
+ <div class="how-illustration"><i class="fas fa-project-diagram"></i></div>
269
+ <h3 class="how-tile-title">Workflow • Users • Benefits</h3>
270
+ <div class="how-divider"></div>
271
+ <ul>
272
+ <li><strong>Workflow:</strong> Admin creates case → Investigator records interview → AI analyses → Summary & dashboards.</li>
273
+ <li><strong>Who:</strong> Law Enforcement, Legal, HR/Security, Private Investigation, Education/Counselling.</li>
274
+ <li><strong>Why:</strong> Fast, non‑invasive, reliable, configurable, secure.</li>
275
+ <li><strong>Key Features:</strong> Case Management, Auto Questioning, Voice/Video/Behaviour Analysis, Truth Score, Admin & Investigator Panels.</li>
276
  </ul>
277
+ </div>
278
+ </div>
279
  </div>
280
  </section>
281
 
 
346
  </a>
347
  </div>
348
  </footer>
349
+ </div>
350
 
351
  <!-- ===== Auth Modals (unchanged) ===== -->
352
  <div class="modal-backdrop" *ngIf="showSignIn || showSignUp" (click)="closeModal()"></div>
src/app/homepage/homepage.component.ts CHANGED
@@ -53,9 +53,9 @@ export class HomepageComponent implements OnInit, OnDestroy {
53
  }
54
 
55
  // === Auth modal open/close (same as your last version in the template) ===
56
- openSignIn() { this.showSignIn = true; this.showSignUp = false; }
57
- openSignUp() { this.showSignUp = true; this.showSignIn = false; }
58
- closeModal() { this.showSignIn = this.showSignUp = false; }
59
 
60
  // If your <app-sign-in> emits signInSuccess, keep the same handler
61
  handleSignInSuccess() {
 
53
  }
54
 
55
  // === Auth modal open/close (same as your last version in the template) ===
56
+ openSignIn() { this.showSignIn = true; this.showSignUp = false; document.body.classList.add('modal-open'); }
57
+ openSignUp() { this.showSignUp = true; this.showSignIn = false; document.body.classList.add('modal-open'); }
58
+ closeModal() { this.showSignIn = this.showSignUp = false; document.body.classList.remove('modal-open'); }
59
 
60
  // If your <app-sign-in> emits signInSuccess, keep the same handler
61
  handleSignInSuccess() {
src/app/homepage/sign-in/sign-in.component.css CHANGED
@@ -1,18 +1,21 @@
1
  /* root colors and variables remain unchanged */
2
 
3
  .signin-popup {
4
- position: fixed;
5
- top: 0;
6
- left: 0;
7
- width: 100vw;
8
- height: 100vh;
9
- display: flex;
10
- flex-direction: column;
11
- align-items: center;
12
- justify-content: center;
13
- z-index: 1000;
14
- background: rgb(30 41 59 / 67%);
15
- backdrop-filter: blur(16px);
 
 
 
16
  }
17
 
18
  /* Ensure no solid white background is set anywhere */
@@ -86,9 +89,15 @@
86
  form {
87
  width: 100%;
88
  }
 
 
 
 
 
 
89
  .signin-row {
90
  display: flex;
91
- gap: 24px;
92
  margin-bottom: 18px;
93
  }
94
  .signin-field {
@@ -96,29 +105,70 @@ form {
96
  display: flex;
97
  flex-direction: column;
98
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  .signin-field label {
100
- color: #fff;
101
  font-weight: 600;
102
- margin-bottom: 6px;
103
  font-size: 1rem;
104
  letter-spacing: 0.5px;
105
  }
106
- .signin-field input {
 
 
 
 
 
107
  background: #fff;
108
  color: #18314a;
109
  border: none;
110
- border-radius: 8px;
111
- padding: 12px 14px;
112
  font-size: 1rem;
113
- margin-bottom: 2px;
114
- box-shadow: 0 1px 4px #0002;
115
  transition: border 0.2s, box-shadow 0.2s;
116
  }
117
- .signin-field input:focus {
 
 
 
 
 
118
  outline: 2px solid #1de9b6;
119
  border-color: #1de9b6;
120
  box-shadow: 0 0 0 2px #1de9b688;
121
  }
 
122
  .signin-field input::placeholder {
123
  color: #b0b8c1;
124
  opacity: 1;
@@ -126,28 +176,34 @@ form {
126
  .signin-field small.error {
127
  color: #ff5252;
128
  font-size: 0.85rem;
129
- margin-top: 2px;
130
  text-shadow: 0 1px 2px #0008;
131
  }
132
  .signin-options-row {
133
  display: flex;
134
  justify-content: space-between;
135
  align-items: center;
136
- margin-bottom: 12px;
137
- margin-top: -8px;
138
  }
139
  .remember-me {
140
  display: flex;
141
  align-items: center;
142
- gap: 6px;
143
  font-size: 1rem;
144
- color: #b0b8c1;
 
 
 
 
 
 
 
 
 
 
 
145
  }
146
- .remember-me input[type="checkbox"] {
147
- accent-color: #38bdf8;
148
- width: 16px;
149
- height: 16px;
150
- }
151
  .forgot-password {
152
  font-size: 1rem;
153
  }
@@ -158,21 +214,21 @@ form {
158
  cursor: pointer;
159
  }
160
  .signin-btn {
161
- width:100%;
162
  background: #18314a;
163
  color: #fff;
164
  border: none;
165
- border-radius: 8px;
166
  padding: 14px 0;
167
- font-size: 1.1rem;
168
  font-weight: 700;
169
  margin-bottom: 18px;
170
  cursor: pointer;
171
  transition: background 0.2s, color 0.2s;
172
  }
173
- .signin-btn:hover {
174
- background: #38bdf8;
175
- }
176
  .signin-footer {
177
  color: #b0b8c1;
178
  font-size: 0.95rem;
@@ -183,13 +239,13 @@ form {
183
  display: block;
184
  height: 16px;
185
  }
186
- .signin-footer a {
187
- color: #38bdf8;
188
- text-decoration: underline;
189
- margin-left: 4px;
190
- font-weight: 600;
191
- cursor: pointer;
192
- }
193
  .signin-close {
194
  position: absolute;
195
  top: 18px;
@@ -210,10 +266,10 @@ form {
210
  transition: background 0.2s, color 0.2s;
211
  box-shadow: 0 2px 8px #0005;
212
  }
213
- .signin-close:hover {
214
- background: #38bdf8;
215
- color: #18314a;
216
- }
217
 
218
  @media (max-width: 700px) {
219
  .signin-box {
@@ -231,7 +287,10 @@ form {
231
  }
232
  .signin-row {
233
  flex-direction: column;
234
- gap: 0;
 
 
 
235
  }
236
  }
237
  .ai-particle-bg {
@@ -246,7 +305,7 @@ form {
246
  @keyframes particleDrift {
247
  0% { background-position: 0 0; }
248
  100% { background-position: 120px 80px; }
249
- }
250
  .spinner {
251
  display: inline-block;
252
  width: 18px;
@@ -256,7 +315,7 @@ form {
256
  border-radius: 50%;
257
  animation: spin 0.7s linear infinite;
258
  vertical-align: middle;
259
- margin-right: 8px;
260
  }
261
 
262
  @keyframes spin {
@@ -299,7 +358,7 @@ form {
299
  font-weight: 600;
300
  }
301
  .signin-welcome {
302
- color: #38bdf8;
303
  font-size: 1.05em;
304
  text-align: center;
305
  margin-bottom: 18px;
@@ -400,130 +459,171 @@ form {
400
  to { opacity: 1; }
401
  }
402
  .forgot-modal {
403
- background: #fff;
404
- border-radius: 18px;
405
- box-shadow: 0 8px 32px #38bdf844, 0 0 24px #1e293b88;
406
- padding: 32px 36px 28px 36px;
407
- min-width: 320px;
408
- max-width: 90vw;
409
- text-align: center;
410
- z-index: 2001;
411
- display: flex;
412
- flex-direction: column;
413
- align-items: center;
414
- animation: fadeInModal 0.4s;
415
- }
416
- @keyframes fadeInModal {
417
- from { opacity: 0; transform: scale(0.98); }
418
- to { opacity: 1; transform: scale(1); }
419
- }
420
- .forgot-modal h3 {
421
- color: #38bdf8;
422
- margin: 12px 0 8px 0;
423
- font-size: 1.4em;
424
- font-weight: 700;
425
- }
426
- .forgot-modal p {
427
- color: #23272b;
428
- font-size: 1.08em;
429
- margin-bottom: 18px;
430
  }
431
  .forgot-modal input[type="email"] {
432
- background: #f4f6fa;
433
- color: #18314a;
434
- border: none;
435
  border-radius: 8px;
436
- padding: 12px 14px;
437
- font-size: 1rem;
438
  margin-bottom: 12px;
439
- box-shadow: 0 1px 4px #0002;
440
- width: 100%;
441
  }
442
- .modal-close {
443
- width: 23%;
444
- background: #18314a;
445
- color: #fff;
446
- border: none;
447
- border-radius: 8px;
448
- padding: 14px 0;
449
- font-size: 1.1rem;
450
- font-weight: 700;
451
- margin-bottom: 0;
452
- cursor: pointer;
453
- transition: background 0.2s, color 0.2s;
454
- display: block;
455
  }
456
 
457
- .modal-close:hover {
458
- background: #38bdf8;
459
- color: #18314a;
460
- }
461
- .google-signin-row {
462
- width: 100%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  display: flex;
464
- justify-content: center;
 
 
 
465
  margin-bottom: 12px;
466
  }
467
- #google-btn-div {
468
- width: 100%;
469
- display: flex;
470
- justify-content: center;
471
  }
472
- #google-btn-div > div {
473
- width: 100%;
474
- min-width: 0;
475
- border-radius: 8px !important;
476
- padding: 14px 0 !important;
477
- font-size: 1.1rem !important;
478
- font-weight: 700 !important;
479
- box-shadow: 0 2px 8px #0003 !important;
480
- background: #fff !important;
481
- color: #18314a !important;
482
- border: none !important;
483
- margin: 0 !important;
 
 
 
 
 
 
 
 
484
  display: flex;
485
  align-items: center;
486
- justify-content: center;
 
 
487
  }
488
- .g-signin2 {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  width: 100%;
490
- min-width: 0;
491
- border-radius: 8px !important;
492
- padding: 14px 0 !important;
493
- font-size: 1.1rem !important;
494
- font-weight: 700 !important;
495
- box-shadow: 0 2px 8px #0003 !important;
496
- background: #fff !important;
497
- color: #18314a !important;
498
- border: none !important;
499
- margin: 0 !important;
500
  display: flex;
501
  align-items: center;
502
  justify-content: center;
503
- }
504
-
505
- .eye-toggle {
506
- position: absolute;
507
- right: 12px;
508
- top: 38px;
509
- background: none;
510
  border: none;
511
- font-size: 1.3em;
512
- color: #888;
513
- cursor: pointer;
514
- z-index: 2;
515
- padding: 0;
516
- line-height: 1;
517
- opacity: 0.7;
518
- transition: color 0.2s, opacity 0.2s;
519
- }
520
- .eye-toggle:hover {
521
- color: #555;
522
- opacity: 1;
523
  }
524
- .eye-toggle:focus {
525
- outline: none;
 
 
 
526
  }
 
527
  /* --- UI DESIGN UPDATE TO MATCH SCREENSHOT --- */
528
  .auth-box {
529
  display: grid;
@@ -685,7 +785,7 @@ form {
685
  .auth-box {
686
  display: grid;
687
  grid-template-columns: 360px 840px; /* reduced right panel from 600px to 520px */
688
- width: 850px; /* adjusted total width to match columns */
689
  height: 820px;
690
  max-width: 98vw;
691
  min-width: 340px;
@@ -695,9 +795,9 @@ form {
695
  background: none;
696
  position: relative;
697
  box-sizing: border-box;
698
- justify-content:end;
699
- align-items:baseline;
700
- justify-items:start;
701
  }
702
 
703
  /* Ensure panels occupy full height and use box-sizing */
@@ -764,7 +864,7 @@ form {
764
  flex-direction: row-reverse;
765
  }
766
  .side-panel { width: 48%; display:flex; align-items:center; justify-content:center; }
767
- .main-panel { width: 55%; padding: 36px 48px; box-sizing:border-box; background:#fff; overflow: visible; }
768
 
769
  .side-right {
770
  background: linear-gradient(135deg,#137ec4 0%,#137ec4 100%);
@@ -777,7 +877,6 @@ form {
777
  .side-text { opacity: 0.95; }
778
  .panel-cta { background:none; border:2px solid #fff; color:#fff; padding:10px 22px; border-radius:999px; cursor:pointer; margin-top:12px; }
779
 
780
- /* Ensure sign-in text colors for white main panel */
781
  .card-front .main-panel .signin-title { color: #222; }
782
  .card-back .main-panel .signup-title { color: #222; }
783
 
@@ -789,7 +888,27 @@ form {
789
  .side-panel{ width:100%; height:200px; }
790
  .main-panel{ width:100%; overflow: visible; }
791
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
792
 
 
 
793
  /* keep existing control styles unchanged (inputs/buttons/etc.) */
794
 
795
  /* minimal overwrite of previous rules that used absolute positioning */
@@ -813,6 +932,7 @@ form {
813
  width: 88%;
814
  padding: 0 32px;
815
  z-index: 2;
 
816
  text-align: left;
817
  }
818
  .side-info-title {
@@ -883,19 +1003,19 @@ form {
883
  align-items: center;
884
  justify-content: center;
885
  width:100%;
886
- margin:12px 018px 0;
887
  }
888
  .divider {
889
  flex:1;
890
  height:1px;
891
  background: #b0b8c1;
892
- margin:08px;
893
  }
894
  .divider-or {
895
  color: #23395d;
896
  font-size:1.08em;
897
  font-weight:600;
898
- margin:08px;
899
  }
900
  .google-btn {
901
  width: 100%;
@@ -909,16 +1029,16 @@ form {
909
  font-weight: 700;
910
  margin-bottom: 18px;
911
  cursor: pointer;
912
- box-shadow: 02px 8px #0003;
913
  display: flex;
914
  align-items: center;
915
  justify-content: center;
916
  gap: 12px;
917
  transition: background 0.2s, color 0.2s;
918
  }
919
- .google-btn:hover {
920
- background: #38bdf8;
921
- }
922
  .google-logo {
923
  width:24px;
924
  height:24px;
@@ -932,7 +1052,7 @@ form {
932
  text-align: left;
933
  transition: opacity 0.6s;
934
  letter-spacing:0.5px;
935
- animation: fadeFact0.6s;
936
  }
937
  @keyframes fadeFact {
938
  from { opacity:0; }
@@ -949,225 +1069,194 @@ form {
949
  top:0; left:0;
950
  width:100%; height:100%;
951
  object-fit: cover;
952
- z-index:0;
953
- opacity:0.12;
954
  }
955
- .side-bg-shapes {
 
 
 
956
  position: absolute;
957
- top:0; left:0; right:0; bottom:0;
 
958
  width:100%;
959
  height:100%;
960
- z-index:1;
961
- pointer-events: none;
962
- }
963
- .bg-circle {
964
- position: absolute;
965
- border-radius:50%;
966
- opacity:0.85;
967
- }
968
- .circle1 {
969
- width: 120px;
970
- height: 120px;
971
- top: 18px;
972
- left: 18px;
973
- background: linear-gradient(135deg, #38bdf8 80%, #137ec4 100%);
974
- }
975
- .circle2 {
976
- width: 80px;
977
- height: 80px;
978
- top: 71%;
979
- left: 113px;
980
- background: linear-gradient(135deg, #38bdf8 80%, #137ec4 100%);
981
- }
982
-
983
- .circle3 {
984
- width: 48px;
985
- height: 48px;
986
- top: 80%;
987
- left: 70%;
988
- background: linear-gradient(135deg, #38bdf8 80%, #137ec4 100%);
989
- }
990
-
991
- .circle4 {
992
- width: 160px;
993
- height: 160px;
994
- top: 70px;
995
- right: 204px;
996
- background: linear-gradient(135deg, #38bdf8 80%, #137ec4 100%);
997
- opacity: 0.7;
998
  }
999
 
1000
- .circle5 {
1001
- width: 36px;
1002
- height: 36px;
1003
- top: 30%;
1004
- left: 80%;
1005
- background: linear-gradient(135deg, #38bdf8 80%, #137ec4 100%);
1006
- opacity: 0.6;
 
 
 
 
1007
  }
1008
 
1009
- .circle6 {
1010
- width: 60px;
1011
- height: 60px;
1012
- bottom: 12%;
1013
- right: 18%;
1014
- background: linear-gradient(135deg, #38bdf8 80%, #137ec4 100%);
1015
- opacity: 0.5;
 
1016
  }
1017
 
1018
- .circle7 {
1019
- width: 54px;
1020
- height: 54px;
1021
- top: 10%;
1022
- right: 10%;
1023
- background: #18314a;
1024
- opacity: 0.8;
1025
  }
1026
 
1027
- .circle8 {
1028
- width: 32px;
1029
- height: 32px;
1030
- bottom: 20%;
1031
- left: 60%;
1032
- background: #14263c;
1033
- opacity: 0.8;
 
 
 
 
 
 
 
 
 
 
 
 
1034
  }
1035
 
1036
- .circle9 {
1037
- width: 44px;
1038
- height: 44px;
1039
- top: 75%;
1040
- left: 40%;
1041
- background: #18314a;
1042
- opacity: 0.7;
1043
  }
1044
 
1045
- .circle10 {
1046
- width: 28px;
1047
- height: 28px;
1048
- top: 15%;
1049
- left: 60%;
1050
- background: #14263c;
1051
- opacity: 0.9;
1052
  }
1053
 
1054
- .circle11 {
1055
- width: 38px;
1056
- height: 38px;
1057
- bottom: 10%;
1058
- right: 10%;
1059
- background: #18314a;
1060
- opacity: 0.7;
 
 
 
 
 
 
1061
  }
1062
 
1063
- .circle12 {
1064
- width: 22px;
1065
- height: 22px;
1066
- top: 85%;
1067
- right: 20%;
1068
- background: #14263c;
1069
- opacity: 0.8;
1070
- }
1071
 
1072
- /* New large solid circles for sign-in page background */
1073
- .circle-large1 {
1074
- width:220px;
1075
- height:220px;
1076
- top:40px;
1077
- left:60px;
1078
- background: #137ec4;
1079
- opacity:0.45;
1080
- z-index:0;
1081
- }
1082
- .circle-large2 {
1083
- width:180px;
1084
- height:180px;
1085
- bottom:40px;
1086
- right:40px;
1087
- background: #38bdf8;
1088
- opacity:0.35;
1089
- z-index:0;
1090
- }
1091
- .circle-large3 {
1092
- width:140px;
1093
- height:140px;
1094
- top:60%;
1095
- left:60%;
1096
- background: #18314a;
1097
- opacity:0.25;
1098
- z-index:0;
1099
- }
1100
- .circle-large4 {
1101
- width:100px;
1102
- height:100px;
1103
- bottom:11%;
1104
- left:5%;
1105
- background: #13bfa6;
1106
- opacity:0.52;
1107
- z-index:0;
1108
- }
1109
 
1110
- /* Add more white rings for contrast */
1111
- .bg-ring {
1112
- position: absolute;
1113
- border-radius:50%;
1114
- border:6px solid #fff;
1115
- background: none;
1116
- opacity:0.8;
1117
- z-index:1;
1118
- }
1119
- .ring1 { width:90px; height:90px; bottom:24px; left:18px; }
1120
- .ring2 { width:48px; height:48px; top:60px; right:32px; }
1121
- .ring3 { width:72px; height:72px; top:10%; left:70%; border-color: #fff; opacity:0.7; }
1122
- .ring4 { width:110px; height:110px; bottom:2%; right:43%; border-color: #fff; opacity:0.5; }
1123
- .ring5 { width:38px; height:38px; top:75%; left:10%; border-color: #fff; opacity:0.8; }
1124
- .ring6 { width:64px; height:64px; top:49%; right:20%; border-color: #fff; opacity:0.7; }
1125
- .ring7 { width:120px; height:120px; top:14%; left:49%; border-color: #fff; opacity:0.6; }
1126
- .ring8 { width:80px; height:80px; bottom:20%; right:30%; border-color: #fff; opacity:0.7; }
1127
- .ring9 { width:60px; height:60px; top:65%; left:36%; border-color: #fff; opacity:0.7; }
1128
 
1129
- .side-welcome-overlay {
1130
- position: absolute;
1131
- top:30%;
1132
- left:50px;
1133
- width:93%;
1134
- text-align: start;
1135
- z-index:2;
1136
- padding:024px;
1137
- pointer-events: auto;
1138
- }
1139
- .welcome-back-title {
1140
- font-size:2.1rem;
1141
- font-weight:800;
1142
- color: #fff;
1143
- margin-bottom:8px;
1144
-
1145
- }
1146
- .welcome-back-desc {
1147
- font-size:1rem;
1148
- color: #e0f7fa;
1149
- margin-bottom:18px;
1150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1151
  }
1152
- .action-btn {
1153
- width:21%;
1154
- background: #18314a;
1155
- color: #fff;
1156
- border: none;
1157
- border-radius:8px;
1158
- padding:14px 0;
1159
- font-size:1.1rem;
1160
- font-weight:700;
1161
- margin-top:18px;
1162
- margin-bottom:0;
 
 
1163
  display: inline-block;
1164
- letter-spacing:0.5px;
1165
- box-shadow:02px 8px #0003;
1166
- cursor: pointer;
1167
- transition: background 0.2s, color 0.2s;
1168
- }
1169
- .action-btn:hover {
1170
- background: #38bdf8;
1171
- color: #18314a;
1172
  }
 
1173
 
 
1
  /* root colors and variables remain unchanged */
2
 
3
  .signin-popup {
4
+ position: fixed;
5
+ top:0;
6
+ left:0;
7
+ width:100vw;
8
+ height:100vh;
9
+ display: flex;
10
+ flex-direction: column;
11
+ align-items: center;
12
+ justify-content: center;
13
+ z-index:1000;
14
+ /* Use a reliable semi-transparent overlay instead of backdrop-filter */
15
+ background: rgba(30,41,59,0.35); /* softly dim the homepage */
16
+ /* Remove flaky backdrop filters */
17
+ -webkit-backdrop-filter: none;
18
+ backdrop-filter: none;
19
  }
20
 
21
  /* Ensure no solid white background is set anywhere */
 
89
  form {
90
  width: 100%;
91
  }
92
+ /* Narrower form and consistent spacing */
93
+ .signin-form {
94
+ width: 86%;
95
+ max-width: 420px;
96
+ margin: 0 auto;
97
+ }
98
  .signin-row {
99
  display: flex;
100
+ gap: 22px;
101
  margin-bottom: 18px;
102
  }
103
  .signin-field {
 
105
  display: flex;
106
  flex-direction: column;
107
  }
108
+ /* New wrapper so input and eye toggle stay aligned */
109
+ .password-wrapper {
110
+ position: relative;
111
+ display: flex;
112
+ align-items: center;
113
+ }
114
+ .password-wrapper input {
115
+ flex:1;
116
+ }
117
+ /* Position the eye toggle only within sign-in password wrapper */
118
+ .signin-popup .password-wrapper .eye-toggle {
119
+ position: absolute;
120
+ right:10px;
121
+ top:46%;
122
+ transform: translateY(-50%);
123
+ background: rgba(255,255,255,0.06);
124
+ border: none;
125
+ width:24px;
126
+ height:24px;
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ border-radius:6px;
131
+ cursor: pointer;
132
+ color: inherit;
133
+ }
134
+
135
+ /* Slightly larger icon in sign-in */
136
+ .signin-popup .password-wrapper .eye-toggle i { font-size:0.85rem; }
137
+
138
  .signin-field label {
139
+ color: #e6f7f9; /* slightly darker than pure white for better contrast */
140
  font-weight: 600;
141
+ margin-bottom: 10px;
142
  font-size: 1rem;
143
  letter-spacing: 0.5px;
144
  }
145
+ /* Inputs: use consistent 8px radius */
146
+ .signin-field input,
147
+ .forgot-modal input[type="email"],
148
+ .forgot-modal .otp-modal input[type="text"],
149
+ .panel-left .signin-field input,
150
+ .panel-left .signin-field select {
151
  background: #fff;
152
  color: #18314a;
153
  border: none;
154
+ border-radius: 8px; /* updated to 8px for consistency */
155
+ padding: 14px 16px;
156
  font-size: 1rem;
157
+ margin-bottom: 8px;
158
+ box-shadow: 0 1px 6px #0002;
159
  transition: border 0.2s, box-shadow 0.2s;
160
  }
161
+
162
+ .signin-field input:focus,
163
+ .forgot-modal input[type="email"]:focus,
164
+ .forgot-modal .otp-modal input[type="text"]:focus,
165
+ .panel-left .signin-field input:focus,
166
+ .panel-left .signin-field select:focus {
167
  outline: 2px solid #1de9b6;
168
  border-color: #1de9b6;
169
  box-shadow: 0 0 0 2px #1de9b688;
170
  }
171
+
172
  .signin-field input::placeholder {
173
  color: #b0b8c1;
174
  opacity: 1;
 
176
  .signin-field small.error {
177
  color: #ff5252;
178
  font-size: 0.85rem;
179
+ margin-top: 6px;
180
  text-shadow: 0 1px 2px #0008;
181
  }
182
  .signin-options-row {
183
  display: flex;
184
  justify-content: space-between;
185
  align-items: center;
186
+ margin-bottom: 28px;
187
+ margin-top: -4px;
188
  }
189
  .remember-me {
190
  display: flex;
191
  align-items: center;
192
+ gap: 8px;
193
  font-size: 1rem;
194
+ color: #e6f7f9;
195
+ }
196
+ .remember-me input[type="checkbox"] {
197
+ width: 18px;
198
+ height: 18px;
199
+ accent-color: var(--primary-accent, #14263c);
200
+ cursor: pointer;
201
+ }
202
+ .remember-me label {
203
+ cursor: pointer;
204
+ color: #e6f7f9;
205
+ user-select: none;
206
  }
 
 
 
 
 
207
  .forgot-password {
208
  font-size: 1rem;
209
  }
 
214
  cursor: pointer;
215
  }
216
  .signin-btn {
217
+ width: 100%;
218
  background: #18314a;
219
  color: #fff;
220
  border: none;
221
+ border-radius: 10px;
222
  padding: 14px 0;
223
+ font-size: 1.05rem;
224
  font-weight: 700;
225
  margin-bottom: 18px;
226
  cursor: pointer;
227
  transition: background 0.2s, color 0.2s;
228
  }
229
+ .signin-btn:hover {
230
+ background: #38bdf8;
231
+ }
232
  .signin-footer {
233
  color: #b0b8c1;
234
  font-size: 0.95rem;
 
239
  display: block;
240
  height: 16px;
241
  }
242
+ .signin-footer a {
243
+ color: #38bdf8;
244
+ text-decoration: underline;
245
+ margin-left: 4px;
246
+ font-weight: 600;
247
+ cursor: pointer;
248
+ }
249
  .signin-close {
250
  position: absolute;
251
  top: 18px;
 
266
  transition: background 0.2s, color 0.2s;
267
  box-shadow: 0 2px 8px #0005;
268
  }
269
+ .signin-close:hover {
270
+ background: #38bdf8;
271
+ color: #18314a;
272
+ }
273
 
274
  @media (max-width: 700px) {
275
  .signin-box {
 
287
  }
288
  .signin-row {
289
  flex-direction: column;
290
+ gap: 10px;
291
+ }
292
+ .signin-form {
293
+ width: 92%;
294
  }
295
  }
296
  .ai-particle-bg {
 
305
  @keyframes particleDrift {
306
  0% { background-position: 0 0; }
307
  100% { background-position: 120px 80px; }
308
+ }
309
  .spinner {
310
  display: inline-block;
311
  width: 18px;
 
315
  border-radius: 50%;
316
  animation: spin 0.7s linear infinite;
317
  vertical-align: middle;
318
+ margin-right: 10px;
319
  }
320
 
321
  @keyframes spin {
 
358
  font-weight: 600;
359
  }
360
  .signin-welcome {
361
+ color: #fff;
362
  font-size: 1.05em;
363
  text-align: center;
364
  margin-bottom: 18px;
 
459
  to { opacity: 1; }
460
  }
461
  .forgot-modal {
462
+ background: #18314a;
463
+ padding: 20px;
464
+ border-radius: 12px;
465
+ color: #fff;
466
+ width: 360px;
467
+ max-width: 92vw;
468
+ box-shadow: 0 8px 32px #0008;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
469
  }
470
  .forgot-modal input[type="email"] {
471
+ width: 100%;
472
+ padding: 10px 0px;
 
473
  border-radius: 8px;
474
+ border: none;
 
475
  margin-bottom: 12px;
 
 
476
  }
477
+ .forgot-modal .modal-close {
478
+ display: block;
479
+ margin: 12px auto 0 auto; /* center horizontally under the primary button */
480
+ background: #00000070;
481
+ color: #e6f7f9;
482
+ border: 1px solid rgba(255,255,255,0.08);
483
+ padding: 10px 14px;
484
+ border-radius: 8px;
485
+ font-size: 1.2rem;
486
+ }
487
+ .forgot-modal .modal-close:hover {
488
+ background: #38bdf8;
489
+ color: #18314a;
490
  }
491
 
492
+ /* Forgot modal send button: prominent background and border */
493
+ .forgot-modal .send-reset-btn {
494
+ display: inline-block;
495
+ width:100%;
496
+ background: #38bdf8; /* bright cyan */
497
+ color: #18314a; /* dark text for contrast */
498
+ border:2px solid #0b57a4; /* darker blue border */
499
+ padding:12px 14px;
500
+ border-radius:8px;
501
+ font-weight:700;
502
+ cursor: pointer;
503
+ box-shadow:0 6px 18px rgba(3,20,36,0.12);
504
+ transition: background 0.18s ease, transform 0.12s ease;
505
+ }
506
+ .forgot-modal .send-reset-btn:hover {
507
+ background: #1dc4ff;
508
+ transform: translateY(-1px);
509
+ }
510
+
511
+ /* OTP modal centered over entire page with blurred backdrop */
512
+ .otp-modal-bg {
513
+ position: fixed; /* full viewport */
514
+ inset:0;
515
+ display: flex;
516
+ align-items: center;
517
+ justify-content: center;
518
+ pointer-events: auto; /* capture clicks on backdrop */
519
+ z-index:3000; /* above other modals */
520
+ background: rgba(2,6,23,0.45); /* dark translucent */
521
+ backdrop-filter: blur(10px);
522
+ -webkit-backdrop-filter: blur(10px);
523
+ }
524
+
525
+ .otp-modal {
526
+ pointer-events: auto;
527
+ background: #18314a;
528
+ padding:20px 22px;
529
+ border-radius:12px;
530
+ width:360px;
531
+ max-width:86vw;
532
+ box-shadow:0 8px 28px rgba(0,0,0,0.45);
533
+ color: #fff;
534
+ }
535
+ .forgot-modal .otp-modal input[type="text"] {
536
+ width:100%;
537
+ padding:10px 12px;
538
+ border-radius:8px;
539
+ border: none;
540
+ margin-top:8px;
541
+ }
542
+ .forgot-modal .otp-modal .modal-close {
543
+ background: transparent;
544
+ color: #fff;
545
+ border:1px solid rgba(255,255,255,0.06);
546
+ padding:8px 12px;
547
+ border-radius:8px;
548
+ }
549
+ .autologin-checkbox {
550
  display: flex;
551
+ align-items: center;
552
+ gap: 8px;
553
+ color: #b0b8c1;
554
+ font-size: 0.9rem;
555
  margin-bottom: 12px;
556
  }
557
+ .autologin-checkbox input {
558
+ accent-color: #38bdf8;
559
+ transform: scale(1.2);
 
560
  }
561
+ .auto-login-desc {
562
+ color: #e6f7f9;
563
+ font-size: 0.9rem;
564
+ margin-left: 4px;
565
+ }
566
+
567
+ /* Ensure otp modal sits centered relative to auth-card */
568
+ .auth-card { position: relative; }
569
+
570
+ /* keep existing forgot-modal-bg style for full-screen modals */
571
+ .forgot-modal-bg { position: fixed; inset:0; background: rgba(30,41,59,0.9); z-index:2000; }
572
+
573
+ /* Security indicator styles */
574
+ .lock-icon-inline {
575
+ color: #38bdf8;
576
+ margin-right: 8px;
577
+ font-size: 1rem;
578
+ vertical-align: middle;
579
+ }
580
+ .security-row .security-info {
581
  display: flex;
582
  align-items: center;
583
+ gap: 8px;
584
+ color: #b0c7d4;
585
+ font-size: 0.95rem;
586
  }
587
+ .security-text {
588
+ /* Brighter color for better contrast on dark background */
589
+ color: #23395d;
590
+ font-weight: 600;
591
+ }
592
+ /* Make Need help line match and be more visible */
593
+ .signin-help {
594
+ color: #23395d;
595
+ font-weight: 600;
596
+ margin-right: 74px;
597
+ }
598
+ .signin-help a {
599
+ color: #e6f7f9;
600
+ text-decoration: underline;
601
+ margin-left: 6px;
602
+ font-weight: 600;
603
+ cursor: pointer;
604
+ }
605
+
606
+ /* Google button */
607
+ .google-btn {
608
  width: 100%;
 
 
 
 
 
 
 
 
 
 
609
  display: flex;
610
  align-items: center;
611
  justify-content: center;
612
+ gap: 10px;
613
+ background: #0f1724;
614
+ color: #fff;
615
+ border-radius: 10px;
616
+ padding: 12px 14px;
617
+ box-shadow: 0 6px 0 rgba(0,0,0,0.12);
 
618
  border: none;
 
 
 
 
 
 
 
 
 
 
 
 
619
  }
620
+ .google-logo { width: 18px; height: 18px; }
621
+
622
+ /* small responsive tweaks */
623
+ @media (max-width: 480px) {
624
+ .security-row .security-info { flex-direction: column; align-items: flex-start; gap: 6px; }
625
  }
626
+
627
  /* --- UI DESIGN UPDATE TO MATCH SCREENSHOT --- */
628
  .auth-box {
629
  display: grid;
 
785
  .auth-box {
786
  display: grid;
787
  grid-template-columns: 360px 840px; /* reduced right panel from 600px to 520px */
788
+ width: 850px; /* adjusted to match columns */
789
  height: 820px;
790
  max-width: 98vw;
791
  min-width: 340px;
 
795
  background: none;
796
  position: relative;
797
  box-sizing: border-box;
798
+ justify-content: end;
799
+ align-items: baseline;
800
+ justify-items: start;
801
  }
802
 
803
  /* Ensure panels occupy full height and use box-sizing */
 
864
  flex-direction: row-reverse;
865
  }
866
  .side-panel { width: 48%; display:flex; align-items:center; justify-content:center; }
867
+ .main-panel { width: 60%; padding: 1px 1px; box-sizing:border-box; background:#fff; overflow: visible; }
868
 
869
  .side-right {
870
  background: linear-gradient(135deg,#137ec4 0%,#137ec4 100%);
 
877
  .side-text { opacity: 0.95; }
878
  .panel-cta { background:none; border:2px solid #fff; color:#fff; padding:10px 22px; border-radius:999px; cursor:pointer; margin-top:12px; }
879
 
 
880
  .card-front .main-panel .signin-title { color: #222; }
881
  .card-back .main-panel .signup-title { color: #222; }
882
 
 
888
  .side-panel{ width:100%; height:200px; }
889
  .main-panel{ width:100%; overflow: visible; }
890
  }
891
+ /* Page-specific eye-toggle for sign-in: scoped to .signin-field to override shared component defaults */
892
+ .signin-field .eye-toggle {
893
+ position: absolute;
894
+ right:8px;
895
+ top:66%;
896
+ transform: translateY(-50%);
897
+ background: rgba(255,255,255,0.06);
898
+ border: none;
899
+ width:20px;
900
+ height:20px;
901
+ display: flex;
902
+ align-items: center;
903
+ justify-content: center;
904
+ border-radius:6px;
905
+ cursor: pointer;
906
+ color: inherit;
907
+ z-index:0; /* ensure modals and popups sit above */
908
+ }
909
 
910
+ /* Slightly larger icon inside sign-in toggle for clarity */
911
+ .signin-field .eye-toggle i { font-size:0.85rem; }
912
  /* keep existing control styles unchanged (inputs/buttons/etc.) */
913
 
914
  /* minimal overwrite of previous rules that used absolute positioning */
 
932
  width: 88%;
933
  padding: 0 32px;
934
  z-index: 2;
935
+ color: #fff;
936
  text-align: left;
937
  }
938
  .side-info-title {
 
1003
  align-items: center;
1004
  justify-content: center;
1005
  width:100%;
1006
+ margin:12px 0 18px 0;
1007
  }
1008
  .divider {
1009
  flex:1;
1010
  height:1px;
1011
  background: #b0b8c1;
1012
+ margin:0 8px;
1013
  }
1014
  .divider-or {
1015
  color: #23395d;
1016
  font-size:1.08em;
1017
  font-weight:600;
1018
+ margin:0 8px;
1019
  }
1020
  .google-btn {
1021
  width: 100%;
 
1029
  font-weight: 700;
1030
  margin-bottom: 18px;
1031
  cursor: pointer;
1032
+ box-shadow: 0 2px 8px #0003;
1033
  display: flex;
1034
  align-items: center;
1035
  justify-content: center;
1036
  gap: 12px;
1037
  transition: background 0.2s, color 0.2s;
1038
  }
1039
+ .google-btn:hover {
1040
+ background: #38bdf8;
1041
+ }
1042
  .google-logo {
1043
  width:24px;
1044
  height:24px;
 
1052
  text-align: left;
1053
  transition: opacity 0.6s;
1054
  letter-spacing:0.5px;
1055
+ animation: fadeFact 0.6s;
1056
  }
1057
  @keyframes fadeFact {
1058
  from { opacity:0; }
 
1069
  top:0; left:0;
1070
  width:100%; height:100%;
1071
  object-fit: cover;
1072
+ z-index:1;
1073
+ opacity:100;
1074
  }
1075
+
1076
+ /* Sign-in: ensure left-side welcome overlay sits above the left image and is styled like sign-up */
1077
+ .side-panel.side-left .side-img {
1078
+ z-index:0 !important; /* image underlay */
1079
  position: absolute;
1080
+ top:0;
1081
+ left:0;
1082
  width:100%;
1083
  height:100%;
1084
+ object-fit: cover;
1085
+ opacity:0.98;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1086
  }
1087
 
1088
+ .side-panel.side-left .side-welcome-overlay,
1089
+ .side-panel.side-left .side-info-box {
1090
+ position: relative !important;
1091
+ z-index:12 !important; /* bring overlay above image */
1092
+ max-width:78% !important;
1093
+
1094
+ padding:18px 20px !important;
1095
+ border-radius:10px !important;
1096
+
1097
+ color: #ffffff !important;
1098
+
1099
  }
1100
 
1101
+ .side-panel.side-left .side-welcome-overlay .welcome-back-title,
1102
+ .side-panel.side-left .side-info-box .side-info-title,
1103
+ .side-panel.side-left .welcome-back-title {
1104
+ font-size:1.6rem !important;
1105
+ font-weight:800 !important;
1106
+ margin:0 0 8px 0 !important;
1107
+ line-height:1.05 !important;
1108
+ color: #ffffff !important;
1109
  }
1110
 
1111
+ .side-panel.side-left .side-welcome-overlay .welcome-back-desc,
1112
+ .side-panel.side-left .side-info-box .side-info-desc,
1113
+ .side-panel.side-left .welcome-back-desc {
1114
+ font-size:1rem !important;
1115
+ line-height:1.6 !important;
1116
+ margin:0 0 12px 0 !important;
1117
+ color: rgba(255,255,255,0.95) !important;
1118
  }
1119
 
1120
+ .side-panel.side-left .side-welcome-overlay .action-btn,
1121
+ .side-panel.side-left .side-info-box .action-btn {
1122
+ display: inline-block !important;
1123
+ background: #010207 !important;
1124
+ color: #ffffff !important;
1125
+ border: 1px solid rgba(255, 255, 255, 0.92) !important;
1126
+ padding: 8px 14px !important;
1127
+ border-radius: 10px !important;
1128
+ font-weight: 700 !important;
1129
+ cursor: pointer !important;
1130
+ margin-top: 300px !important;
1131
+ margin-left: 25px;
1132
+ width: 293px;
1133
+ height: 58px;
1134
+ font-size: 2.28rem;
1135
+ }
1136
+ .side-panel.side-left .side-welcome-overlay .action-btn:hover,
1137
+ .side-panel.side-left .side-info-box .action-btn:hover {
1138
+ background: rgba(255,255,255,0.08) !important;
1139
  }
1140
 
1141
+ .side-panel.side-left .side-welcome-overlay a,
1142
+ .side-panel.side-left .side-info-box a {
1143
+ color: #ffffff !important;
1144
+ text-decoration: underline !important;
1145
+ font-weight:700 !important;
 
 
1146
  }
1147
 
1148
+ @media (max-width:900px) {
1149
+ .side-panel.side-left .side-welcome-overlay,
1150
+ .side-panel.side-left .side-info-box { max-width:92% !important; padding:12px !important; }
1151
+ .side-panel.side-left .side-welcome-overlay .welcome-back-title { font-size:1.25rem !important; }
1152
+ .side-panel.side-left .side-welcome-overlay .welcome-back-desc { font-size:0.98rem !important; }
1153
+ .side-panel.side-left .side-welcome-overlay .action-btn { padding:8px 12px !important; }
 
1154
  }
1155
 
1156
+ /* Sign-up: style side-info-box like sign-in overlay */
1157
+ .side-panel.side-right .side-info-box,
1158
+ .side-panel.side-right .side-welcome-overlay,
1159
+ .welcome-info-box {
1160
+ position: relative !important;
1161
+ z-index: 12 !important; /* bring overlay above image */
1162
+ max-width: 78% !important;
1163
+
1164
+ padding: 18px 20px !important;
1165
+ border-radius: 10px !important;
1166
+ ;
1167
+ color: #ffffff !important;
1168
+
1169
  }
1170
 
1171
+ .side-panel.side-right .side-info-box .side-info-title,
1172
+ .welcome-info-box .welcome-info-title {
1173
+ font-size: 1.6rem !important;
1174
+ font-weight: 800 !important;
1175
+ margin: 0 0 8px 0 !important;
1176
+ line-height: 1.05 !important;
1177
+ color: #ffffff !important;
1178
+ }
1179
 
1180
+ .side-panel.side-right .side-info-box .side-info-desc,
1181
+ .welcome-info-box .welcome-info-desc {
1182
+ font-size: 1rem !important;
1183
+ line-height: 1.6 !important;
1184
+ margin: 0 0 12px 0 !important;
1185
+ color: rgba(255,255,255,0.95) !important;
1186
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1187
 
1188
+ .side-panel.side-right .side-info-box .action-btn,
1189
+ .welcome-info-box .action-btn,
1190
+ .side-panel.side-right .side-info-box .panel-cta {
1191
+ display: inline-block !important;
1192
+ background: #010207 !important;
1193
+ color: #fff !important;
1194
+ border: 1px solid rgba(255, 255, 255, 0.92) !important;
1195
+ padding: 8px 14px !important;
1196
+ border-radius: 10px !important;
1197
+ font-weight: 700 !important;
1198
+ cursor: pointer !important;
1199
+ margin-bottom: 25px !important;
1200
+ margin-left: 50px;
1201
+ width: 293px;
1202
+ height: 58px;
1203
+ font-size: 2.28rem;
1204
+ }
 
1205
 
1206
+ .side-panel.side-right .side-info-box .action-btn:hover,
1207
+ .welcome-info-box .action-btn:hover,
1208
+ .side-panel.side-right .side-info-box .panel-cta:hover {
1209
+ background: rgba(255,255,255,0.08) !important;
1210
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1211
 
1212
+ .side-panel.side-right .side-info-box a,
1213
+ .welcome-info-box a {
1214
+ color: #ffffff !important;
1215
+ text-decoration: underline !important;
1216
+ font-weight: 700 !important;
1217
+ }
1218
+ .welcome-back-title.welcome-line-1 {
1219
+ font-size: 1.6rem !important;
1220
+ font-weight: 800 !important;
1221
+ margin: 0 0 8px 0 !important;
1222
+ line-height: 1.05 !important;
1223
+ color: #ffffff !important;
1224
+ }
1225
+ .forgot-modal-bg { /* existing styles assumed */ }
1226
+ /* Support modal uses same styling as forgot password */
1227
+ .forgot-modal h3 { margin-top:0; }
1228
+ .support-modal-bg {
1229
+ position: fixed;
1230
+ inset:0;
1231
+ background: rgba(2,6,23,0.55);
1232
+ z-index:2200;
1233
+ display: flex;
1234
+ align-items: center;
1235
+ justify-content: center;
1236
+ animation: fadeInModalBg0.4s;
1237
+ backdrop-filter: blur(6px);
1238
  }
1239
+ .support-modal {
1240
+ background: #0f1724;
1241
+ padding: 27px 47px;
1242
+ border-radius: 12px;
1243
+ color: #e6f7f9;
1244
+ width: 486px;
1245
+ max-width: 92vw;
1246
+ box-shadow: 0 10px 36px rgba(0, 0, 0, 0.45);
1247
+ margin-right: 1090px;
1248
+ }
1249
+ .support-modal h3 { margin-top:0; color:#38bdf8; }
1250
+ .support-modal a { color:#38bdf8; text-decoration: underline; }
1251
+ .support-modal-close {
1252
  display: inline-block;
1253
+ margin-top:12px;
1254
+ background: transparent;
1255
+ color: #e6f7f9;
1256
+ border:1px solid rgba(255,255,255,0.12);
1257
+ padding:8px 12px;
1258
+ border-radius:8px;
1259
+ font-size:1rem;
 
1260
  }
1261
+ .support-modal-close:hover { background: rgba(255,255,255,0.08); color:#fff; }
1262
 
src/app/homepage/sign-in/sign-in.component.html CHANGED
@@ -1,7 +1,6 @@
1
  <section class="signin-popup ai-bg-animate">
2
  <div class="ai-particle-bg"></div>
3
- <div class="signin-header">
4
- </div>
5
 
6
  <div class="auth-card" [class.flipped]="isFlipped">
7
  <div class="card-inner">
@@ -41,57 +40,66 @@
41
  </div>
42
  <!-- Overlay text and button -->
43
  <div class="side-welcome-overlay">
44
- <div class="welcome-back-title">Welcome back!</div>
45
- <div class="welcome-back-desc">You can sign in to access with your existing account.</div>
46
- <div class="welcome-back-desc">First time here? Join now.</div>
47
- <button class="action-btn" type="button" (click)="flipToSignUp()">Sign Up</button>
48
  </div>
49
- <!-- Replace /assets/side-placeholder-left.jpg with your chosen image -->
 
50
  </div>
51
 
52
- <div class="main-panel" style="display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 420px;">
53
- <!-- existing sign-in form -->
54
- <button class="signin-close" type="button" (click)="closePopup()" aria-label="Close" style="align-self: flex-end; margin-bottom: 10px;">&times;</button>
55
- <h2 class="signin-title" style="text-align: center; margin-bottom: 24px;">
56
- <span class="login-text">Login</span>
57
- </h2>
58
- <form [formGroup]="form" (ngSubmit)="signIn()" novalidate style="width: 100%; max-width: 340px;">
 
59
  <div class="signin-row">
60
  <div class="signin-field">
61
  <label for="email">Email</label>
62
- <input id="email" type="email" placeholder="you@example.com" formControlName="email" [attr.aria-invalid]="form.get('email')?.invalid && form.get('email')?.touched" />
 
63
  </div>
64
  </div>
 
65
  <div class="signin-row">
66
  <div class="signin-field" style="position:relative;">
67
- <label for="password">Password</label>
68
- <input id="password" [type]="showPassword ? 'text' : 'password'" placeholder="••••••••" formControlName="password" [attr.aria-invalid]="form.get('password')?.invalid && form.get('password')?.touched" />
69
- <button type="button" class="eye-toggle" (click)="togglePasswordVisibility()" tabindex="-1" aria-label="Show/Hide password">
 
 
 
 
 
70
  </button>
71
- <div *ngIf="errorMessage" class="signin-error-toast">{{ errorMessage }}</div>
 
 
72
  </div>
73
  </div>
 
74
  <div class="signin-row signin-options-row">
75
  <div class="remember-me">
76
- <label class="switch">
77
- <input id="rememberMe" type="checkbox" />
78
- <span class="slider"></span>
79
- </label>
80
- <span>Remember me</span>
81
  </div>
82
  <div class="forgot-password">
83
- <a href="#" class="forgot-link" (click)="openForgotModal($event)">Forgot password?</a>
84
  </div>
85
  </div>
86
- <!-- Hint removed for minimal UI -->
87
- <button class="signin-btn ai-pulse" type="submit" [disabled]="loading">
88
  <ng-container *ngIf="!loading; else signingIn">
89
  Sign In
90
  </ng-container>
91
  <ng-template #signingIn>
92
- <span class="spinner"></span> Signing In...
93
  </ng-template>
94
- </button>
95
 
96
  <!-- Divider with 'or' -->
97
  <div class="signin-divider-row">
@@ -101,12 +109,52 @@
101
  </div>
102
 
103
  <!-- Custom Google Sign-In button -->
104
- <button class="google-btn" type="button" (click)="onGoogleSignIn()">
105
  <img src="assets/google-logo.svg" alt="Google logo" class="google-logo" />
106
  <span>Log in with Google</span>
107
  </button>
108
 
 
 
 
 
 
 
 
 
 
109
  </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  </div>
111
  </div>
112
  </div>
@@ -122,57 +170,54 @@
122
  <div class="signin-footer">
123
  <a href="#" (click)="flipToSignIn(); $event.preventDefault()">Back to Sign In</a>
124
  </div>
 
125
  </div>
126
 
127
  <div class="side-panel side-right">
128
- <!-- Decorative background shapes -->
129
- <div class="side-bg-shapes">
130
- <div class="bg-circle circle1"></div>
131
- <div class="bg-circle circle2"></div>
132
- <div class="bg-circle circle3"></div>
133
- <div class="bg-circle circle4"></div>
134
- <div class="bg-circle circle5"></div>
135
- <div class="bg-circle circle6"></div>
136
- <div class="bg-circle circle7"></div>
137
- <div class="bg-circle circle8"></div>
138
- <div class="bg-circle circle9"></div>
139
- <div class="bg-circle circle10"></div>
140
- <div class="bg-circle circle11"></div>
141
- <div class="bg-circle circle12"></div>
142
- <div class="bg-circle circle13"></div>
143
- <div class="bg-circle circle14"></div>
144
- <!-- New large solid circles -->
145
- <div class="bg-circle circle-large1"></div>
146
- <div class="bg-circle circle-large2"></div>
147
- <div class="bg-circle circle-large3"></div>
148
- <div class="bg-circle circle-large4"></div>
149
- <!-- More white rings for contrast -->
150
- <div class="bg-ring ring1"></div>
151
- <div class="bg-ring ring2"></div>
152
- <div class="bg-ring ring3"></div>
153
- <div class="bg-ring ring4"></div>
154
- <div class="bg-ring ring5"></div>
155
- <div class="bg-ring ring6"></div>
156
- <div class="bg-ring ring7"></div>
157
- <div class="bg-ring ring8"></div>
158
- <div class="bg-ring ring9"></div>
159
- </div>
160
- <div class="side-info-box">
161
- <div class="welcome-back-title">Investigate Smarter.</div>
162
- <div class="welcome-back-title">Analyze Deeper.</div>
163
- <div class="welcome-back-desc">
164
- Py-Detect transforms traditional investigations with advanced emotion, tone, and consistency analysis.
165
- </div>
166
- <div class="welcome-back-desc">
167
- Get started today — powered by the intelligence of tomorrow.
168
- </div>
169
- <div class="welcome-back-desc">
170
- Existing user? Log in to your account.
171
- </div>
172
- <button class="action-btn" type="button" (click)="goToLogin()">Sign In</button>
173
- </div>
174
- <!-- Replace /assets/side-placeholder-right.jpg with your chosen image -->
175
- <img class="side-img" src="/assets/side-placeholder-right.jpg" alt="" />
176
  </div>
177
  </div>
178
  </div>
@@ -185,7 +230,7 @@
185
  <h3>Forgot Password</h3>
186
  <p>Enter your email to receive password reset instructions.</p>
187
  <input type="email" [(ngModel)]="forgotEmail" placeholder="Your email" />
188
- <button class="signin-btn" (click)="sendForgotEmail()">Send Reset Link</button>
189
  <button class="modal-close" (click)="closeForgotModal()">Close</button>
190
  </div>
191
  </div>
 
1
  <section class="signin-popup ai-bg-animate">
2
  <div class="ai-particle-bg"></div>
3
+ <div class="signin-header"></div>
 
4
 
5
  <div class="auth-card" [class.flipped]="isFlipped">
6
  <div class="card-inner">
 
40
  </div>
41
  <!-- Overlay text and button -->
42
  <div class="side-welcome-overlay">
43
+ <!--<div class="welcome-back-title welcome-line-1">Welcome back!</div>
44
+ <div class="welcome-back-desc welcome-line-2">Sign in to access your account and continue your work.</div>
45
+ <div class="welcome-back-desc welcome-line-3">New here? Create your account now.</div>-->
46
+ <button class="action-btn welcome-line-4" type="button" (click)="flipToSignUp()">Sign Up</button>
47
  </div>
48
+ <!-- Left-side artwork: replace with your chosen image in /assets -->
49
+ <img class="side-img" src="/assets/side-scale-right.jpg" alt="Scales of justice" />
50
  </div>
51
 
52
+ <div class="main-panel" style="display: flex; flex-direction: column; align-items: center; justify-content: center; min-height:420px;">
53
+ <!-- existing sign-in form -->
54
+ <button class="signin-close" type="button" (click)="closePopup()" aria-label="Close" style="align-self: flex-end; margin-bottom:10px;">&times;</button>
55
+ <h2 id="signinTitle" class="signin-title" style="text-align: center; margin-bottom:24px;">
56
+ <span class="login-text">Login</span>
57
+ </h2>
58
+ <form class="signin-form" [formGroup]="form" (ngSubmit)="signIn()" novalidate style="width:90%; max-width:455px;" role="form" aria-labelledby="signinTitle" [attr.aria-busy]="loading" autocomplete="off">
59
+
60
  <div class="signin-row">
61
  <div class="signin-field">
62
  <label for="email">Email</label>
63
+ <input id="email" type="email" placeholder="you@example.com" formControlName="email" aria-label="Email address" [attr.aria-invalid]="form.get('email')?.invalid && form.get('email')?.touched" aria-describedby="emailHelp" autocomplete="off" autocapitalize="off" spellcheck="false" />
64
+ <span id="emailHelp" class="sr-only">Enter your email address (example: you@example.com)</span>
65
  </div>
66
  </div>
67
+
68
  <div class="signin-row">
69
  <div class="signin-field" style="position:relative;">
70
+ <!-- Lock icon added to password label -->
71
+ <label for="password"><i class="fa-solid fa-lock lock-icon-inline" aria-hidden="true"></i> Password</label>
72
+ <!-- Wrap password input and eye toggle together so they move as one -->
73
+ <div class="password-wrapper">
74
+ <input id="password" [type]="showPassword ? 'text' : 'password'" placeholder="••••••••" formControlName="password" (keydown)="onPasswordKey($event)" (keyup)="onPasswordKey($event)" aria-label="Password" [attr.aria-invalid]="form.get('password')?.invalid && form.get('password')?.touched" [attr.aria-describedby]="capsLockOn ? 'capsWarning' : null" autocomplete="new-password" autocapitalize="off" spellcheck="false" />
75
+ <!-- local eye toggle button (sign-in only) -->
76
+ <button type="button" class="eye-toggle variant-signin" aria-label="Show password" [attr.aria-pressed]="showPassword" (click)="togglePasswordVisibility()">
77
+ <i class="fa-solid" [ngClass]="showPassword ? 'fa-eye' : 'fa-eye-slash'" aria-hidden="true"></i>
78
  </button>
79
+ </div>
80
+ <div *ngIf="capsLockOn" id="capsWarning" class="caps-lock-warning" role="status" aria-live="polite">Caps Lock is on</div>
81
+ <div *ngIf="errorMessage" class="signin-error-toast" role="alert" aria-live="assertive">{{ errorMessage }}</div>
82
  </div>
83
  </div>
84
+
85
  <div class="signin-row signin-options-row">
86
  <div class="remember-me">
87
+ <input id="rememberMe" name="rememberMe" type="checkbox" aria-label="Remember me" tabindex="0" />
88
+ <label for="rememberMe">Remember me</label>
 
 
 
89
  </div>
90
  <div class="forgot-password">
91
+ <a href="#" class="forgot-link" (click)="openForgotModal($event)" aria-label="Forgot password" tabindex="0">Forgot password?</a>
92
  </div>
93
  </div>
94
+
95
+ <button class="signin-btn ai-pulse" type="submit" [disabled]="loading" aria-label="Sign in">
96
  <ng-container *ngIf="!loading; else signingIn">
97
  Sign In
98
  </ng-container>
99
  <ng-template #signingIn>
100
+ <span class="spinner" aria-hidden="true"></span> Signing In...
101
  </ng-template>
102
+ </button>
103
 
104
  <!-- Divider with 'or' -->
105
  <div class="signin-divider-row">
 
109
  </div>
110
 
111
  <!-- Custom Google Sign-In button -->
112
+ <button class="google-btn" type="button" (click)="onGoogleSignIn()" aria-label="Sign in with Google">
113
  <img src="assets/google-logo.svg" alt="Google logo" class="google-logo" />
114
  <span>Log in with Google</span>
115
  </button>
116
 
117
+ <!-- SECURITY NOTE &2FA indicator only (no setup button) -->
118
+ <div class="signin-row security-row" style="margin-top:14px;">
119
+ <div class="security-info">
120
+ <i class="fa-solid fa-shield-halved" aria-hidden="true"></i>
121
+ <span class="security-text">Your data is encrypted and handled securely.</span>
122
+ <span class="twofa-indicator" *ngIf="twoFAEnabled">•2FA enabled</span>
123
+ </div>
124
+ </div>
125
+
126
  </form>
127
+
128
+ <div class="signin-help">Need help? <a href="#" (click)="openSupportModal($event)" aria-label="Contact support">Contact support</a> • <a href="/legal/terms" target="_blank" rel="noopener noreferrer">Terms</a> • <a href="/legal/privacy" target="_blank" rel="noopener noreferrer">Privacy</a></div>
129
+
130
+ <!-- Support Assistance Modal -->
131
+ <div *ngIf="showSupportModal" class="support-modal-bg">
132
+ <div class="support-modal">
133
+ <h3>Support Assistance</h3>
134
+ <p>If you need help with your account, login issues, or access questions, please contact our support team.</p>
135
+ <p><strong>Email:</strong> <a href="mailto:info@pykara.ai">info@pykara.ai </a></p>
136
+ <p><strong>Response Time:</strong> Within 24 hours (business days)</p>
137
+ <p>Please include your registered email and a short description of the issue.</p>
138
+ <b><button class="support-modal-close" (click)="closeSupportModal()"><b>Close</b></button></b>
139
+ </div>
140
+ </div>
141
+
142
+ <!--2FA Modal (centered over auth-card) -->
143
+ <div *ngIf="show2FAModal" class="otp-modal-bg">
144
+ <div class="forgot-modal otp-modal">
145
+ <h3>Enter verification code</h3>
146
+ <p *ngIf="twoFACodeSent">Enter the verification code sent to your email or phone.</p>
147
+ <div *ngIf="twoFACodeSent">
148
+ <input type="text" [(ngModel)]="twoFAInput" placeholder="Enter code" />
149
+ <div style="display:flex; gap:8px; margin-top:12px;">
150
+ <button class="signin-btn" (click)="verify2FACode()">Verify</button>
151
+ <button class="modal-close" (click)="cancel2FA()">Cancel</button>
152
+ </div>
153
+ </div>
154
+ <div *ngIf="twoFAMessage" style="margin-top:8px; color:#b0c7d4">{{ twoFAMessage }}</div>
155
+ </div>
156
+ </div>
157
+
158
  </div>
159
  </div>
160
  </div>
 
170
  <div class="signin-footer">
171
  <a href="#" (click)="flipToSignIn(); $event.preventDefault()">Back to Sign In</a>
172
  </div>
173
+ <div class="signin-help">Need help? <a href="mailto:info@pykara.ai">Contact support</a></div>
174
  </div>
175
 
176
  <div class="side-panel side-right">
177
+ <!-- Decorative background shapes -->
178
+ <div class="side-bg-shapes">
179
+ <div class="bg-circle circle1"></div>
180
+ <div class="bg-circle circle2"></div>
181
+ <div class="bg-circle circle3"></div>
182
+ <div class="bg-circle circle4"></div>
183
+ <div class="bg-circle circle5"></div>
184
+ <div class="bg-circle circle6"></div>
185
+ <div class="bg-circle circle7"></div>
186
+ <div class="bg-circle circle8"></div>
187
+ <div class="bg-circle circle9"></div>
188
+ <div class="bg-circle circle10"></div>
189
+ <div class="bg-circle circle11"></div>
190
+ <div class="bg-circle circle12"></div>
191
+ <div class="bg-circle circle13"></div>
192
+ <div class="bg-circle circle14"></div>
193
+ <!-- New large solid circles -->
194
+ <div class="bg-circle circle-large1"></div>
195
+ <div class="bg-circle circle-large2"></div>
196
+ <div class="bg-circle circle-large3"></div>
197
+ <div class="bg-circle circle-large4"></div>
198
+ <!-- More white rings for contrast -->
199
+ <div class="bg-ring ring1"></div>
200
+ <div class="bg-ring ring2"></div>
201
+ <div class="bg-ring ring3"></div>
202
+ <div class="bg-ring ring4"></div>
203
+ <div class="bg-ring ring5"></div>
204
+ <div class="bg-ring ring6"></div>
205
+ <div class="bg-ring ring7"></div>
206
+ <div class="bg-ring ring8"></div>
207
+ <div class="bg-ring ring9"></div>
208
+ </div>
209
+ <div class="side-info-box">
210
+ <!--<div class="welcome-back-title welcome-line-1">Start Your Journey</div>
211
+ <div class="welcome-back-desc welcome-line-2">
212
+ Create your account to access your workspace and continue your activities securely.
213
+ </div>
214
+ <div class="welcome-back-desc welcome-line-3">
215
+ Already have an account? Sign in.
216
+ </div>-->
217
+ <button class="action-btn welcome-line-4" type="button" (click)="goToLogin()">Sign In</button>
218
+ </div>
219
+ <!-- Replace /assets/side-placeholder-right.jpg with your chosen image -->
220
+ <img class="side-img" src="/assets/side-scale-right.jpg" alt="" />
 
 
 
 
221
  </div>
222
  </div>
223
  </div>
 
230
  <h3>Forgot Password</h3>
231
  <p>Enter your email to receive password reset instructions.</p>
232
  <input type="email" [(ngModel)]="forgotEmail" placeholder="Your email" />
233
+ <button class="send-reset-btn" (click)="sendForgotEmail()">Send Reset Link</button>
234
  <button class="modal-close" (click)="closeForgotModal()">Close</button>
235
  </div>
236
  </div>
src/app/homepage/sign-in/sign-in.component.ts CHANGED
@@ -1,21 +1,23 @@
1
  // sign-in.component.ts
2
- import { Component, ChangeDetectionStrategy, Output, EventEmitter } from '@angular/core';
3
  import { CommonModule } from '@angular/common';
4
  import { ReactiveFormsModule, FormBuilder, Validators, FormGroup, FormsModule } from '@angular/forms';
5
  import { Router, RouterLink } from '@angular/router';
6
  import { SignInService } from './sign-in.service';
7
- import { AuthService } from '../../auth.service'; // Import SignInService
8
  import { SignUpComponent } from '../sign-up/sign-up.component';
 
9
 
10
  @Component({
11
  selector: 'app-sign-in',
12
  standalone: true,
13
- imports: [CommonModule, ReactiveFormsModule, RouterLink, FormsModule, SignUpComponent],
14
  templateUrl: './sign-in.component.html',
15
  styleUrls: ['./sign-in.component.css'],
16
  changeDetection: ChangeDetectionStrategy.OnPush
17
  })
18
  export class SignInComponent {
 
19
  @Output() switchToSignUp = new EventEmitter<void>();
20
  @Output() close = new EventEmitter<void>();
21
  form: FormGroup;
@@ -23,55 +25,128 @@ export class SignInComponent {
23
  loading = false;
24
  errorMessage = '';
25
 
26
- // UI and template state
27
- isFlipped = false;
28
- typingTitle = '';
29
- showPassword = false;
30
- showForgotModal = false;
31
- forgotEmail = '';
 
32
 
33
- // Template methods
34
- flipToSignUp() {
35
- this.isFlipped = true;
36
- }
37
- flipToSignIn() {
38
- this.isFlipped = false;
39
- }
40
- togglePasswordVisibility() {
41
- this.showPassword = !this.showPassword;
42
- }
43
- openForgotModal(event?: Event) {
44
- if (event) event.preventDefault();
45
- this.showForgotModal = true;
46
- }
47
- closeForgotModal() {
48
- this.showForgotModal = false;
49
- this.forgotEmail = '';
50
- }
51
- sendForgotEmail() {
52
- this.closeForgotModal();
53
- alert('Password reset link sent to your email.');
54
- }
55
- onGoogleSignIn() {
56
- // Implement Google sign-in logic here
57
- console.log('Google sign-in clicked');
58
- }
59
- goToLogin() {
60
- this.flipToSignIn();
61
- }
62
 
63
  constructor(
64
  private fb: FormBuilder,
65
  private router: Router,
66
  private signInService: SignInService,
67
- private authService: AuthService
 
68
  ) {
69
  this.form = this.fb.group({
70
- email: ['', [Validators.required, Validators.email]], // Added email validation
71
  password: ['', [Validators.required]]
72
  });
73
  }
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  goToSignUp() {
76
  this.switchToSignUp.emit();
77
  }
@@ -87,28 +162,49 @@ export class SignInComponent {
87
  }
88
  this.loading = true;
89
  this.errorMessage = '';
 
90
  const payload = {
91
  email: this.form.get('email')?.value,
92
  password: this.form.get('password')?.value
93
  };
 
 
94
  this.signInService.signIn(payload).subscribe(
95
  (response) => {
96
  this.loading = false;
97
- console.log(response);
98
- // Store user info and navigate based on role
99
- this.handleSuccessfulLogin(response, payload.email);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  },
101
  (error) => {
102
  this.loading = false;
103
  console.log('Sign-in error:', error);
104
- if (error && error.status === 401) {
105
  this.errorMessage = 'Password is incorrect';
106
  } else {
107
  this.errorMessage = 'An error occurred. Please try again.';
108
  }
 
109
  setTimeout(() => {
110
  this.errorMessage = '';
111
- }, 3000);
 
112
  }
113
  );
114
  }
@@ -119,7 +215,6 @@ export class SignInComponent {
119
  const userData = response?.data?.user || response?.user || { email: email, role: userRole };
120
 
121
  // Store in localStorage
122
-
123
  localStorage.setItem('userRole', userRole);
124
  localStorage.setItem('user', JSON.stringify({
125
  email: email,
@@ -139,7 +234,11 @@ export class SignInComponent {
139
  console.log('User role detected:', userRole);
140
  console.log('Current URL before redirect:', window.location.href);
141
 
142
- if (userRole === 'admin') {
 
 
 
 
143
  console.log('Redirecting admin to infopage');
144
  this.router.navigate(['/infopage']).then((success) => {
145
  console.log('Navigation to infopage successful:', success);
@@ -156,4 +255,11 @@ export class SignInComponent {
156
  console.log('Sign-in component initialized');
157
  // Do not auto-redirect - user must manually login
158
  }
 
 
 
 
 
 
 
159
  }
 
1
  // sign-in.component.ts
2
+ import { Component, ChangeDetectionStrategy, Output, EventEmitter, ChangeDetectorRef, Input } from '@angular/core';
3
  import { CommonModule } from '@angular/common';
4
  import { ReactiveFormsModule, FormBuilder, Validators, FormGroup, FormsModule } from '@angular/forms';
5
  import { Router, RouterLink } from '@angular/router';
6
  import { SignInService } from './sign-in.service';
7
+ import { AuthService } from '../../auth.service'; // Import SignInService
8
  import { SignUpComponent } from '../sign-up/sign-up.component';
9
+ import { EyeToggleComponent } from '../../shared/eye-toggle/eye-toggle.component';
10
 
11
  @Component({
12
  selector: 'app-sign-in',
13
  standalone: true,
14
+ imports: [CommonModule, ReactiveFormsModule, RouterLink, FormsModule, SignUpComponent, EyeToggleComponent],
15
  templateUrl: './sign-in.component.html',
16
  styleUrls: ['./sign-in.component.css'],
17
  changeDetection: ChangeDetectionStrategy.OnPush
18
  })
19
  export class SignInComponent {
20
+ @Input() cardState?: 'signup' | 'signin';
21
  @Output() switchToSignUp = new EventEmitter<void>();
22
  @Output() close = new EventEmitter<void>();
23
  form: FormGroup;
 
25
  loading = false;
26
  errorMessage = '';
27
 
28
+ // UI and template state
29
+ isFlipped = false;
30
+ typingTitle = '';
31
+ showPassword = false;
32
+ showForgotModal = false;
33
+ showSupportModal = false; // added support assistance modal flag
34
+ forgotEmail = '';
35
 
36
+ // Caps lock state
37
+ capsLockOn = false;
38
+
39
+ //2FA state
40
+ show2FAModal = false;
41
+ twoFAEnabled = false;
42
+ twoFACodeSent = false;
43
+ twoFAInput = '';
44
+ twoFAMessage = '';
45
+
46
+ // store last auth response until OTP verified
47
+ private lastAuthResponse: any = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
  constructor(
50
  private fb: FormBuilder,
51
  private router: Router,
52
  private signInService: SignInService,
53
+ private authService: AuthService,
54
+ private cdr: ChangeDetectorRef
55
  ) {
56
  this.form = this.fb.group({
57
+ email: ['', [Validators.required, Validators.email]], // Added email validation
58
  password: ['', [Validators.required]]
59
  });
60
  }
61
 
62
+ // Template methods
63
+ flipToSignUp() {
64
+ this.isFlipped = true;
65
+ }
66
+ flipToSignIn() {
67
+ this.isFlipped = false;
68
+ }
69
+ togglePasswordVisibility() {
70
+ this.showPassword = !this.showPassword;
71
+ }
72
+ openForgotModal(event?: Event) {
73
+ if (event) event.preventDefault();
74
+ this.showForgotModal = true;
75
+ this.cdr.markForCheck();
76
+ }
77
+ closeForgotModal() {
78
+ this.showForgotModal = false;
79
+ this.forgotEmail = '';
80
+ this.cdr.markForCheck();
81
+ }
82
+ openSupportModal(event?: Event) { // new method to open support assistance modal
83
+ if (event) event.preventDefault();
84
+ this.showSupportModal = true;
85
+ this.cdr.markForCheck();
86
+ }
87
+ closeSupportModal() { // new method to close support assistance modal
88
+ this.showSupportModal = false;
89
+ this.cdr.markForCheck();
90
+ }
91
+ sendForgotEmail() {
92
+ this.closeForgotModal();
93
+ alert('Password reset link sent to your email.');
94
+ }
95
+ onGoogleSignIn() {
96
+ // Implement Google sign-in logic here
97
+ console.log('Google sign-in clicked');
98
+ }
99
+ goToLogin() {
100
+ this.flipToSignIn();
101
+ }
102
+
103
+ //2FA methods (mock)
104
+ start2FA() {
105
+ // kept for compatibility but not used in mock flow
106
+ this.show2FAModal = true;
107
+ this.twoFACodeSent = true;
108
+ this.twoFAMessage = 'Mock code sent to email/phone:123456';
109
+ this.cdr.markForCheck();
110
+ }
111
+ send2FACode() {
112
+ // mock sending code
113
+ this.twoFACodeSent = true;
114
+ this.twoFAMessage = 'Mock code sent to email/phone:123456';
115
+ this.cdr.markForCheck();
116
+ // keep message visible briefly
117
+ setTimeout(() => { this.twoFAMessage = ''; this.cdr.markForCheck(); },3500);
118
+ }
119
+ verify2FACode() {
120
+ // Verify mock OTP
121
+ if (this.twoFAInput && this.twoFAInput.trim() === '123456') {
122
+ this.twoFAMessage = '2FA verified. Signing you in...';
123
+ // finalize login using stored response (if any) or current form email
124
+ const email = this.form.get('email')?.value || '';
125
+ // call handleSuccessfulLogin with stored response or minimal payload
126
+ this.show2FAModal = false;
127
+ this.twoFACodeSent = false;
128
+ this.twoFAInput = '';
129
+ this.twoFAMessage = '';
130
+ this.cdr.markForCheck();
131
+
132
+ // If we have a lastAuthResponse, use it; otherwise create a minimal user object
133
+ const resp = this.lastAuthResponse || { user: { role: 'user', email } };
134
+ this.handleSuccessfulLogin(resp, email);
135
+ } else {
136
+ this.twoFAMessage = 'Invalid code. Please try again.';
137
+ this.cdr.markForCheck();
138
+ }
139
+ }
140
+ cancel2FA() {
141
+ this.show2FAModal = false;
142
+ this.twoFACodeSent = false;
143
+ this.twoFAInput = '';
144
+ this.twoFAMessage = '';
145
+ // clear stored response so user must re-authenticate
146
+ this.lastAuthResponse = null;
147
+ this.cdr.markForCheck();
148
+ }
149
+
150
  goToSignUp() {
151
  this.switchToSignUp.emit();
152
  }
 
162
  }
163
  this.loading = true;
164
  this.errorMessage = '';
165
+ this.cdr.markForCheck();
166
  const payload = {
167
  email: this.form.get('email')?.value,
168
  password: this.form.get('password')?.value
169
  };
170
+
171
+ // Call backend sign-in; for mock2FA we will show OTP modal after successful password auth
172
  this.signInService.signIn(payload).subscribe(
173
  (response) => {
174
  this.loading = false;
175
+ console.log('Sign-in response:', response);
176
+ // Determine2FA state from response
177
+ const user = response?.data?.user || response?.user || response;
178
+ const twoFAEnabled = !!(user?.twoFAEnabled || user?.twofaEnabled);
179
+ const twoFAMethod = (user?.twoFAMethod || user?.twofaMethod || '').toString();
180
+
181
+ if (twoFAEnabled && twoFAMethod) {
182
+ // store response until OTP verified
183
+ this.lastAuthResponse = response;
184
+ // show OTP modal only when2FA is configured
185
+ this.show2FAModal = true;
186
+ this.twoFACodeSent = true;
187
+ this.twoFAMessage = 'Mock code sent to email/phone:123456';
188
+ this.cdr.markForCheck();
189
+ } else {
190
+ // No2FA configured; proceed to login directly
191
+ const email = this.form.get('email')?.value || '';
192
+ this.handleSuccessfulLogin(response, email);
193
+ }
194
  },
195
  (error) => {
196
  this.loading = false;
197
  console.log('Sign-in error:', error);
198
+ if (error && error.status ===401) {
199
  this.errorMessage = 'Password is incorrect';
200
  } else {
201
  this.errorMessage = 'An error occurred. Please try again.';
202
  }
203
+ this.cdr.markForCheck();
204
  setTimeout(() => {
205
  this.errorMessage = '';
206
+ this.cdr.markForCheck();
207
+ },3000);
208
  }
209
  );
210
  }
 
215
  const userData = response?.data?.user || response?.user || { email: email, role: userRole };
216
 
217
  // Store in localStorage
 
218
  localStorage.setItem('userRole', userRole);
219
  localStorage.setItem('user', JSON.stringify({
220
  email: email,
 
234
  console.log('User role detected:', userRole);
235
  console.log('Current URL before redirect:', window.location.href);
236
 
237
+ // Normalize possible role keys from sign-up
238
+ const normalizedRole = (userRole || '').toLowerCase();
239
+ const isAdmin = normalizedRole === 'admin' || normalizedRole === 'adminothers' || normalizedRole === 'admin & others';
240
+
241
+ if (isAdmin) {
242
  console.log('Redirecting admin to infopage');
243
  this.router.navigate(['/infopage']).then((success) => {
244
  console.log('Navigation to infopage successful:', success);
 
255
  console.log('Sign-in component initialized');
256
  // Do not auto-redirect - user must manually login
257
  }
258
+
259
+ onPasswordKey(event: KeyboardEvent) {
260
+ const caps = event.getModifierState && event.getModifierState('CapsLock');
261
+ this.capsLockOn = !!caps;
262
+ // ensure template updates under OnPush
263
+ this.cdr.markForCheck();
264
+ }
265
  }
src/app/homepage/sign-in/sign-in.service.ts CHANGED
@@ -1,18 +1,15 @@
1
  import { Injectable } from '@angular/core';
2
  import { HttpClient } from '@angular/common/http';
3
  import { Observable } from 'rxjs';
4
- import { environment } from 'src/environments/environment'; // adjust path if needed
5
 
6
  @Injectable({
7
  providedIn: 'root'
8
  })
9
  export class SignInService {
10
 
11
- private apiUrl = environment.pyDetectApiUrl;
12
-
13
  constructor(private http: HttpClient) { }
14
 
15
  signIn(payload: any): Observable<any> {
16
- return this.http.post(`${this.apiUrl}/sign-in`, payload);
17
  }
18
  }
 
1
  import { Injectable } from '@angular/core';
2
  import { HttpClient } from '@angular/common/http';
3
  import { Observable } from 'rxjs';
 
4
 
5
  @Injectable({
6
  providedIn: 'root'
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:5002/sign-in', payload);
14
  }
15
  }
src/app/homepage/sign-up/sign-up.component.css CHANGED
@@ -47,6 +47,36 @@
47
  border-radius: 18px;
48
  box-shadow: 0 12px 48px rgba(2, 6, 23, 0.18), 0 0 0 2px rgba(56, 189, 248, 0.08);
49
  margin: 0 auto;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  }
51
 
52
  .create-title {
@@ -67,7 +97,8 @@
67
  letter-spacing: 1px;
68
  color: #23395d;
69
  text-shadow: 0 2px 8px #0008;
70
- /* animation: logoGlow 3.5s ease-in-out infinite alternate; */
 
71
  }
72
 
73
  @keyframes logoGlow {
@@ -82,12 +113,14 @@
82
 
83
  .create-form {
84
  width: 100%;
85
- max-width: 510px;
86
  display: grid;
87
  grid-template-columns: 1fr 1fr;
88
- gap: 12px 15px;
89
  align-items: start;
90
- margin-bottom: 14px;
 
 
91
  }
92
 
93
  .terms-info {
@@ -98,6 +131,7 @@
98
  margin: 12px 0 0 0;
99
  letter-spacing: 0.5px;
100
  display: block;
 
101
  }
102
 
103
  .form-row {
@@ -112,9 +146,11 @@
112
  }
113
 
114
  .form-field label {
115
- font-size: 1.05rem;
116
- font-weight: 700;
 
117
  color: #23395d;
 
118
  }
119
 
120
  .form-field input,
@@ -123,7 +159,7 @@
123
  color: #23395d;
124
  border: none;
125
  border-radius: 8px;
126
- padding: 12px 14px;
127
  font-size: 1rem;
128
  margin-bottom: 2px;
129
  box-shadow: 0 1px 4px #0002;
@@ -131,7 +167,7 @@
131
  width: 100%;
132
  min-width: 0;
133
  max-width: 100%;
134
- height: 46px;
135
  box-sizing: border-box;
136
  }
137
 
@@ -142,45 +178,301 @@
142
  box-shadow: 0 0 6px rgba(56, 189, 248, 0.5), 0 0 0 2px #1de9b688;
143
  }
144
 
 
 
 
 
 
145
  .form-field input::placeholder {
146
  color: #b0b8c1;
147
  opacity: 1;
148
  }
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  .form-checkbox {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  grid-column: 1 / -1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  display: flex;
153
- gap: 10px;
154
  align-items: center;
155
- color: #2b5160;
 
 
 
156
  margin-top: 8px;
 
157
  }
158
 
159
- .form-checkbox input[type="checkbox"] {
160
- width: 18px;
161
- height: 18px;
162
- accent-color: #38bdf8;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  }
164
 
 
 
 
 
 
165
  .create-btn {
166
- grid-column: 1 / -1;
167
- width: 100%;
168
  background: #23395d;
169
  color: #fff;
170
- padding: 14px 18px;
171
  border-radius: 10px;
172
  font-weight: 800;
173
  border: none;
174
  box-shadow: 0 10px 30px rgba(3, 20, 36, 0.32);
175
  cursor: pointer;
176
  font-size: 1.15rem;
177
- margin-top: 10px;
 
 
 
 
 
178
  }
179
 
180
- .create-btn:hover {
181
- background: #38bdf8;
182
- color: #fff;
183
- }
 
 
 
 
 
 
 
 
184
 
185
  .create-login-link {
186
  grid-column: 1 / -1;
@@ -199,15 +491,18 @@
199
  text-align: center;
200
  color: #010207;
201
  font-size: 0.9rem;
202
- margin-top: 16px;
203
  }
204
 
205
  .form-field .error {
206
  color: #ff5252;
207
  font-size: 0.85rem;
208
  margin-top: 0px;
 
209
  }
210
 
 
 
211
  .welcome-info-box {
212
  position: absolute;
213
  top: 32px;
@@ -228,7 +523,7 @@
228
 
229
  .welcome-info-desc {
230
  font-size: 1.08rem;
231
- color: #e0f7fa;
232
  margin-bottom: 8px;
233
  }
234
 
@@ -244,6 +539,41 @@
244
  color: #23395d;
245
  }
246
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  /* Extra whitespace and centering for small screens */
248
  @media (max-width: 900px) {
249
  .signup-container {
@@ -308,364 +638,374 @@
308
  color: #18314a;
309
  }
310
 
311
- /* Eye toggle inside password fields: match sign-in positioning */
312
- .form-field .eye-toggle {
313
- position: absolute;
314
- right: 12px;
315
- top: 38px;
316
- background: none;
317
- border: none;
318
- font-size: 1.3em;
319
- color: #888;
320
- cursor: pointer;
321
- z-index: 2;
322
- padding: 0;
323
- line-height: 1;
324
- opacity: 0.9;
325
- transition: color 0.2s, opacity 0.2s;
326
  }
327
 
328
- .form-field .eye-toggle:hover {
329
- color: #555;
330
- opacity: 1;
331
- }
332
-
333
- .form-field .eye-toggle:focus {
334
- outline: none;
335
- }
336
 
337
- /* Ensure button SVG scales nicely */
338
- .form-field .eye-toggle svg {
339
- width: 22px;
340
- height: 22px;
341
- }
342
 
343
- /* Small screen tweak: move eye toggle slightly up if spacing differs */
344
- @media (max-width: 700px) {
345
- .form-field .eye-toggle {
346
- top: 28px;
347
- }
348
  }
349
 
350
- .fact-rotator {
351
- font-size: 1.18rem;
352
- font-weight: 700;
353
- color: #fff;
354
- margin-bottom: 18px;
355
- min-height: 32px;
356
- text-align: center;
357
- transition: opacity 0.6s;
358
- letter-spacing: 0.5px;
359
- animation: fadeFact 0.6s;
360
  }
361
 
362
- @keyframes fadeFact {
363
- from {
364
- opacity: 0;
365
- }
366
-
367
- to {
368
- opacity: 1;
369
- }
 
 
370
  }
371
 
372
- .side-panel.side-right {
373
- position: relative;
374
- overflow: hidden;
375
- display: flex;
376
- align-items: center;
377
- justify-content: center;
378
- background: linear-gradient(135deg, #38bdf8 0%, #7b2ff2 100%);
379
- min-height: 100%;
380
  }
381
 
382
- .side-panel.side-left {
383
- position: relative;
384
- overflow: hidden;
385
- display: flex;
386
- align-items: center;
387
- justify-content: center;
388
-
389
- min-height: 100%;
390
  }
391
 
392
- .wave-bg {
393
- position: absolute;
394
- inset: 0;
395
- width: 100%;
396
- height: 100%;
397
- z-index: 1;
398
- overflow: hidden;
399
  }
400
 
401
- .wave-svg {
402
- position: absolute;
403
- top: 0;
404
- left: 0;
405
- width: 100%;
406
- height: 100%;
407
- z-index: 1;
 
 
 
408
  }
409
 
410
- .circle {
411
- position: absolute;
412
- border-radius: 50%;
413
- background: linear-gradient(135deg, #38bdf8 0%, #7b2ff2 100%);
414
- opacity: 0.85;
415
  }
416
 
417
- .circle1 {
418
- width: 90px;
419
- height: 90px;
420
- top: 80px;
421
- left: 60px;
422
- box-shadow: 0 0 32px #7b2ff2aa;
423
- }
424
 
425
- .circle2 {
426
- width: 60px;
427
- height: 60px;
428
- top: 220px;
429
- left: 220px;
430
- box-shadow: 0 0 24px #38bdf8aa;
 
 
431
  }
432
 
433
- .circle3 {
434
- width: 120px;
435
- height: 120px;
436
- bottom: 40px;
437
- left: 120px;
438
- box-shadow: 0 0 48px #7b2ff2aa;
439
- }
440
 
441
- .circle4 {
442
- width: 36px;
443
- height: 36px;
444
- bottom: 80px;
445
- right: 60px;
446
- box-shadow: 0 0 12px #38bdf8aa;
 
 
 
 
 
447
  }
448
 
449
- .welcome-content {
450
- position: relative;
451
- z-index: 2;
452
- width: 100%;
453
- text-align: center;
 
454
  display: flex;
455
- flex-direction: column;
456
  align-items: center;
457
  justify-content: center;
458
- margin-top: 80px;
459
  }
460
 
461
- .welcome-title {
462
- font-size: 2.2rem;
463
- font-weight: 800;
464
- color: #fff;
465
- margin-bottom: 18px;
466
- text-shadow: 0 2px 16px #0006;
 
 
 
 
 
 
 
467
  }
468
 
469
- .welcome-subtitle {
470
  font-size: 1.08rem;
471
- color: #e0e7ef;
472
- margin-bottom: 32px;
473
- letter-spacing: 1px;
474
- text-shadow: 0 2px 8px #0004;
 
 
 
 
 
 
475
  }
476
 
477
- .welcome-footer {
478
- font-size: 1.05rem;
 
 
 
479
  color: #fff;
480
- opacity: 0.7;
481
- margin-top: 120px;
482
- letter-spacing: 2px;
 
 
 
 
483
  }
484
 
485
- /* Glossy info popup for role info */
486
- .role-info-popup-bg {
487
  position: fixed;
488
  inset: 0;
489
- background: rgba(30, 41, 59, 0.55);
490
- backdrop-filter: blur(1px);
491
- z-index: 0;
492
  display: flex;
493
  align-items: center;
494
- justify-content: flex-end;
495
- padding: 48px 56px 48px 24px;
496
- animation: fadeInModalBg 0.3s;
497
  }
498
 
499
- @keyframes fadeInModalBg {
500
- from {
501
- opacity: 0;
502
- }
503
-
504
- to {
505
- opacity: 1;
506
- }
 
 
507
  }
508
 
509
- @keyframes popupOpen {
510
- 0% {
511
- opacity: 0;
512
- transform: scale(0.92) translateY(24px);
513
- }
514
-
515
- 100% {
516
- opacity: 1;
517
- transform: scale(1) translateY(0);
518
- }
 
 
 
519
  }
520
 
521
- .role-info-popup {
522
- background: rgba(255, 255, 255, 0.92);
523
- border-radius: 4px;
524
- box-shadow: 0 8px 32px #38bdf844 0 24px #1e293b88;
525
- padding: 22px 28px 18px 28px;
526
- min-width: 220px;
527
- max-width: 90vw;
528
- text-align: left;
529
- z-index: 3001;
530
- font-size: 0.98em;
531
- color: #23395d;
532
- letter-spacing: 0.2px;
533
- line-height: 1.5;
534
- position: relative;
535
- font-family: inherit;
536
- opacity: 1;
537
- animation: popupOpen 1.2s cubic-bezier(.22, .9, .32, 1) both;
538
  }
539
 
540
- .role-info-popup .close-btn {
541
- position: absolute;
542
- top: 8px;
543
- right: 8px;
544
- width: 24px;
545
- height: 24px;
546
- border: none;
547
- background: #14263c;
548
- color: #fff;
549
- border-radius: 50%;
550
- font-size: 1.1rem;
551
- font-weight: bold;
552
- display: flex;
553
- align-items: center;
554
- justify-content: center;
555
- cursor: pointer;
556
- z-index: 10;
557
- transition: background 0.2s, color 0.2s;
558
- box-shadow: 0 2px 8px #0005;
559
- }
560
-
561
- .role-info-popup .close-btn:hover {
562
- background: #38bdf8;
563
- color: #18314a;
564
- }
565
 
566
- .role-info-btn {
567
- background: none;
568
- border: none;
569
- color: #38bdf8;
570
- font-size: 1.15em;
571
- cursor: pointer;
572
- margin-left: 6px;
573
- vertical-align: middle;
574
- padding: 0;
575
- display: inline-flex;
576
- align-items: center;
577
- justify-content: center;
578
- transition: color 0.2s;
579
  }
580
 
581
- .role-info-btn:hover {
582
- color: #137ec4;
 
 
583
  }
584
 
585
- /* Spinner for loading state on buttons */
586
- .spinner {
587
- display: inline-block;
588
- width:18px;
589
- height:18px;
590
- border:3px solid #fff;
591
- border-top:3px solid #38bdf8;
592
- border-radius:50%;
593
- animation: spin0.7s linear infinite;
594
- vertical-align: middle;
595
- margin-right:8px;
596
- }
597
- @keyframes spin {
598
- 0% { transform: rotate(0deg);}
599
- 100% { transform: rotate(360deg);}
600
- }
601
 
602
- /* Info button and floating info popup styles */
603
- .info-btn {
604
- background: #38bdf8;
605
- color: #fff;
606
- border: none;
607
- border-radius:50%;
608
- width:28px;
609
- height:28px;
610
- font-size:1.1rem;
611
- font-weight: bold;
612
- cursor: pointer;
613
- margin-left:8px;
614
  }
615
 
616
- .info-popup-bg {
617
- position: fixed;
618
- inset:0;
619
- background: rgba(30,41,59,0.45);
620
- backdrop-filter: blur(2px);
621
- z-index:;
622
  display: flex;
 
623
  align-items: center;
624
- justify-content: center;
625
- }
626
-
627
- .info-popup {
628
- background: rgba(255,255,255,0.85);
629
- border-radius: 16px;
630
- box-shadow: 08px 32px #38bdf844,0024px #1e293b88;
631
- padding: 24px 28px 18px 28px;
632
- min-width: 320px;
633
- max-width: 90vw;
634
- text-align: left;
635
- font-size: 0.98rem;
636
- color: #23395d;
637
- position: relative;
638
- font-family: inherit;
639
- left: 840px;
640
  }
641
-
642
- .info-title {
643
- font-size:1.08rem;
644
- font-weight:700;
645
- margin-bottom:8px;
646
- color: #38bdf8;
647
- letter-spacing:0.5px;
648
- }
649
-
650
- .info-text {
651
- font-size:0.95rem;
652
  color: #23395d;
653
- opacity:0.95;
654
  }
655
-
656
- .info-close {
657
- position: absolute;
658
- top:8px;
659
- right:12px;
660
- background: none;
661
- border: none;
662
- font-size:1.5rem;
663
- color: #38bdf8;
664
- cursor: pointer;
665
- font-weight: bold;
666
- opacity:0.7;
667
- transition: opacity 0.2s;
668
- }
669
- .info-close:hover {
670
- opacity:1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
671
  }
 
47
  border-radius: 18px;
48
  box-shadow: 0 12px 48px rgba(2, 6, 23, 0.18), 0 0 0 2px rgba(56, 189, 248, 0.08);
49
  margin: 0 auto;
50
+ position: relative;
51
+ z-index: 2;
52
+ transform: translateY(-8px);
53
+ box-shadow: 0 14px 40px rgba(2, 6, 23, 0.18);
54
+ transition: transform 220ms ease, box-shadow 220ms ease;
55
+ }
56
+
57
+ .create-card:hover {
58
+ transform: translateY(-12px);
59
+ box-shadow: 0 20px 60px rgba(2, 6, 23, 0.22);
60
+ }
61
+
62
+ @media (max-width: 700px) {
63
+ .create-card {
64
+ transform: none;
65
+ box-shadow: 0 8px 24px rgba(2, 6, 23, 0.12);
66
+ }
67
+
68
+ .create-card:hover {
69
+ transform: none;
70
+ box-shadow: 0 8px 24px rgba(2, 6, 23, 0.12);
71
+ }
72
+ }
73
+
74
+ @media (prefers-reduced-motion: reduce) {
75
+ .create-card,
76
+ .create-card:hover {
77
+ transition: none;
78
+ transform: none;
79
+ }
80
  }
81
 
82
  .create-title {
 
97
  letter-spacing: 1px;
98
  color: #23395d;
99
  text-shadow: 0 2px 8px #0008;
100
+ margin-top:24px;
101
+ /* animation: logoGlow 3.5s ease-in-out infinite; */
102
  }
103
 
104
  @keyframes logoGlow {
 
113
 
114
  .create-form {
115
  width: 100%;
116
+ max-width: 580px;
117
  display: grid;
118
  grid-template-columns: 1fr 1fr;
119
+ gap: 16px 16px;
120
  align-items: start;
121
+ margin-bottom: 10px;
122
+ padding: 25px;
123
+ padding-top: 0px;
124
  }
125
 
126
  .terms-info {
 
131
  margin: 12px 0 0 0;
132
  letter-spacing: 0.5px;
133
  display: block;
134
+ margin-left: 25px !important; /* align with .twofa-methods and .form-checkbox */
135
  }
136
 
137
  .form-row {
 
146
  }
147
 
148
  .form-field label {
149
+ color: #e6f7f9;
150
+ font-size: 1rem;
151
+ font-weight: 600;
152
  color: #23395d;
153
+ letter-spacing: 0.5px;
154
  }
155
 
156
  .form-field input,
 
159
  color: #23395d;
160
  border: none;
161
  border-radius: 8px;
162
+ padding: 3px 10px 3px 10px;
163
  font-size: 1rem;
164
  margin-bottom: 2px;
165
  box-shadow: 0 1px 4px #0002;
 
167
  width: 100%;
168
  min-width: 0;
169
  max-width: 100%;
170
+ height: 36px;
171
  box-sizing: border-box;
172
  }
173
 
 
178
  box-shadow: 0 0 6px rgba(56, 189, 248, 0.5), 0 0 0 2px #1de9b688;
179
  }
180
 
181
+ .form-field input:focus {
182
+ box-shadow: 006px rgba(14,165,164,0.08);
183
+ border-color: #0ea5a4;
184
+ }
185
+
186
  .form-field input::placeholder {
187
  color: #b0b8c1;
188
  opacity: 1;
189
  }
190
 
191
+ /* Reserve extra space when an eye toggle is present so text doesn't overlap and layout stays stable */
192
+ .form-field.has-eye input {
193
+ padding-right: 46px; /* enough for the eye button + spacing */
194
+ }
195
+
196
+ /* Anchor wrapper for inputs that include an eye toggle */
197
+ .input-with-eye {
198
+ position: relative;
199
+ display: block;
200
+ height: 46px; /* match input height so the eye is vertically stable */
201
+ box-sizing: border-box;
202
+ }
203
+
204
+ /* Ensure the input fills the wrapper and reserves space for the eye */
205
+ .input-with-eye input {
206
+ padding-right: 50px; /* space for the eye */
207
+ height: 79%;
208
+ box-sizing: border-box;
209
+ }
210
+
211
+ /* More specific selector so the eye is positioned relative to the wrapper and won't shift */
212
+ .input-with-eye .eye-toggle {
213
+ position: absolute;
214
+ right: 10px;
215
+ top: 41%;
216
+ transform: translateY(-50%);
217
+ border: none;
218
+ width: 32px;
219
+ height: 32px;
220
+ display: flex;
221
+ align-items: center;
222
+ justify-content: center;
223
+ border-radius: 6px;
224
+ cursor: pointer;
225
+ color: #23395d;
226
+ z-index: 2; /* keep above input */
227
+ pointer-events: auto;
228
+ }
229
+
230
+ /* Keep the original .form-field .eye-toggle as a fallback but prefer the wrapper-based positioning */
231
+ .form-field .eye-toggle {
232
+ /* fallthrough for any other usages; do not change */
233
+ }
234
+
235
+ /* Eye toggle inside password field for sign-up: vertically center and avoid movement */
236
+ .form-field .eye-toggle {
237
+ position: absolute;
238
+ right: 0px;
239
+ top: 41%;
240
+ transform: translateY(-50%);
241
+ /* background: #fff;*/
242
+ border: transparent;
243
+ width: 32px;
244
+ height: 32px;
245
+ display: flex;
246
+ background: transparent;
247
+ align-items: center;
248
+ justify-content: center;
249
+ border-radius: 6px;
250
+ cursor: pointer;
251
+ color: #23395d;
252
+ z-index: 0;
253
+ pointer-events: auto;
254
+ }
255
+ .form-field .eye-toggle i {
256
+ font-size: 0.95rem;
257
+ line-height: 1;
258
+ }
259
+
260
+ .form-field .eye-toggle:focus {
261
+ outline: 2px solid rgba(29,233,182,0.12);
262
+ }
263
+
264
  .form-checkbox {
265
+ display: flex;
266
+ gap: 10px;
267
+ align-items: center;
268
+ color: #2b5160;
269
+ margin-top: -19px; /* normalized spacing */
270
+ margin-left: 25px !important; /* enforce consistent left alignment */
271
+ }
272
+
273
+ .form-checkbox input[type="checkbox"] {
274
+ width: 20px;
275
+ height: 20px;
276
+ min-width: 20px;
277
+ min-height: 20px;
278
+ margin: 0; /* gap handles spacing */
279
+ padding: 0;
280
+ box-sizing: border-box;
281
+ vertical-align: middle;
282
+ appearance: none;
283
+ -webkit-appearance: none;
284
+ background: #fff;
285
+ border: 2px solid #cbd5e1; /* subtle border */
286
+ border-radius: 4px;
287
+ display: inline-block;
288
+ position: relative;
289
+ cursor: pointer;
290
+ margin-bottom: 0px;
291
+ }
292
+ input#twoFAOptIn {
293
+ margin-top: 0; /* remove extra offset which caused misalignment */
294
+ }
295
+
296
+ /* Checked state - show a simple tick using box-shadow trick (keeps no extra elements) */
297
+ .form-checkbox input[type="checkbox"]:checked {
298
+ background: linear-gradient(180deg, #38bdf8, #137ec4);
299
+ border-color: #137ec4;
300
+ }
301
+
302
+ .form-checkbox input[type="checkbox"]:checked::after {
303
+ content: '\2713'; /* check mark */
304
+ color: #083344;
305
+ font-weight: 700;
306
+ font-size: 14px;
307
+ position: absolute;
308
+ top: 50%;
309
+ left: 50%;
310
+ transform: translate(-50%, -58%);
311
+ line-height: 1;
312
+ }
313
+
314
+ /* Keep label aligned and allow wrapping */
315
+ .form-checkbox label {
316
+ display: inline-block;
317
+ line-height: 1.2;
318
+ cursor: pointer;
319
+ }
320
+
321
+ /* New: 2FA styles moved from inline */
322
+ .twofa-field {
323
  grid-column: 1 / -1;
324
+ padding-top: 0px;
325
+ margin-left: 0;
326
+ }
327
+
328
+ .twofa-section {
329
+ margin-top: 6px;
330
+ }
331
+
332
+ .twofa-label-bold {
333
+ font-weight: 700;
334
+ display: block;
335
+ margin-bottom: 6px;
336
+ margin-left: 25px; /* match .form-checkbox and form padding */
337
+ }
338
+
339
+ .twofa-methods {
340
+ display: flex;
341
+ gap: 12px;
342
+ align-items: center;
343
+ margin-left: 25px !important; /* start at same x as checkbox label */
344
+ }
345
+
346
+ .inline-control {
347
  display: flex;
348
+ gap: 6px;
349
  align-items: center;
350
+ }
351
+
352
+ .twofa-email-options .twofa-alt-email,
353
+ .twofa-sms-options {
354
  margin-top: 8px;
355
+ margin-left: 25px !important; /* align alt inputs with checkbox label */
356
  }
357
 
358
+ .twofa-alt-email input,
359
+ .twofa-sms-options input {
360
+ width: 100%;
361
+ background: #fff;
362
+ border: none;
363
+ border-radius: 8px;
364
+ padding: 6px 10px;
365
+ height: 36px;
366
+ box-shadow: 0 1px 4px #0002;
367
+ }
368
+
369
+ .twofa-alt-email input:focus,
370
+ .twofa-sms-options input:focus {
371
+ outline: 2px solid #1de9b6;
372
+ box-shadow: 0 0 6px rgba(56, 189, 248, 0.5);
373
+ }
374
+
375
+ /* Reduce SMS input width only (override the earlier100% width) */
376
+ .twofa-sms-options input {
377
+ width:260px; /* reduced fixed width for SMS input */
378
+ max-width:100%;
379
+ }
380
+
381
+ .twofa-sms-options label {
382
+ display: block;
383
+ margin-bottom:6px; /* small gap -- reduced */
384
+ font-weight:600;
385
+ color: #23395d;
386
+ }
387
+
388
+ .twofa-sms-options input {
389
+ margin-top:4px; /* tiny gap to bring input closer to label */
390
+ }
391
+
392
+ .twofa-error {
393
+ display: block;
394
+ margin-top: 8px;
395
+ }
396
+
397
+ .twofa-sms-options input[readonly] {
398
+ background: #f3f4f6;
399
+ }
400
+
401
+ /* 2FA single-line row: checkbox + method radios aligned */
402
+ .twofa-row {
403
+ display: flex;
404
+ align-items: center;
405
+ gap:14px; /* small gap between checkbox label and method controls */
406
+ }
407
+
408
+ /* ensure checkbox block has same spacing as other checkboxes */
409
+ .twofa-checkbox {
410
+ margin-left:0px !important;
411
+ margin-top:0px;
412
+ }
413
+
414
+ /* plain control: no extra background, keep text and control inline */
415
+ .plain-control {
416
+ background: transparent;
417
+ padding:0;
418
+ margin:0;
419
+ display: inline-flex;
420
+ align-items: center;
421
+ gap:8px;
422
+ color: inherit;
423
+ }
424
+
425
+ /* ensure radios align vertically with checkbox center */
426
+ .plain-control input[type="radio"] {
427
+ width:16px;
428
+ height:16px;
429
+ margin:0;
430
+ }
431
+
432
+ /* Small responsive tweaks */
433
+ @media (max-width: 900px) {
434
+ .twofa-methods {
435
+ flex-direction: column;
436
+ align-items: flex-start;
437
  }
438
 
439
+ .twofa-field .form-checkbox {
440
+ margin-left: 0;
441
+ }
442
+ }
443
+
444
  .create-btn {
445
+ width: 50%;
446
+ max-width: 320px;
447
  background: #23395d;
448
  color: #fff;
449
+ padding: 12px 18px;
450
  border-radius: 10px;
451
  font-weight: 800;
452
  border: none;
453
  box-shadow: 0 10px 30px rgba(3, 20, 36, 0.32);
454
  cursor: pointer;
455
  font-size: 1.15rem;
456
+ margin-top: 100px;
457
+ margin-left: 0; /* remove previous fixed offset */
458
+ margin-right: 0;
459
+ margin-bottom: 0;
460
+ display: block;
461
+ margin-inline: auto; /* center horizontally */
462
  }
463
 
464
+ .create-btn:hover {
465
+ background: #38bdf8;
466
+ color: #fff;
467
+ }
468
+
469
+ /* Grey out disabled create button */
470
+ .create-btn[disabled] {
471
+ background: #b8c6d6;
472
+ color: #fff;
473
+ cursor: not-allowed;
474
+ box-shadow: none;
475
+ }
476
 
477
  .create-login-link {
478
  grid-column: 1 / -1;
 
491
  text-align: center;
492
  color: #010207;
493
  font-size: 0.9rem;
494
+ margin-top: 8px;
495
  }
496
 
497
  .form-field .error {
498
  color: #ff5252;
499
  font-size: 0.85rem;
500
  margin-top: 0px;
501
+ margin-left: 10px;
502
  }
503
 
504
+
505
+
506
  .welcome-info-box {
507
  position: absolute;
508
  top: 32px;
 
523
 
524
  .welcome-info-desc {
525
  font-size: 1.08rem;
526
+ color: #fff;
527
  margin-bottom: 8px;
528
  }
529
 
 
539
  color: #23395d;
540
  }
541
 
542
+ .rotation-container {
543
+ display: flex;
544
+ align-items: center;
545
+ justify-content: center;
546
+ position: absolute;
547
+ inset: 0;
548
+ pointer-events: none;
549
+ }
550
+
551
+ .rotation-item {
552
+ display: inline-block;
553
+ will-change: transform;
554
+ pointer-events: auto;
555
+ position: relative;
556
+ }
557
+
558
+ @keyframes rotateAnimation {
559
+ 0% {
560
+ transform: rotate(0deg);
561
+ }
562
+
563
+ 100% {
564
+ transform: rotate(1turn);
565
+ }
566
+ }
567
+
568
+ .rotation-item:nth-child(1) {
569
+ animation: rotateAnimation 24s linear infinite;
570
+ z-index: 1;
571
+ }
572
+
573
+ .rotation-item:nth-child(2) {
574
+ animation: rotateAnimation 36s linear infinite;
575
+ }
576
+
577
  /* Extra whitespace and centering for small screens */
578
  @media (max-width: 900px) {
579
  .signup-container {
 
638
  color: #18314a;
639
  }
640
 
641
+ .side-bg-shapes,
642
+ .bg-circle,
643
+ .bg-ring,
644
+ .circle,
645
+ .circle-large1,
646
+ .circle-large2,
647
+ .circle-large3,
648
+ .circle-large4 {
649
+ display: none !important;
650
+ opacity:0 !important;
651
+ pointer-events: none !important;
652
+ animation: none !important;
 
 
 
653
  }
654
 
655
+ @keyframes floatSlow { }
656
+ @keyframes floatSlowReverse { }
657
+ @keyframes ringPulse { }
 
 
 
 
 
658
 
659
+ .side-panel.side-right .side-img { display:block !important; position:absolute; top:0; left:0; width:100%; height:100%; object-fit:cover; z-index:0; }
660
+ .side-panel.side-right .signup-panel-right, .side-panel.side-right .signup-panel-left { position: relative; z-index:1; }
 
 
 
661
 
662
+ .welcome-back-title,
663
+ .welcome-info-title {
664
+ color: #ffffff !important;
 
 
665
  }
666
 
667
+ .side-panel.side-right .side-img {
668
+ z-index:0 !important; /* image underlay */
 
 
 
 
 
 
 
 
669
  }
670
 
671
+ .side-panel.side-right .side-info-box,
672
+ .side-panel.side-right .side-welcome-overlay,
673
+ .welcome-info-box,
674
+ .welcome-back-title,
675
+ .welcome-info-title,
676
+ .side-panel.side-right .welcome-back-title {
677
+ position: relative !important;
678
+ z-index:5 !important; /* bring text above image */
679
+ color: #ffffff !important; /* white text */
680
+ text-shadow: 0 2px 8px rgba(0,0,0,0.6) !important; /* improve contrast */
681
  }
682
 
683
+ .side-panel.side-right .side-info-box p,
684
+ .side-panel.side-right .side-welcome-overlay p,
685
+ .welcome-info-box p,
686
+ .welcome-info-desc {
687
+ color: #ffffff !important;
688
+ text-shadow: 0 1px 6px rgba(0,0,0,0.45) !important;
 
 
689
  }
690
 
691
+ .side-panel.side-right .action-btn,
692
+ .side-panel.side-right .panel-cta,
693
+ .welcome-line-4,
694
+ .side-panel.side-right .action-btn {
695
+ position: relative;
696
+ z-index:6 !important;
 
 
697
  }
698
 
699
+ .side-panel.side-right .side-welcome-overlay,
700
+ .side-panel.side-right .side-info-box,
701
+ .welcome-info-box {
702
+ position: relative !important;
703
+ z-index:10 !important;
704
+ color: #ffffff !important;
 
705
  }
706
 
707
+ .side-panel.side-right .side-welcome-overlay .welcome-back-title,
708
+ .side-panel.side-right .side-info-box .welcome-back-title,
709
+ .welcome-info-box .welcome-back-title,
710
+ .side-panel.side-right .side-welcome-overlay .welcome-back-desc,
711
+ .side-panel.side-right .side-info-box .welcome-back-desc,
712
+ .welcome-info-box .welcome-info-desc,
713
+ .side-panel.side-right .side-welcome-overlay .welcome-line-3,
714
+ .side-panel.side-right .side-info-box .welcome-line-3 {
715
+ color: #ffffff !important;
716
+ text-shadow: 02px 8px rgba(0,0,0,0.6) !important;
717
  }
718
 
719
+ .side-panel.side-right .side-welcome-overlay a,
720
+ .side-panel.side-right .side-info-box a,
721
+ .welcome-info-box a {
722
+ color: #ffffff !important;
723
+ text-decoration: underline;
724
  }
725
 
 
 
 
 
 
 
 
726
 
727
+ @media (max-width:900px) {
728
+ .side-panel.side-right .side-info-box,
729
+ .welcome-info-box { max-width:92% !important; padding:12px !important; }
730
+ .side-panel.side-right .side-info-box .side-info-title,
731
+ .welcome-info-box .welcome-info-title { font-size:1.25rem !important; }
732
+ .side-panel.side-right .side-info-box .side-info-desc,
733
+ .welcome-info-box .welcome-info-desc { font-size:0.98rem !important; }
734
+ .side-panel.side-right .side-info-box .action-btn { padding:8px 12px !important; }
735
  }
736
 
 
 
 
 
 
 
 
737
 
738
+ .info-btn {
739
+ background: #23395d;
740
+ color: #fff;
741
+ border: none;
742
+ border-radius: 20%;
743
+ width: 20px;
744
+ height: 20px;
745
+ font-size: 1.1rem;
746
+ font-weight: bold;
747
+ cursor: pointer;
748
+ margin-left: 1px;
749
  }
750
 
751
+ .info-popup-bg {
752
+ position: fixed;
753
+ inset: 0;
754
+ background: rgba(30,41,59,0.45);
755
+ backdrop-filter: blur(2px);
756
+ z-index:;
757
  display: flex;
 
758
  align-items: center;
759
  justify-content: center;
 
760
  }
761
 
762
+ .info-popup {
763
+ background: rgba(255,255,255,0.85);
764
+ border-radius: 16px;
765
+ box-shadow: 08px 32px #38bdf844, 0024px #1e293b88;
766
+ padding: 24px 28px 18px 28px;
767
+ min-width: 320px;
768
+ max-width: 90vw;
769
+ text-align: left;
770
+ font-size: 0.98rem;
771
+ color: #23395d;
772
+ position: relative;
773
+ font-family: inherit;
774
+ left: 840px;
775
  }
776
 
777
+ .info-title {
778
  font-size: 1.08rem;
779
+ font-weight: 700;
780
+ margin-bottom: 8px;
781
+ color: #38bdf8;
782
+ letter-spacing: 0.5px;
783
+ }
784
+
785
+ .info-text {
786
+ font-size: 0.95rem;
787
+ color: #23395d;
788
+ opacity: 0.95;
789
  }
790
 
791
+ .info-close {
792
+ position: absolute;
793
+ top: 8px;
794
+ right: 10px;
795
+ background: #38bdf8;
796
  color: #fff;
797
+ border: none;
798
+ width: 26px;
799
+ height: 26px;
800
+ border-radius: 50%;
801
+ font-size: 1rem;
802
+ line-height: 1;
803
+ cursor: pointer;
804
  }
805
 
806
+ /* Floating info popup (small) */
807
+ .info-popup-bg {
808
  position: fixed;
809
  inset: 0;
 
 
 
810
  display: flex;
811
  align-items: center;
812
+ justify-content: center;
813
+ z-index: 1200;
 
814
  }
815
 
816
+ .info-popup {
817
+ background: rgba(255,255,255,0.98);
818
+ width: 420px;
819
+ max-width: calc(100% -48px);
820
+ border-radius: 12px;
821
+ padding: 14px 18px;
822
+ box-shadow: 0 12px 30px rgba(2,6,23,0.18);
823
+ position: relative;
824
+ color: #23395d;
825
+ font-size: 0.95rem;
826
  }
827
 
828
+ .info-close {
829
+ position: absolute;
830
+ top: 8px;
831
+ right: 10px;
832
+ background: #38bdf8;
833
+ color: #fff;
834
+ border: none;
835
+ width: 26px;
836
+ height: 26px;
837
+ border-radius: 50%;
838
+ font-size: 1rem;
839
+ line-height: 1;
840
+ cursor: pointer;
841
  }
842
 
843
+ .info-title {
844
+ color: #38bdf8;
845
+ font-weight: 800;
846
+ margin-bottom: 8px;
847
+ padding-left: 6px;
 
 
 
 
 
 
 
 
 
 
 
 
848
  }
849
 
850
+ .info-text ul {
851
+ list-style: disc;
852
+ padding-left: 22px;
853
+ margin: 6px 0;
854
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
855
 
856
+ .info-text li {
857
+ margin-bottom: 8px;
858
+ line-height: 1.35;
 
 
 
 
 
 
 
 
 
 
859
  }
860
 
861
+ .info-text li strong {
862
+ display: inline-block;
863
+ width: 120px;
864
+ font-weight: 800;
865
  }
866
 
867
+ /* Ensure popup scales on small screens */
868
+ @media (max-width:520px) {
869
+ .role-help-modal, .info-popup {
870
+ width: auto;
871
+ margin: 016px;
872
+ padding: 12px;
873
+ }
 
 
 
 
 
 
 
 
 
874
 
875
+ .role-help-list li strong, .info-text li strong {
876
+ display: block;
877
+ width: auto;
878
+ margin-bottom: 4px;
879
+ }
 
 
 
 
 
 
 
880
  }
881
 
882
+ /* Email display for2FA: plain text and label aligned with other controls */
883
+ .twofa-email-display {
884
+ margin-left:25px;
885
+ margin-top:8px;
 
 
886
  display: flex;
887
+ gap:12px;
888
  align-items: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
889
  }
890
+ .twofa-email-label {
891
+ font-weight:600;
 
 
 
 
 
 
 
 
 
892
  color: #23395d;
 
893
  }
894
+ .twofa-email-value {
895
+ color: #516b78;
896
+ font-weight:600;
897
+ }
898
+
899
+ /* remove any residual styles for alternate email inputs */
900
+ .twofa-alt-email { display: none !important; }
901
+
902
+ /* Cross indicator under invalid fields */
903
+ .field-cross {
904
+ color: #ff5252;
905
+ font-weight:800;
906
+ margin-top:4px;
907
+ font-size:1rem;
908
+ }
909
+
910
+ /* Ensure input wrapper has room for cross under it */
911
+ .form-field { position: relative; margin-bottom:8px; }
912
+
913
+ /* Ensure main-panel is a positioned container so pinned controls can be placed inside it */
914
+ .main-panel {
915
+ position: relative;
916
+ /* reserve space so form content doesn't overlap pinned controls */
917
+ padding-bottom:120px; /* space for button + footer */
918
+ }
919
+
920
+ /* Pin the create button to the bottom center of the viewport (fixed) so it does not move when form content changes */
921
+ .main-panel .create-btn {
922
+ position: fixed; /* was absolute - changed to fixed */
923
+ left:86%;
924
+ transform: translateX(-50%);
925
+ bottom:56px; /* distance above footer */
926
+ width:48%;
927
+ max-width:360px;
928
+ margin:0;
929
+ z-index:50;
930
+ }
931
+
932
+ /* Pin the footer/version text to the bottom center of the viewport (fixed) */
933
+ .main-panel .create-footer {
934
+ position: fixed; /* was absolute - changed to fixed */
935
+ left:86%;
936
+ transform: translateX(-50%);
937
+ bottom:16px;
938
+ z-index:50;
939
+ width:100%;
940
+ text-align: center;
941
+ pointer-events: none;
942
+ }
943
+
944
+ /* Make sure Google row and other form elements don't get hidden behind the pinned controls */
945
+ .main-panel .google-signup-row {
946
+ margin-bottom:96px;
947
+ }
948
+
949
+ /* Remove any large flow margin on create button and rely on pinned positioning */
950
+ .create-btn { margin-top:0; }
951
+
952
+ /* Responsive adjustments */
953
+ @media (max-width:900px) {
954
+ .main-panel { padding-bottom:150px; }
955
+ .main-panel .create-btn {
956
+ bottom:86px;
957
+ width:90%;
958
+ max-width: none;
959
+ }
960
+ .main-panel .create-footer { bottom:12px; }
961
+ .main-panel .google-signup-row { margin-bottom:120px; }
962
+ }
963
+
964
+ /* Keep disabled styling intact */
965
+ .create-btn[disabled] { opacity:0.9; }
966
+ /* Cross shown inside confirm password input wrapper when mismatch */
967
+ .input-with-eye { position: relative; }
968
+ /* Removed the inside-cross element */
969
+ /* .input-with-eye .inside-cross { ... } */
970
+
971
+ /* Password field layout: label + gap + cross */
972
+ .password-field {
973
+ display: flex;
974
+ flex-direction: column;
975
+ }
976
+ .password-field label {
977
+ display: flex;
978
+ align-items: center;
979
+ gap:8px;
980
+ }
981
+
982
+ /* Keep label-cross visible when rendered by *ngIf */
983
+ .label-cross { display: inline-block; }
984
+
985
+ /* Force label cross visible and prominent */
986
+ .password-field label { padding-right:48px; }
987
+ .password-field .label-cross {
988
+ display: inline-block !important;
989
+ position: absolute !important;
990
+ right:8px !important;
991
+ top:50% !important;
992
+ transform: translateY(-50%) !important;
993
+ color: #ff5252 !important;
994
+ font-weight:900 !important;
995
+ font-size:1.1rem !important;
996
+ z-index:9999 !important;
997
+ pointer-events: none !important;
998
+ }
999
+
1000
+ /* Ensure inside-cross sits above everything */
1001
+ /* .input-with-eye .inside-cross { ... } */
1002
+
1003
+ /* Bring eye toggle slightly below inside-cross */
1004
+ .input-with-eye .eye-toggle { z-index:9000 !important; }
1005
+
1006
+ /* Strong outline and color on mismatch */
1007
+ .input-with-eye.password-mismatch input {
1008
+ outline:2px solid rgba(255,82,82,0.18) !important;
1009
+ box-shadow:0002px rgba(255,82,82,0.08) !important;
1010
+ color: #b91c1c !important;
1011
  }
src/app/homepage/sign-up/sign-up.component.html CHANGED
@@ -1,4 +1,4 @@
1
- <section class="signup-popup ai-bg-animate">
2
  <div class="ai-particle-bg"></div>
3
  <div class="signup-header">
4
  <div class="signup-logo">
@@ -17,97 +17,151 @@
17
  <div class="signup-panel-left">
18
 
19
  </div>
20
- <div class="main-panel">
21
  <h2 class="signup-title center-title">Create An Account</h2>
22
 
23
- <form [formGroup]="form" (ngSubmit)="submit()" class="create-form" novalidate>
 
 
 
 
24
  <div class="form-row">
25
  <div class="form-field">
26
- <label for="firstName">First Name</label>
27
- <input id="firstName" type="text" placeholder="First Name" formControlName="name" [attr.aria-invalid]="controlHasError('name')" />
28
- <small *ngIf="controlHasError('name','required') && form.get('name')?.touched" class="error">First name is required.</small>
29
- <small *ngIf="controlHasError('name','minlength') && form.get('name')?.touched" class="error">Enter at least 2 characters.</small>
30
- <small *ngIf="controlHasError('name','invalidName') && form.get('name')?.touched" class="error">Only alphabets and spaces allowed.</small>
31
  </div>
32
  <div class="form-field">
33
- <label for="lastName">Last Name</label>
34
- <input id="lastName" type="text" placeholder="Last Name" formControlName="lastName" [attr.aria-invalid]="controlHasError('lastName')" />
35
- <small *ngIf="controlHasError('lastName','required') && form.get('lastName')?.touched" class="error">Last name is required.</small>
36
- <small *ngIf="controlHasError('lastName','minlength') && form.get('lastName')?.touched" class="error">Enter at least 2 characters.</small>
37
- <small *ngIf="controlHasError('lastName','invalidName') && form.get('lastName')?.touched" class="error">Only alphabets and spaces allowed.</small>
38
  </div>
39
  </div>
40
  <div class="form-row">
41
- <div class="form-field">
42
- <label for="email">Email</label>
43
- <input id="email" type="text" placeholder="email@gmail.com" formControlName="email" [attr.aria-invalid]="controlHasError('email')" />
44
- <small *ngIf="controlHasError('email','required') && form.get('email')?.touched" class="error">Email is required.</small>
45
- <small *ngIf="controlHasError('email','pattern') && form.get('email')?.touched" class="error">Enter a valid email/phone.</small>
 
 
46
  </div>
47
  <div class="form-field role-field-wrapper">
48
- <label for="role">
49
- Role
50
- <button class="info-btn" type="button" (click)="showInfo = true">i</button>
51
  </label>
52
- <select id="role" formControlName="role" [attr.aria-invalid]="controlHasError('role')">
53
- <option value="">-- Select Role --</option>
54
- <option value="admin">Admin</option>
55
- <option value="teachers">Teachers</option>
56
- <option value="lawyers">Lawyers</option>
57
- <option value="investigators">Investigators</option>
58
- <option value="others">Others</option>
59
  </select>
60
- <small *ngIf="controlHasError('role','required') && form.get('role')?.touched" class="error">Role is required.</small>
61
  </div>
62
  </div>
 
63
  <div class="form-row">
64
- <div class="form-field" style="position:relative;">
65
- <label for="password">Create Password</label>
66
- <input id="password" [type]="showPassword ? 'text' : 'password'" placeholder="••••••••" formControlName="password" [attr.aria-invalid]="controlHasError('password')" />
67
- <button type="button" class="eye-toggle" (click)="toggleConfirmPasswordVisibility()" tabindex="-1" aria-label="Show/Hide confirm password">
68
- </button>
69
- <small *ngIf="controlHasError('password','required') && form.get('password')?.touched" class="error">Password is required.</small>
70
- <small *ngIf="controlHasError('password','minlength') && form.get('password')?.touched" class="error">Use at least 8 characters.</small>
71
- <small *ngIf="form.get('password')?.hasError('passwordPolicy') && form.get('password')?.touched" class="policy-info">
72
- Create a strong password with at least 8 characters using letters, numbers, and special symbols.
 
 
 
 
 
 
73
  </small>
74
  </div>
75
-
76
- <div class="form-field" style="position:relative;">
77
- <label for="confirmPassword">Confirm Password</label>
78
- <input id="confirmPassword" [type]="showConfirmPassword ? 'text' : 'password'" placeholder="••••••••" formControlName="confirmPassword" [attr.aria-invalid]="showPwdMismatch()" />
79
- <button type="button" class="eye-toggle" (click)="toggleConfirmPasswordVisibility()" tabindex="-1" aria-label="Show/Hide confirm password">
80
- </button>
81
- <small *ngIf="controlHasError('confirmPassword','required') && form.get('confirmPassword')?.touched" class="error">Confirm password is required.</small>
82
- <small *ngIf="showPwdMismatch() && form.get('confirmPassword')?.touched" class="error">Passwords do not match.</small>
 
 
 
 
 
 
 
83
  </div>
84
  </div>
85
- <div class="form-checkbox">
86
- <input type="checkbox" id="terms" formControlName="terms" />
87
- <label for="terms">Creating your account and you accepting <a href="#">Terms & Conditions.</a></label>
88
- </div>
89
- <div *ngIf="submitted && !form.get('terms')?.value" class="terms-info">
90
- Please accept Terms &amp; Conditions.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  </div>
92
- <button class="create-btn ai-pulse" type="submit" [disabled]="loading">
 
 
 
 
 
 
93
  <ng-container *ngIf="!loading; else creatingAccount">
94
- Create Account
95
  </ng-container>
96
  <ng-template #creatingAccount>
97
- <span class="spinner"></span> Creating Account...
98
  </ng-template>
99
  </button>
 
 
 
 
 
 
 
 
 
100
 
101
-
102
- <!-- Google Sign-In button -->
103
- <div class="google-signup-row">
104
- <div id="google-signup-btn-div">
105
- <div class="g-signin2" data-width="240" data-height="50" data-longtitle="true"></div>
106
- </div>
107
  </div>
108
-
109
- <div class="create-footer"><b>© Pykara Technologies, 2025. All rights reserved.</b></div>
110
- </form>
111
  </div>
112
  </div>
113
  </div>
@@ -117,15 +171,14 @@
117
  <div class="role-help-overlay" *ngIf="showRoleInfo" (click)="hideRoleInfo()">
118
  <div class="role-help-modal" role="dialog" aria-label="Role descriptions" (click)="$event.stopPropagation()">
119
  <button type="button" class="role-help-close" aria-label="Close role info" (click)="hideRoleInfo()">×</button>
120
- <h3 class="role-help-title">Choose the right role</h3>
121
  <ul class="role-help-list">
122
- <li><strong>Admin:</strong> Full control: users, roles, system settings.</li>
123
- <li><strong>Teachers:</strong> Run assessments / training evaluations.</li>
124
- <li><strong>Lawyers:</strong> Review analytics for case & witness prep.</li>
125
- <li><strong>Investigators:</strong> Conduct interviews and capture signals.</li>
126
- <li><strong>Others:</strong> General or limited access usage.</li>
127
  </ul>
128
- <p class="role-help-tip">Not sure? Select Others; an Admin can upgrade your role later.</p>
129
  </div>
130
  </div>
131
 
@@ -134,20 +187,16 @@
134
  <div class="info-popup">
135
  <button class="info-close" type="button" (click)="showInfo = false">&times;</button>
136
  <div class="info-title">Role Information</div>
 
137
  <div class="info-text">
138
  <ul>
139
- <li><strong>Admin:</strong> Full control: users, roles, system settings.</li>
140
- <li><strong>Teachers:</strong> Run assessments / training evaluations.</li>
141
- <li><strong>Lawyers:</strong> Review analytics for case & witness prep.</li>
142
- <li><strong>Investigators:</strong> Conduct interviews and capture signals.</li>
143
- <li><strong>Others:</strong> General or limited access usage.</li>
144
  </ul>
145
- <p>Not sure? Select Others; an Admin can upgrade your role later.</p>
146
  </div>
147
  </div>
148
  </div>
149
 
150
-
151
-
152
-
153
-
 
1
+ <section class="signup-popup">
2
  <div class="ai-particle-bg"></div>
3
  <div class="signup-header">
4
  <div class="signup-logo">
 
17
  <div class="signup-panel-left">
18
 
19
  </div>
20
+ <div class="main-panel" [class.pwd-mismatch]="submitted && pwdMismatch">
21
  <h2 class="signup-title center-title">Create An Account</h2>
22
 
23
+ <!-- aria-live region for screen readers (visually hidden) -->
24
+ <div class="sr-only" aria-live="assertive" *ngIf="submitted && invalidFieldsMessage">{{ invalidFieldsMessage }}</div>
25
+
26
+ <!-- Extend form to include terms, reCAPTCHA, and submit button -->
27
+ <form [formGroup]="form" (ngSubmit)="submit()" class="create-form" novalidate autocomplete="off">
28
  <div class="form-row">
29
  <div class="form-field">
30
+ <label for="firstName">First Name <span class="required-star" *ngIf="true">*</span>
31
+ <span class="label-cross" *ngIf="submitted && controlHasError('name')" aria-hidden="true">✖</span>
32
+ </label>
33
+ <input id="firstName" type="text" placeholder="First Name" formControlName="name" [attr.aria-invalid]="controlHasError('name')" [class.input-invalid]="submitted && (controlHasError('name') || nameHasDigits('name'))" />
34
+ <!-- Name errors intentionally hidden until Create Account and per user request no name error texts -->
35
  </div>
36
  <div class="form-field">
37
+ <label for="lastName">Last Name <span class="required-star" *ngIf="true">*</span>
38
+ <span class="label-cross" *ngIf="submitted && controlHasError('lastName')" aria-hidden="true">✖</span>
39
+ </label>
40
+ <input id="lastName" type="text" placeholder="Last Name" formControlName="lastName" [attr.aria-invalid]="controlHasError('lastName')" [class.input-invalid]="submitted && (controlHasError('lastName') || nameHasDigits('lastName'))" />
41
+ <!-- Last name errors intentionally hidden until Create Account and per user request no name error texts -->
42
  </div>
43
  </div>
44
  <div class="form-row">
45
+ <div class="form-field email-field" [class.email-invalid]="submitted && controlHasError('email')">
46
+ <label for="email">Email <span class="required-star">*</span>
47
+ </label>
48
+ <input id="email" type="text" placeholder="email@gmail.com" formControlName="email" [attr.aria-invalid]="controlHasError('email')" [class.input-invalid]="submitted && controlHasError('email')" autocomplete="off" autocapitalize="off" spellcheck="false" />
49
+ <small *ngIf="submitted && controlHasError('email','required')" class="error">Email is required.</small>
50
+ <small *ngIf="submitted && controlHasError('email','pattern')" class="error">Enter a valid email/phone.</small>
51
+ <small *ngIf="submitted && controlHasError('email','emailExists')" class="error">Email already exists.</small>
52
  </div>
53
  <div class="form-field role-field-wrapper">
54
+ <label for="roleGroup">Role Group <span class="required-star">*</span>
55
+ <span class="label-cross" *ngIf="submitted && controlHasError('roleGroup')" aria-hidden="true">✖</span>
 
56
  </label>
57
+ <select id="roleGroup" formControlName="roleGroup" (change)="onRoleGroupChange($any($event.target).value)" aria-label="Role group" [class.input-invalid]="submitted && controlHasError('roleGroup')">
58
+ <option value="">-- Select role group --</option>
59
+ <option *ngFor="let g of roleGroups" [value]="g.key">{{ g.label }}</option>
 
 
 
 
60
  </select>
61
+ <small *ngIf="submitted && controlHasError('roleGroup','required')" class="error">Please select a role group.</small>
62
  </div>
63
  </div>
64
+
65
  <div class="form-row">
66
+ <div class="form-field password-field">
67
+ <label for="password">Create Password <span class="required-star">*</span>
68
+ </label>
69
+ <!-- wrap input and eye in a positioned wrapper so the eye is anchored to the input only -->
70
+ <div class="input-with-eye">
71
+ <input id="password" [type]="showPassword ? 'text' : 'password'" placeholder="••••••••" formControlName="password" (keydown)="onPasswordKey($event)" (keyup)="onPasswordKey($event)" [class.input-invalid]="submitted && controlHasError('password')" autocomplete="new-password" autocapitalize="off" spellcheck="false" />
72
+ <!-- local eye toggle button (sign-up only) -->
73
+ <button type="button" class="eye-toggle variant-signup" aria-label="Show password" [attr.aria-pressed]="showPassword" (click)="togglePasswordVisibility()">
74
+ <i class="fa-solid" [ngClass]="showPassword ? 'fa-eye' : 'fa-eye-slash'" aria-hidden="true"></i>
75
+ </button>
76
+ </div>
77
+ <div *ngIf="capsLockOn" class="caps-lock-warning">Caps Lock is on</div>
78
+ <small *ngIf="submitted && controlHasError('password','required')" class="error">Password is required.</small>
79
+ <small *ngIf="submitted && form.get('password')?.hasError('passwordPolicy')" class="policy-info">
80
+ Required at least 8 characters using letters, numbers, and special character.
81
  </small>
82
  </div>
83
+
84
+ <div class="form-field password-field">
85
+ <label for="confirmPassword">Confirm Password <span class="required-star">*</span>
86
+ </label>
87
+ <!-- wrap confirm input and eye similarly -->
88
+ <div class="input-with-eye" [class.password-mismatch]="submitted && pwdMismatch" [class.confirm-cross]="submitted && pwdMismatch">
89
+ <input id="confirmPassword" [type]="showConfirmPassword ? 'text' : 'password'" placeholder="••••••••" formControlName="confirmPassword" (keydown)="onPasswordKey($event)" (keyup)="onPasswordKey($event)" [class.input-invalid]="submitted && (controlHasError('confirmPassword') || showPwdMismatch())" autocomplete="new-password" autocapitalize="off" spellcheck="false" />
90
+ <!-- local eye toggle for confirm (sign-up only) -->
91
+ <button type="button" class="eye-toggle variant-signup" aria-label="Show confirm password" [attr.aria-pressed]="showConfirmPassword" (click)="toggleConfirmPasswordVisibility()">
92
+ <i class="fa-solid" [ngClass]="showConfirmPassword ? 'fa-eye' : 'fa-eye-slash'" aria-hidden="true"></i>
93
+ </button>
94
+ </div>
95
+ <div *ngIf="capsLockOn" class="caps-lock-warning">Caps Lock is on</div>
96
+ <small *ngIf="submitted && controlHasError('confirmPassword','required')" class="error">Confirm password is required.</small>
97
+ <small *ngIf="submitted && showPwdMismatch()" class="error">Passwords do not match.</small>
98
  </div>
99
  </div>
100
+
101
+ <!--2FA opt-in and method selection -->
102
+ <div class="form-row">
103
+ <div class="form-field twofa-field">
104
+ <!-- single row: checkbox + methods -->
105
+ <div class="twofa-row">
106
+ <div class="form-checkbox twofa-checkbox">
107
+ <input type="checkbox" id="twoFAOptIn" formControlName="twoFAOptIn" />
108
+ <label for="twoFAOptIn">Enable Two-Factor Authentication (2FA)</label>
109
+ </div>
110
+
111
+ <div *ngIf="form.get('twoFAOptIn')?.value" class="twofa-methods">
112
+ <label class="inline-control plain-control"><input type="radio" formControlName="twoFAMethod" value="email" /> Email</label>
113
+ <label class="inline-control plain-control"><input type="radio" formControlName="twoFAMethod" value="sms" /> SMS</label>
114
+ </div>
115
+ </div>
116
+
117
+ <!-- Email-specific display: show account email (plain text) -->
118
+ <div *ngIf="form.get('twoFAOptIn')?.value && form.get('twoFAMethod')?.value === 'email'" class="twofa-email-display">
119
+ <label class="twofa-email-label">2FA will be sent to</label>
120
+ <div class="twofa-email-value">{{ form.get('email')?.value || '—' }}</div>
121
+ </div>
122
+
123
+ <!-- SMS-specific input -->
124
+ <div *ngIf="form.get('twoFAOptIn')?.value && form.get('twoFAMethod')?.value === 'sms'" class="twofa-sms-options">
125
+ <label for="twoFAPhone">Phone number for SMS2FA</label>
126
+ <input id="twoFAPhone" type="tel" placeholder="+1234567890" formControlName="twoFAPhone" (input)="formatPhone($event)" inputmode="tel" autocomplete="tel" [class.input-invalid]="submitted && form.get('twoFAPhone')?.invalid" />
127
+ <small *ngIf="submitted && form.get('twoFAPhone')?.invalid" class="error">Enter a valid phone number for SMS 2FA.</small>
128
+ </div>
129
+
130
+ <small *ngIf="twoFAError" class="error twofa-error">{{ twoFAError }}</small>
131
+ </div>
132
  </div>
133
+
134
+ <!-- reCAPTCHA -->
135
+ <div id="recaptcha-container" class="recaptcha-container"></div>
136
+ <small *ngIf="recaptchaError" class="error">{{ recaptchaError }}</small>
137
+
138
+ <!-- Submit -->
139
+ <button class="create-btn ai-pulse" type="submit" [disabled]="loading || !isFormFilled()">
140
  <ng-container *ngIf="!loading; else creatingAccount">
141
+ Create Account
142
  </ng-container>
143
  <ng-template #creatingAccount>
144
+ <span class="spinner"></span> Creating Account...
145
  </ng-template>
146
  </button>
147
+ </form>
148
+
149
+ <!-- Terms & Privacy moved outside form group, aligned with2FA -->
150
+ <div [formGroup]="form">
151
+ <div class="form-checkbox">
152
+ <input type="checkbox" id="terms" formControlName="terms" />
153
+ <label for="terms">Agree to our <a href="/legal/terms" target="_blank" rel="_noopener noreferrer">Terms &amp; Conditions</a> and <a href="/legal/privacy" target="_blank" rel="_noopener noreferrer">Privacy Policy</a>.</label>
154
+ </div>
155
+ </div>
156
 
157
+ <!-- Google Sign-In button -->
158
+ <div class="google-signup-row">
159
+ <div id="google-signup-btn-div">
160
+ <div class="g-signin2" data-width="240" data-height="50" data-longtitle="true"></div>
 
 
161
  </div>
162
+ </div>
163
+
164
+ <div class="create-footer"><b>Version1.0.0 | © Pykara Technologies</b></div>
165
  </div>
166
  </div>
167
  </div>
 
171
  <div class="role-help-overlay" *ngIf="showRoleInfo" (click)="hideRoleInfo()">
172
  <div class="role-help-modal" role="dialog" aria-label="Role descriptions" (click)="$event.stopPropagation()">
173
  <button type="button" class="role-help-close" aria-label="Close role info" (click)="hideRoleInfo()">×</button>
174
+ <h3 class="role-help-title">Role Information</h3>
175
  <ul class="role-help-list">
176
+ <li><strong>Law Enforcement</strong><br/>Investigator, Supervisor for users handling case investigation, interviews, evidence review, and workflow supervision. These roles may require admin approval or identity verification.</li>
177
+ <li><strong>Legal</strong><br/>Lawyer for legal professionals involved in reviewing case summaries, preparing legal notes, analyzing evidence, or assisting in compliance processes.</li>
178
+ <li><strong>Administration</strong><br/>Admin for users who manage system settings, user access, roles, case assignments, and overall application operations.</li>
179
+ <li><strong>General Access</strong><br/>Other for users who require limited or basic access, such as support staff, trainees, or external collaborators.</li>
 
180
  </ul>
181
+ <p class="role-help-tip">If you are not sure which role to choose, select <strong>Other</strong>, or contact: <a href="mailto:support@pykara.ai">support@pykara.ai</a></p>
182
  </div>
183
  </div>
184
 
 
187
  <div class="info-popup">
188
  <button class="info-close" type="button" (click)="showInfo = false">&times;</button>
189
  <div class="info-title">Role Information</div>
190
+
191
  <div class="info-text">
192
  <ul>
193
+ <li><strong>Law Enforcement</strong><br/>Investigator, Supervisor for users handling case investigation, interviews, evidence review, and workflow supervision. These roles may require admin approval or identity verification.</li>
194
+ <li><strong>Legal</strong><br/>Lawyer for legal professionals involved in reviewing case summaries, preparing legal notes, analyzing evidence, or assisting in compliance processes.</li>
195
+ <li><strong>Administration</strong><br/>Admin for users who manage system settings, user access, roles, case assignments, and overall application operations.</li>
196
+ <li><strong>General Access</strong><br/>Other for users who require limited or basic access, such as support staff, trainees, or external collaborators.</li>
 
197
  </ul>
198
+ <p>If you are not sure which role to choose, select <strong>Other</strong>, or contact: <a href="mailto:info@pykara.ai">info@pykara.ai</a></p>
199
  </div>
200
  </div>
201
  </div>
202
 
 
 
 
 
src/app/homepage/sign-up/sign-up.component.ts CHANGED
@@ -1,267 +1,612 @@
1
-
2
- import { Component, Output, EventEmitter, ChangeDetectorRef, Input, OnInit, OnDestroy } from '@angular/core';
3
  import { CommonModule } from '@angular/common';
4
- import { FormBuilder, FormGroup, ReactiveFormsModule, Validators, AbstractControl, ValidationErrors } from '@angular/forms';
5
  import { Router, RouterLink } from '@angular/router';
6
- import { SignUpService } from './sign-up.service'; // Import the SignUpService
7
  import { AuthService } from '../../auth.service';
8
  import { trigger, transition, style, animate } from '@angular/animations';
 
 
 
 
 
 
 
 
 
9
 
10
  export function nameValidator(control: AbstractControl): ValidationErrors | null {
11
- const value = control.value || '';
12
- // Only allow alphabets and spaces, min2 chars
13
- if (!/^[A-Za-z ]{2,}$/.test(value)) {
14
- return { invalidName: true };
15
- }
16
- return null;
17
  }
18
 
19
  @Component({
20
- selector: 'app-sign-up',
21
- standalone: true,
22
- imports: [CommonModule, ReactiveFormsModule, RouterLink],
23
- templateUrl: './sign-up.component.html',
24
- styleUrls: ['./sign-up.component.css'],
25
- animations: [
26
- trigger('fadeInOut', [
27
- transition(':enter', [
28
- style({ opacity:0 }),
29
- animate('600ms', style({ opacity:1 }))
30
- ]),
31
- transition(':leave', [
32
- animate('600ms', style({ opacity:0 }))
33
- ])
34
- ])
35
- ]
36
  })
37
  export class SignUpComponent implements OnInit, OnDestroy {
38
- @Input() embedded = false; // when true, render only inner panel (for embedding in auth-card)
39
- @Output() switchToSignIn = new EventEmitter<void>();
40
- form: FormGroup;
41
- private isSubmitting = false;
42
-
43
- // Role info popover logic preserved
44
- showRoleInfo = false;
45
- toggleRoleInfo(ev?: Event) { ev?.stopPropagation(); this.showRoleInfo = !this.showRoleInfo; }
46
- hideRoleInfo() { this.showRoleInfo = false; }
47
-
48
- @Output() close = new EventEmitter<void>();
49
-
50
- showPassword = false;
51
- showConfirmPassword = false;
52
- errorMessage = '';
53
-
54
- isSignUpActive = true; // ← Added state for sign-up panel activation
55
- public loading = false; // Used to disable the button during sign-up
56
- submitted = false; // Track form submission status
57
-
58
- // Added: terms & conditions error handling
59
- termsError: string = '';
60
-
61
- facts: string[] = [
62
- '🧠 Py-Detect AI analyzes tone, emotion, and consistency.',
63
- '🎥 Supports video and audio interrogation analysis.',
64
- '📊 Generates instant investigation summary reports.'
65
- ];
66
- currentFact: string = this.facts[0];
67
- private factIndex =0;
68
- private factInterval: any;
69
-
70
- showInfo = false;
71
-
72
- constructor(
73
- private fb: FormBuilder,
74
- private router: Router,
75
- private signUpService: SignUpService,
76
- private cdr: ChangeDetectorRef
77
- ) {
78
- this.form = this.fb.group({
79
- name: ['', [Validators.required, Validators.minLength(2), nameValidator]],
80
- lastName: ['', [Validators.required, Validators.minLength(2), nameValidator]],
81
- email: ['', [
82
- Validators.required,
83
- Validators.pattern(/(^[^@]+@[^@]+\.[^@]+$)|(^\+?\d[\d\-\s]{8,14}\d$)/)
84
- ]],
85
- password: ['', [Validators.required, Validators.minLength(8), passwordPolicyValidator]],
86
- confirmPassword: ['', [Validators.required]],
87
- role: ['', [Validators.required]],
88
- terms: [false, Validators.requiredTrue] // Added terms control with requiredTrue validator
89
- }, { validators: [this.passwordsMatchValidator] });
90
-
91
- // Close popover when clicking anywhere in document (capture phase not needed here)
92
- document.addEventListener('click', () => this.hideRoleInfo());
93
- }
94
-
95
- ngOnInit() {
96
- this.startFactRotation();
97
- }
98
-
99
- ngOnDestroy() {
100
- if (this.factInterval) {
101
- clearInterval(this.factInterval);
102
- }
103
- }
104
-
105
- startFactRotation() {
106
- this.factInterval = setInterval(() => {
107
- this.factIndex = (this.factIndex +1) % this.facts.length;
108
- this.currentFact = this.facts[this.factIndex];
109
- },5000);
110
- }
111
-
112
- control(path: string): AbstractControl | null { return this.form.get(path); }
113
-
114
- controlHasError(path: string, error?: string): boolean {
115
- const c = this.control(path);
116
- if (!c) return false;
117
- return error ? !!(c.touched && c.errors?.[error]) : !!(c.touched && c.invalid);
118
- }
119
-
120
- showPwdMismatch(): boolean {
121
- const pw = this.control('password');
122
- const cpw = this.control('confirmPassword');
123
- const groupMismatch = this.form.errors?.['passwordMismatch'];
124
- return !!(pw && cpw && (cpw.touched || pw.touched) && groupMismatch);
125
- }
126
-
127
- passwordsMatchValidator(group: AbstractControl) {
128
- const pw = group.get('password')?.value;
129
- const cpw = group.get('confirmPassword')?.value;
130
- return pw && cpw && pw === cpw ? null : { passwordMismatch: true };
131
- }
132
-
133
- togglePasswordVisibility() {
134
- this.showPassword = !this.showPassword;
135
- }
136
- toggleConfirmPasswordVisibility() {
137
- this.showConfirmPassword = !this.showConfirmPassword;
138
- }
139
-
140
- submit() {
141
- // Confirm button click
142
- alert("Sign-Up button clicked!");
143
- console.log("Sign-Up button clicked!");
144
-
145
- this.submitted = true; // Track the form submission attempt
146
-
147
- // Mark all form controls as touched to trigger validation
148
- this.form.markAllAsTouched();
149
-
150
- // Check if the form is invalid
151
- if (this.form.invalid) {
152
- console.log("Form is invalid:");
153
-
154
- // Log individual control states for debugging
155
- Object.keys(this.form.controls).forEach(controlName => {
156
- const control = this.form.get(controlName);
157
- if (control?.invalid) {
158
- console.log(`${controlName} is invalid. Errors:`, control?.errors);
159
- }
160
- });
161
- return;
162
- }
163
-
164
- // Check terms & conditions acceptance
165
- if (!this.form.get('terms')?.value) {
166
- this.termsError = 'Please accept Terms & Conditions.';
167
- return;
168
- }
169
- this.termsError = '';
170
-
171
- this.loading = true; // Set loading to true when starting submission
172
- try {
173
- // Prepare the payload to send to the backend
174
- const payload = {
175
- name: this.control('name')?.value,
176
- lastName: this.control('lastName')?.value,
177
- email: this.control('email')?.value,
178
- password: this.control('password')?.value,
179
- role: this.control('role')?.value
180
- };
181
-
182
- console.log("Form data before submission:", this.form.value);
183
- console.log("Payload to send:", payload);
184
-
185
- // Make the HTTP request
186
- this.signUpService.signUp(payload).subscribe(
187
- (response) => {
188
- this.errorMessage = '';
189
- console.log("Sign-up request sent successfully!");
190
- this.loading = false; // Reset loading on success
191
- // Wait for loader to finish, then navigate
192
- setTimeout(() => {
193
- this.router.navigate(['/auth/signin']);
194
- }, 500);
195
- },
196
- (error) => {
197
- if (error && error.status === 400) {
198
- this.errorMessage = 'This email or username is already registered.';
199
- } else {
200
- this.errorMessage = 'An error occurred. Please try again.';
201
- }
202
- this.loading = false; // Reset loading on error
203
- this.cdr.markForCheck();
204
- setTimeout(() => {
205
- this.errorMessage = '';
206
- this.cdr.markForCheck();
207
- }, 3000);
208
- }
209
- );
210
- } catch (error) {
211
- console.error("Error occurred during sign-up:", error); // Log any errors from the API call
212
- this.loading = false; // Reset loading on exception
213
- }
214
- }
215
-
216
- navigateHome() { this.router.navigateByUrl('/'); }
217
-
218
- goToLogin() {
219
- this.switchToSignIn.emit(); // ← Emit event instead of router navigation
220
- }
221
-
222
- closePopup() {
223
- try {
224
- // dispatch a global event so parent or other listeners always can close modals
225
- window.dispatchEvent(new CustomEvent('auth-close'));
226
- } catch (e) {
227
- // ignore
228
- }
229
-
230
- this.close.emit();
231
- // Defensive: remove modal/backdrop if parent didn't hide them
232
- try {
233
- const modal = document.querySelector('.modal');
234
- if (modal && modal.parentElement) modal.parentElement.removeChild(modal);
235
- const backdrop = document.querySelector('.modal-backdrop');
236
- if (backdrop && backdrop.parentElement) backdrop.parentElement.removeChild(backdrop);
237
- } catch (e) {
238
- console.warn('Failed to remove modal/backdrop DOM elements', e);
239
- }
240
- // Ensure change detection updates
241
- this.cdr.markForCheck();
242
- }
243
-
244
- tr(key: string): string {
245
- const map: Record<string, string> = { title: 'Create your account', subtitle: 'Join to continue' };
246
- return map[key] || '';
247
- }
248
-
249
- goToSignIn() {
250
- // Emit to parent when embedded so the card can slide back
251
- this.switchToSignIn.emit();
252
- }
253
-
254
- goToSignUp() {
255
- // no-op when embedded
256
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  }
258
 
 
259
  function passwordPolicyValidator(control: AbstractControl): ValidationErrors | null {
260
- const value = control.value || '';
261
- // Policy: min 8 chars, 1 uppercase, 1 lowercase, 1 number, 1 special char
262
- const policy = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>\/?]).{8,}$/;
263
- if (!policy.test(value)) {
264
- return { passwordPolicy: true };
265
- }
266
- return null;
267
  }
 
 
 
1
  import { CommonModule } from '@angular/common';
2
+ import { ReactiveFormsModule, FormBuilder, FormGroup, Validators, AbstractControl, ValidationErrors } from '@angular/forms';
3
  import { Router, RouterLink } from '@angular/router';
4
+ import { SignUpService } from './sign-up.service'; // Import the SignUpService
5
  import { AuthService } from '../../auth.service';
6
  import { trigger, transition, style, animate } from '@angular/animations';
7
+ import { ChangeDetectorRef, Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core';
8
+ import { EyeToggleComponent } from '../../shared/eye-toggle/eye-toggle.component';
9
+ import { Subscription } from 'rxjs';
10
+
11
+ // allow grecaptcha global variable
12
+ declare global {
13
+ interface Window { grecaptcha: any; }
14
+ }
15
+ declare const grecaptcha: any;
16
 
17
  export function nameValidator(control: AbstractControl): ValidationErrors | null {
18
+ const value = control.value || '';
19
+ // Only allow alphabets and spaces, min2 chars
20
+ if (!/^[A-Za-z ]{2,}$/.test(value)) {
21
+ return { invalidName: true };
22
+ }
23
+ return null;
24
  }
25
 
26
  @Component({
27
+ selector: 'app-sign-up',
28
+ standalone: true,
29
+ imports: [CommonModule, ReactiveFormsModule, RouterLink, EyeToggleComponent],
30
+ templateUrl: './sign-up.component.html',
31
+ styleUrls: ['./sign-up.component.css'],
32
+ animations: [
33
+ trigger('fadeInOut', [
34
+ transition(':enter', [
35
+ style({ opacity:0 }),
36
+ animate('600ms', style({ opacity:1 }))
37
+ ]),
38
+ transition(':leave', [
39
+ animate('600ms', style({ opacity:0 }))
40
+ ])
41
+ ])
42
+ ]
43
  })
44
  export class SignUpComponent implements OnInit, OnDestroy {
45
+ @Input() embedded = false; // when true, render only inner panel (for embedding in auth-card)
46
+ @Input() cardState?: 'signup' | 'signin';
47
+ @Output() switchToSignIn = new EventEmitter<void>();
48
+ form: FormGroup;
49
+ private isSubmitting = false;
50
+
51
+ // Role info popover logic preserved
52
+ showRoleInfo = false;
53
+ toggleRoleInfo(ev?: Event) { ev?.stopPropagation(); this.showRoleInfo = !this.showRoleInfo; }
54
+ hideRoleInfo() { this.showRoleInfo = false; }
55
+
56
+ @Output() close = new EventEmitter<void>();
57
+
58
+ showPassword = false;
59
+ showConfirmPassword = false;
60
+ errorMessage = '';
61
+
62
+ isSignUpActive = true; //  Added state for sign-up panel activation
63
+ public loading = false; // Used to disable the button during sign-up
64
+ submitted = false; // Track form submission status
65
+ // Explicit flag to drive mismatch UI (set on submit)
66
+ public pwdMismatch: boolean = false;
67
+
68
+ // Added: terms & conditions error handling
69
+ termsError: string = '';
70
+ twoFAError: string = ''; // validation message for2FA
71
+
72
+ // reCAPTCHA
73
+ private recaptchaSiteKey = 'YOUR_RECAPTCHA_SITE_KEY'; // <-- Replace with your real site key
74
+ private recaptchaWidgetId: number | null = null;
75
+ recaptchaError = '';
76
+
77
+ facts: string[] = [
78
+ '🧠 Py-Detect AI analyzes tone, emotion, and consistency.',
79
+ '🎥 Supports video and audio interrogation analysis.',
80
+ '📊 Generates instant investigation summary reports.'
81
+ ];
82
+ currentFact: string = this.facts[0];
83
+ private factIndex =0;
84
+ private factInterval: any;
85
+
86
+ showInfo = false;
87
+
88
+ // Caps Lock state
89
+ capsLockOn = false;
90
+
91
+ // Role grouping options
92
+ roleGroups = [
93
+ { key: 'lawenforcement', label: 'Law Enforcement', subs: [ { key: 'investigator', label: 'Investigator' }, { key: 'supervisor', label: 'Supervisor' } ] },
94
+ { key: 'legal', label: 'Legal', subs: [ { key: 'lawyer', label: 'Lawyer' } ] },
95
+ { key: 'adminothers', label: 'Admin & Others', subs: [ { key: 'admin', label: 'Admin' }, { key: 'other', label: 'Other' } ] }
96
+ ];
97
+
98
+ availableSubRoles: Array<{key:string,label:string}> = [];
99
+
100
+ // subscriptions for2FA opt-in/method/phone changes
101
+ private twoFASubscriptions: Subscription[] = [];
102
+ private savedTwoFAMethod: string | null = null;
103
+ private savedTwoFAPhone: string | null = null;
104
+
105
+ // Additional property to track form enable state
106
+ public canEnable: boolean = false;
107
+ private formSubscription?: Subscription;
108
+
109
+ constructor(
110
+ private fb: FormBuilder,
111
+ private router: Router,
112
+ private signUpService: SignUpService,
113
+ private cdr: ChangeDetectorRef
114
+ ) {
115
+ this.form = this.fb.group({
116
+ name: ['', [Validators.required, Validators.minLength(2), nameValidator]],
117
+ lastName: ['', [Validators.required, Validators.minLength(2), nameValidator]],
118
+ email: ['', [
119
+ Validators.required,
120
+ Validators.pattern(/(^[^@]+@[^@]+\.[^@]+$)|(^\+?\d[\d\-\s]{8,14}\d$)/)
121
+ ]],
122
+ password: ['', [Validators.required, Validators.minLength(8), passwordPolicyValidator]],
123
+ confirmPassword: ['', [Validators.required]],
124
+ roleGroup: ['', [Validators.required]],
125
+ role: ['', [Validators.required]],
126
+ terms: [false, Validators.requiredTrue], // Added terms control with requiredTrue validator
127
+ // reCAPTCHA token
128
+ // recaptcha: ['', [Validators.required]],
129
+
130
+ //2FA controls
131
+ twoFAOptIn: [false],
132
+ twoFAMethod: [''], // 'email' or 'sms'
133
+ twoFAUseSameEmail: [true],
134
+ twoFAAltEmail: ['', [Validators.pattern(/^[^@]+@[^@]+\.[^@]+$/)]],
135
+ twoFAPhone: ['', [Validators.pattern(/^\+?\d[\d\-\s]{8,14}\d$/)]]
136
+
137
+ }, { validators: [this.passwordsMatchValidator] });
138
+
139
+ // Close popover when clicking anywhere in document (capture phase not needed here)
140
+ document.addEventListener('click', () => this.hideRoleInfo());
141
+ }
142
+
143
+ ngOnInit() {
144
+ this.startFactRotation();
145
+ this.loadRecaptcha();
146
+
147
+ // update initial canEnable state
148
+ this.canEnable = this.isFormFilled();
149
+
150
+ // keep canEnable updated whenever form values change
151
+ this.formSubscription = this.form.valueChanges.subscribe(() => {
152
+ this.canEnable = this.isFormFilled();
153
+ // debug log to help diagnose why button remains disabled
154
+ // eslint-disable-next-line no-console
155
+ console.log('form value changed, isFormFilled=', this.canEnable, 'form values:', this.form.value);
156
+ try { this.cdr.detectChanges(); } catch (e) { /* ignore */ }
157
+ // Do NOT compute pwdMismatch here; only compute on submit so crosses appear after clicking Create Account
158
+ });
159
+
160
+ // Persist2FA selection when toggling opt-in
161
+ const optInCtrl = this.form.get('twoFAOptIn');
162
+ const methodCtrl = this.form.get('twoFAMethod');
163
+ const phoneCtrl = this.form.get('twoFAPhone');
164
+
165
+ if (optInCtrl) {
166
+ const sub = optInCtrl.valueChanges.subscribe((val: boolean) => {
167
+ if (!val) {
168
+ // save current selections when user unchecks
169
+ this.savedTwoFAMethod = methodCtrl?.value || null;
170
+ this.savedTwoFAPhone = phoneCtrl?.value || null;
171
+ } else {
172
+ // restore previous selections when user re-checks
173
+ if (this.savedTwoFAMethod) {
174
+ methodCtrl?.setValue(this.savedTwoFAMethod, { emitEvent: false });
175
+ }
176
+ if (this.savedTwoFAPhone) {
177
+ phoneCtrl?.setValue(this.savedTwoFAPhone, { emitEvent: false });
178
+ }
179
+ }
180
+ });
181
+ this.twoFASubscriptions.push(sub);
182
+ }
183
+
184
+ if (this.form.get('twoFAMethod')) {
185
+ const sub2 = this.form.get('twoFAMethod')!.valueChanges.subscribe((m: string) => {
186
+ // update saved value whenever user changes method while visible
187
+ this.savedTwoFAMethod = m || this.savedTwoFAMethod;
188
+ });
189
+ this.twoFASubscriptions.push(sub2);
190
+ }
191
+
192
+ if (this.form.get('twoFAPhone')) {
193
+ const sub3 = this.form.get('twoFAPhone')!.valueChanges.subscribe((p: string) => {
194
+ this.savedTwoFAPhone = p || this.savedTwoFAPhone;
195
+ });
196
+ this.twoFASubscriptions.push(sub3);
197
+ }
198
+ }
199
+
200
+ ngOnDestroy() {
201
+ if (this.factInterval) {
202
+ clearInterval(this.factInterval);
203
+ }
204
+ // cleanup subscriptions
205
+ this.twoFASubscriptions.forEach(s => s.unsubscribe());
206
+ if (this.formSubscription) {
207
+ this.formSubscription.unsubscribe();
208
+ }
209
+ }
210
+
211
+ private loadRecaptcha() {
212
+ // Don't attempt to load if no site key configured
213
+ if (!this.recaptchaSiteKey || this.recaptchaSiteKey === 'YOUR_RECAPTCHA_SITE_KEY') {
214
+ // skip loading but keep control present so dev can replace key
215
+ console.warn('reCAPTCHA site key not configured. Please replace recaptchaSiteKey with your site key.');
216
+ return;
217
+ }
218
+
219
+ const existing = document.querySelector('script[src*="recaptcha/api.js"]');
220
+ const renderWhenReady = () => {
221
+ try {
222
+ // render explicit
223
+ this.recaptchaWidgetId = window.grecaptcha.render('recaptcha-container', {
224
+ sitekey: this.recaptchaSiteKey,
225
+ callback: (token: string) => this.onRecaptchaSuccess(token),
226
+ 'expired-callback': () => this.onRecaptchaExpired()
227
+ });
228
+ } catch (e) {
229
+ console.error('Failed to render grecaptcha', e);
230
+ }
231
+ };
232
+
233
+ if (existing) {
234
+ if (window.grecaptcha && window.grecaptcha.render) {
235
+ renderWhenReady();
236
+ } else {
237
+ // wait for onload
238
+ existing.addEventListener('load', renderWhenReady);
239
+ }
240
+ return;
241
+ }
242
+
243
+ const script = document.createElement('script');
244
+ script.src = 'https://www.google.com/recaptcha/api.js?onload=__onRecaptchaLoadCallback&render=explicit';
245
+ script.async = true;
246
+ script.defer = true;
247
+ (window as any).__onRecaptchaLoadCallback = () => {
248
+ renderWhenReady();
249
+ };
250
+ document.head.appendChild(script);
251
+ }
252
+
253
+ onRecaptchaSuccess(token: string) {
254
+ this.form.get('recaptcha')?.setValue(token);
255
+ this.recaptchaError = '';
256
+ this.cdr.markForCheck();
257
+ }
258
+
259
+ onRecaptchaExpired() {
260
+ this.form.get('recaptcha')?.setValue('');
261
+ this.recaptchaError = 'reCAPTCHA expired. Please verify again.';
262
+ this.cdr.markForCheck();
263
+ }
264
+
265
+ resetRecaptcha() {
266
+ try {
267
+ if (window.grecaptcha && this.recaptchaWidgetId != null) {
268
+ window.grecaptcha.reset(this.recaptchaWidgetId);
269
+ }
270
+ this.form.get('recaptcha')?.setValue('');
271
+ this.cdr.markForCheck();
272
+ } catch (e) { /* ignore */ }
273
+ }
274
+
275
+ startFactRotation() {
276
+ this.factInterval = setInterval(() => {
277
+ this.factIndex = (this.factIndex +1) % this.facts.length;
278
+ this.currentFact = this.facts[this.factIndex];
279
+ },5000);
280
+ }
281
+
282
+ control(path: string): AbstractControl | null { return this.form.get(path); }
283
+
284
+ controlHasError(path: string, error?: string): boolean {
285
+ const c = this.control(path);
286
+ if (!c) return false;
287
+ // Only show validation state after the user has attempted submission
288
+ if (!this.submitted) return false;
289
+ return error ? !!(c.errors?.[error]) : !!(c.invalid);
290
+ }
291
+
292
+ showPwdMismatch(): boolean {
293
+ return !!this.pwdMismatch;
294
+ }
295
+
296
+ passwordsMatchValidator(group: AbstractControl) {
297
+ const pw = group.get('password')?.value;
298
+ const cpw = group.get('confirmPassword')?.value;
299
+ // don't mark mismatch unless both fields have a value
300
+ if (!pw || !cpw) return null;
301
+ return pw === cpw ? null : { passwordMismatch: true };
302
+ }
303
+
304
+ togglePasswordVisibility() {
305
+ this.showPassword = !this.showPassword;
306
+ }
307
+ toggleConfirmPasswordVisibility() {
308
+ this.showConfirmPassword = !this.showConfirmPassword;
309
+ }
310
+
311
+ invalidFieldsMessage: string = ''; // ARIA live message for screen reader
312
+
313
+ submit() {
314
+ // Confirm button click
315
+ this.submitted = true; // Track the form submission attempt
316
+
317
+ // Mark all form controls as touched to trigger validation
318
+ this.form.markAllAsTouched();
319
+ // Ensure validators (including group validator) run so form.errors is populated
320
+ this.form.updateValueAndValidity({ onlySelf: false, emitEvent: true });
321
+ // Trigger change detection so template picks up updated form errors immediately
322
+ try { this.cdr.detectChanges(); } catch (e) { this.cdr.markForCheck(); }
323
+
324
+ // Ensure the group validator ran and update the pwdMismatch flag used by template bindings
325
+ try { this.cdr.detectChanges(); } catch (e) { this.cdr.markForCheck(); }
326
+ // update pwdMismatch from the form-level validator result so existing template bindings work
327
+ this.pwdMismatch = !!this.form.hasError('passwordMismatch');
328
+ // Debug logs to verify validation state
329
+ // eslint-disable-next-line no-console
330
+ console.log('submit: form.errors=', this.form.errors, 'pwdMismatch=', this.pwdMismatch);
331
+ // eslint-disable-next-line no-console
332
+ console.log('submit: email.errors=', this.form.get('email')?.errors, 'email.invalid=', this.form.get('email')?.invalid);
333
+
334
+ // Build invalid fields message for screen readers
335
+ const missing: string[] = [];
336
+ const checks: Array<[string, string]> = [ ['name','First name'], ['lastName','Last name'], ['email','Email'], ['roleGroup','Role'], ['password','Password'], ['confirmPassword','Confirm password'] ];
337
+ for (const [k,label] of checks) {
338
+ if (this.form.get(k)?.invalid) missing.push(label);
339
+ }
340
+ if (this.form.get('twoFAOptIn')?.value && this.form.get('twoFAMethod')?.value === 'sms') {
341
+ if (this.form.get('twoFAPhone')?.invalid) missing.push('2FA phone');
342
+ }
343
+ if (!this.form.get('terms')?.value) missing.push('Terms and Privacy');
344
+ this.invalidFieldsMessage = missing.length ? 'Please correct the following fields: ' + missing.join(', ') : '';
345
+
346
+ // Check if the form is invalid
347
+ if (this.form.invalid) {
348
+ return;
349
+ }
350
+
351
+ // Check terms & conditions acceptance
352
+ if (!this.form.get('terms')?.value) {
353
+ this.termsError = 'Please accept Terms & Conditions.';
354
+ return;
355
+ }
356
+ this.termsError = '';
357
+
358
+ // Additional2FA validation if opted-in
359
+ this.twoFAError = '';
360
+ if (this.form.get('twoFAOptIn')?.value) {
361
+ const method = this.form.get('twoFAMethod')?.value;
362
+ if (!method) {
363
+ this.twoFAError = 'Please select a2FA method (Email or SMS).';
364
+ return;
365
+ }
366
+ if (method === 'email') {
367
+ const useSame = this.form.get('twoFAUseSameEmail')?.value;
368
+ if (!useSame) {
369
+ const alt = this.form.get('twoFAAltEmail');
370
+ if (!alt || alt.invalid) {
371
+ this.twoFAError = 'Please provide a valid alternate email for2FA.';
372
+ return;
373
+ }
374
+ }
375
+ }
376
+ if (method === 'sms') {
377
+ const phone = this.form.get('twoFAPhone');
378
+ if (!phone || phone.invalid) {
379
+ this.twoFAError = 'Please provide a valid phone number for SMS2FA.';
380
+ return;
381
+ }
382
+ }
383
+ }
384
+
385
+ // recaptcha check
386
+ // if (!this.form.get('recaptcha')?.value) {
387
+ // this.recaptchaError = 'Please verify that you are not a robot.';
388
+ // }
389
+
390
+ // if (this.form.invalid || this.recaptchaError) {
391
+ if (this.form.invalid) {
392
+ return;
393
+ }
394
+
395
+ this.loading = true; // Set loading to true when starting submission
396
+ try {
397
+ // Prepare the payload to send to the backend
398
+ const payload: any = {
399
+ name: this.control('name')?.value,
400
+ lastName: this.control('lastName')?.value,
401
+ email: this.control('email')?.value,
402
+ password: this.control('password')?.value,
403
+ role: this.control('role')?.value
404
+ };
405
+
406
+ // Include2FA settings when opted-in
407
+ if (this.form.get('twoFAOptIn')?.value) {
408
+ payload.twoFAEnabled = true;
409
+ payload.twoFAMethod = this.form.get('twoFAMethod')?.value;
410
+ if (payload.twoFAMethod === 'email') {
411
+ payload.twoFAContact = this.form.get('twoFAUseSameEmail')?.value ? payload.email : this.form.get('twoFAAltEmail')?.value;
412
+ } else if (payload.twoFAMethod === 'sms') {
413
+ payload.twoFAContact = this.form.get('twoFAPhone')?.value;
414
+ }
415
+ } else {
416
+ payload.twoFAEnabled = false;
417
+ }
418
+
419
+ // Make the HTTP request
420
+ this.signUpService.signUp(payload).subscribe(
421
+ (response) => {
422
+ this.errorMessage = '';
423
+ console.log("Sign-up request sent successfully!");
424
+ this.loading = false; // Reset loading on success
425
+ // Wait for loader to finish, then navigate
426
+ setTimeout(() => {
427
+ this.switchToSignIn.emit();
428
+ },500);
429
+ },
430
+ (error) => {
431
+ // If backend indicates duplicate email/user, set a specific error on the email control
432
+ const emailCtrl = this.form.get('email');
433
+ if (error && (error.status ===409 || error.status ===400)) {
434
+ this.errorMessage = 'Email already exists!';
435
+ // set reactive form error on email so template can render inline message
436
+ if (emailCtrl) {
437
+ const currentErrors = emailCtrl.errors || {};
438
+ emailCtrl.setErrors({ ...currentErrors, emailExists: true });
439
+ }
440
+ } else {
441
+ this.errorMessage = 'An error occurred. Please try again.';
442
+ }
443
+ this.loading = false; // Reset loading on error
444
+ this.cdr.markForCheck();
445
+ // keep the error visible briefly
446
+ setTimeout(() => {
447
+ this.errorMessage = '';
448
+ this.cdr.markForCheck();
449
+ },3000);
450
+ }
451
+ );
452
+ } catch (error) {
453
+ console.error("Error occurred during sign-up:", error); // Log any errors from the API call
454
+ this.loading = false; // Reset loading on exception
455
+ }
456
+ }
457
+
458
+ navigateHome() { this.router.navigateByUrl('/'); }
459
+
460
+ goToLogin() {
461
+ this.switchToSignIn.emit(); // ← Emit event instead of router navigation
462
+ }
463
+
464
+ closePopup() {
465
+ try {
466
+ // dispatch a global event so parent or other listeners always can close modals
467
+ window.dispatchEvent(new CustomEvent('auth-close'));
468
+ } catch (e) {
469
+ // ignore
470
+ }
471
+
472
+ this.close.emit();
473
+ // Defensive: remove modal/backdrop if parent didn't hide them
474
+ try {
475
+ const modal = document.querySelector('.modal');
476
+ if (modal && modal.parentElement) modal.parentElement.removeChild(modal);
477
+ const backdrop = document.querySelector('.modal-backdrop');
478
+ if (backdrop && backdrop.parentElement) backdrop.parentElement.removeChild(backdrop);
479
+ } catch (e) {
480
+ console.warn('Failed to remove modal/backdrop DOM elements', e);
481
+ }
482
+ // Ensure change detection updates
483
+ this.cdr.markForCheck();
484
+ }
485
+
486
+ tr(key: string): string {
487
+ const map: Record<string, string> = { title: 'Create your account', subtitle: 'Join to continue' };
488
+ return map[key] || '';
489
+ }
490
+
491
+ goToSignIn() {
492
+ // Emit to parent when embedded so the card can slide back
493
+ this.switchToSignIn.emit();
494
+ }
495
+
496
+ goToSignUp() {
497
+ // no-op when embedded
498
+ }
499
+
500
+ onPasswordKey(event: KeyboardEvent) {
501
+ const caps = event.getModifierState && event.getModifierState('CapsLock');
502
+ this.capsLockOn = !!caps;
503
+ this.cdr.markForCheck();
504
+ }
505
+
506
+ // Called when user changes top-level role group
507
+ onRoleGroupChange(groupKey: string) {
508
+ const group = this.roleGroups.find(g => g.key === groupKey);
509
+ this.availableSubRoles = group ? group.subs : [];
510
+ // set the form's role to the selected group key so backend receives the grouping
511
+ this.form.get('role')?.setValue(groupKey);
512
+ this.cdr.markForCheck();
513
+ }
514
+
515
+ // Simple phone formatting: keep '+' then digits, group by spaces for readability
516
+ formatPhone(event: Event) {
517
+ const input = event.target as HTMLInputElement;
518
+ if (!input) return;
519
+ // strip non-digit except leading +
520
+ let v = input.value.trim();
521
+ const hasPlus = v.startsWith('+');
522
+ v = v.replace(/[^0-9]/g, '');
523
+ // group digits in blocks of3 for readability
524
+ const parts: string[] = [];
525
+ while (v.length) {
526
+ parts.push(v.substring(0,3));
527
+ v = v.substring(3);
528
+ }
529
+ input.value = (hasPlus ? '+' : '') + parts.join(' ');
530
+ // update reactive form control value without emitting extra events
531
+ const control = this.form.get('twoFAPhone');
532
+ if (control) {
533
+ control.setValue(input.value, { emitEvent: false });
534
+ }
535
+ }
536
+
537
+ // Return true if all mandatory fields have a non-empty value (used to enable the Create Account button)
538
+ isFormFilled(): boolean {
539
+ try {
540
+ const req = ['name', 'lastName', 'email', 'password', 'confirmPassword', 'roleGroup'];
541
+ let allFilled = true;
542
+ for (const k of req) {
543
+ const v = (this.form.get(k)?.value || '').toString().trim();
544
+ if (!v) { allFilled = false; break; }
545
+ }
546
+ if (!allFilled) {
547
+ const domIds = { name: 'firstName', lastName: 'lastName', email: 'email', password: 'password', confirmPassword: 'confirmPassword', roleGroup: 'roleGroup' };
548
+ let domAll = true;
549
+ for (const k of Object.keys(domIds)) {
550
+ const el = document.getElementById((domIds as any)[k]) as HTMLInputElement | HTMLSelectElement | null;
551
+ if (!el) { domAll = false; break; }
552
+ const val = (el as HTMLInputElement).value || '';
553
+ if (!val.toString().trim()) { domAll = false; break; }
554
+ }
555
+ if (domAll) {
556
+ try {
557
+ this.form.get('name')?.setValue((document.getElementById('firstName') as HTMLInputElement).value, { emitEvent: false });
558
+ this.form.get('lastName')?.setValue((document.getElementById('lastName') as HTMLInputElement).value, { emitEvent: false });
559
+ this.form.get('email')?.setValue((document.getElementById('email') as HTMLInputElement).value, { emitEvent: false });
560
+ this.form.get('password')?.setValue((document.getElementById('password') as HTMLInputElement).value, { emitEvent: false });
561
+ this.form.get('confirmPassword')?.setValue((document.getElementById('confirmPassword') as HTMLInputElement).value, { emitEvent: false });
562
+ const rg = (document.getElementById('roleGroup') as HTMLSelectElement).value;
563
+ if (rg) this.form.get('roleGroup')?.setValue(rg, { emitEvent: false });
564
+ } catch (e) { }
565
+ allFilled = true;
566
+ }
567
+ }
568
+ if (!allFilled) return false;
569
+ const termsChecked = !!this.form.get('terms')?.value || !!(document.getElementById('terms') as HTMLInputElement)?.checked;
570
+ if (!termsChecked) return false;
571
+ const twoFAOptIn = !!this.form.get('twoFAOptIn')?.value;
572
+ if (twoFAOptIn) {
573
+ const method = (this.form.get('twoFAMethod')?.value || '').toString().trim();
574
+ if (!method) return false;
575
+ if (method === 'sms') {
576
+ const phoneVal = (this.form.get('twoFAPhone')?.value || '').toString().trim() || (document.getElementById('twoFAPhone') as HTMLInputElement)?.value || '';
577
+ if (!phoneVal.toString().trim()) return false;
578
+ }
579
+ }
580
+ return true;
581
+ } catch (e) {
582
+ return false;
583
+ }
584
+ }
585
+
586
+ // Ensure we trigger change detection when controls change so template reevaluates the method
587
+ // (formSubscription added in ngOnInit keeps canEnable updated)
588
+
589
+ // Visual helpers used by the template
590
+ // Return true if the name contains any digits (show cross after submit)
591
+ public nameHasDigits(controlName: string): boolean {
592
+ if (!this.submitted) return false;
593
+ const v = (this.form.get(controlName)?.value || '').toString();
594
+ return /\d/.test(v);
595
+ }
596
+
597
+ // Return true when password mismatch should show the cross (after submit)
598
+ public passwordMismatchClass(): boolean {
599
+ return !!this.pwdMismatch;
600
+ }
601
  }
602
 
603
+ // standalone validator follows
604
  function passwordPolicyValidator(control: AbstractControl): ValidationErrors | null {
605
+ const value = control.value || '';
606
+ // Policy: min8 chars,1 uppercase,1 lowercase,1 number,1 special char
607
+ const policy = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>\/?]).{8,}$/;
608
+ if (!policy.test(value)) {
609
+ return { passwordPolicy: true };
610
+ }
611
+ return null;
612
  }
src/app/homepage/sign-up/sign-up.service.ts CHANGED
@@ -1,16 +1,15 @@
1
  import { Injectable } from '@angular/core';
2
- import { HttpClient } from '@angular/common/http';
3
- import { Observable } from 'rxjs';
4
- import { environment } from 'src/environments/environment'; // adjust path if needed
5
 
6
  @Injectable({
7
  providedIn: 'root'
8
  })
9
  export class SignUpService {
10
- private apiUrl = environment.pyDetectApiUrl;
11
-
12
  constructor(private http: HttpClient) { }
13
 
 
14
  signUp(payload: any): Observable<any> {
15
  return this.http.post(`${this.apiUrl}/sign-up`, payload);
16
  }
 
1
  import { Injectable } from '@angular/core';
2
+ import { HttpClient } from '@angular/common/http'; // Import HttpClient for API calls
3
+ import { Observable } from 'rxjs'; // Import Observable for async handling
 
4
 
5
  @Injectable({
6
  providedIn: 'root'
7
  })
8
  export class SignUpService {
9
+ private apiUrl = 'http://127.0.0.1:5002'; // 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
  }
src/app/legal/privacy.component.ts ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Component } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'app-privacy',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <section style="padding:24px; max-width:900px; margin:32px auto; background:#fff; border-radius:8px; box-shadow:08px24px rgba(0,0,0,0.08);">
10
+ <h1>Privacy Policy</h1>
11
+ <p><strong>(For Py-Detect Application)</strong></p>
12
+
13
+ <h2>1. Introduction</h2>
14
+ <p>This Privacy Policy explains how Pykara Technologies collects, uses, stores, and protects your information when you use the PyDetect application. We respect your privacy and handle your data responsibly.</p>
15
+
16
+ <h2>2. Information We Collect</h2>
17
+ <h3>a. Personal Information</h3>
18
+ <ul>
19
+ <li>Name</li>
20
+ <li>Email address</li>
21
+ <li>Profile details (role, department)</li>
22
+ <li>Login activity</li>
23
+ </ul>
24
+
25
+ <h3>b. Case-Related Information</h3>
26
+ <ul>
27
+ <li>Crime details</li>
28
+ <li>Suspect details</li>
29
+ <li>Evidence</li>
30
+ <li>Notes and reports</li>
31
+ </ul>
32
+
33
+ <h3>c. Audio & Video Data</h3>
34
+ <ul>
35
+ <li>Voice recordings</li>
36
+ <li>Video recordings</li>
37
+ <li>Body-language metrics</li>
38
+ <li>AI analysis summaries</li>
39
+ </ul>
40
+
41
+ <h3>d. Technical Data</h3>
42
+ <ul>
43
+ <li>IP address</li>
44
+ <li>Browser details</li>
45
+ <li>Device details</li>
46
+ <li>Log files</li>
47
+ <li>Activity history</li>
48
+ </ul>
49
+
50
+ <h2>3. How We Use Your Data</h2>
51
+ <p>We use the collected data to: create and manage user accounts, provide investigation features, generate AI-based analysis, improve system accuracy, maintain security, prevent unauthorized access, and respond to support requests. We do not sell or rent your data to third parties.</p>
52
+
53
+ <h2>4. Legal Basis for Data Processing</h2>
54
+ <p>We process your data based on user consent, contractual need, legal obligations, and security and fraud-prevention.</p>
55
+
56
+ <h2>5. Sharing of Information</h2>
57
+ <p>We may share data only with authorized users (based on role), internal team members for support, and legal authorities if required by law. We never share your data for marketing purposes.</p>
58
+
59
+ <h2>6. Data Storage and Security</h2>
60
+ <p>We use secure methods to store your data, including encrypted databases, restricted access, secure servers, and regular audits. Audio and video data may be stored in cloud storage with encryption.</p>
61
+
62
+ <h2>7. Data Retention</h2>
63
+ <p>We keep your data for as long as required for active investigations, legal compliance, and system analysis. After that, data may be archived or deleted securely.</p>
64
+
65
+ <h2>8. Your Rights</h2>
66
+ <p>Depending on your region (India/UK/EU/US), you may have rights including access, correction, deletion, restriction, and withdrawal of consent. Send requests to <a href="mailto:support@pykara.ai">support@pykara.ai</a>.</p>
67
+
68
+ <h2>9. Cookies</h2>
69
+ <p>The application may use cookies for login sessions, security, and preferences. You may disable cookies, but some features may not work.</p>
70
+
71
+ <h2>10. Changes to This Policy</h2>
72
+ <p>We may update this Privacy Policy from time to time. The updated version will be available on our website.</p>
73
+
74
+ <h2>11. Contact Information</h2>
75
+ <p>For questions or privacy concerns: <a href="mailto:info@pykara.ai">info@pykara.ai</a></p>
76
+ <p>Company: Pykara Technologies</p>
77
+ </section>
78
+ `
79
+ })
80
+ export class PrivacyComponent {}
src/app/legal/terms.component.ts ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Component } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'app-terms',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <section style="padding:24px; max-width:900px; margin:32px auto; background:#fff; border-radius:8px; box-shadow:08px24px rgba(0,0,0,0.08);">
10
+ <h1>Terms & Conditions (T&C)</h1>
11
+ <p><strong>(For Py-Detect Application)</strong></p>
12
+
13
+ <h2>1. Introduction</h2>
14
+ <p>Welcome to Py-Detect, a platform developed by Pykara Technologies. By creating an account or using our application, you agree to follow the terms explained here.</p>
15
+ <p>If you do not agree with any part of these terms, please do not use this application.</p>
16
+
17
+ <h2>2. Eligibility</h2>
18
+ <p>You must:</p>
19
+ <ul>
20
+ <li>Be at least18 years old</li>
21
+ <li>Provide accurate information</li>
22
+ <li>Use the application only for lawful activities</li>
23
+ </ul>
24
+ <p>Roles such as Admin, Investigator, Lawyer, and Supervisor may require approval from Pykara Technologies.</p>
25
+
26
+ <h2>3. User Responsibilities</h2>
27
+ <p>You agree to:</p>
28
+ <ul>
29
+ <li>Keep your login details safe</li>
30
+ <li>Use the application responsibly</li>
31
+ <li>Not misuse the platform in any way</li>
32
+ <li>Not attempt to damage, hack, or interfere with the system</li>
33
+ </ul>
34
+ <p>You are responsible for all activities that happen under your account.</p>
35
+
36
+ <h2>4. Prohibited Activities</h2>
37
+ <ul>
38
+ <li>Upload harmful, illegal, or misleading content</li>
39
+ <li>Reverse-engineer the application</li>
40
+ <li>Access data you are not authorized to view</li>
41
+ <li>Share confidential investigation data with unauthorized persons</li>
42
+ </ul>
43
+
44
+ <h2>5. Application Purpose</h2>
45
+ <p>PyDetect is designed to support:</p>
46
+ <ul>
47
+ <li>Data collection</li>
48
+ <li>Analytical processing</li>
49
+ <li>AI-based insights</li>
50
+ <li>Investigation workflows</li>
51
+ </ul>
52
+ <p>The AI outputs are for support purposes only and are not guaranteed to be100% accurate.</p>
53
+
54
+ <h2>6. Data Usage</h2>
55
+ <p>You allow us to process:</p>
56
+ <ul>
57
+ <li>Personal details</li>
58
+ <li>Case data</li>
59
+ <li>Audio/video recordings</li>
60
+ <li>Analytical results</li>
61
+ </ul>
62
+ <p>All processing will follow our Privacy Policy.</p>
63
+
64
+ <h2>7. Account Suspension & Termination</h2>
65
+ <p>Your account may be suspended or terminated if:</p>
66
+ <ul>
67
+ <li>You violate these terms</li>
68
+ <li>You misuse data</li>
69
+ <li>You attempt unauthorized access</li>
70
+ <li>You provide false information</li>
71
+ </ul>
72
+
73
+ <h2>8. Intellectual Property</h2>
74
+ <p>All code, design, texts, graphics, and features belong to Pykara Technologies. You are not allowed to copy, modify, or sell any part of the application.</p>
75
+
76
+ <h2>9. Limitation of Liability</h2>
77
+ <p>Pykara Technologies is not responsible for:</p>
78
+ <ul>
79
+ <li>Data loss caused by user actions</li>
80
+ <li>Incorrect interpretation of AI results</li>
81
+ <li>Operational delays due to internet or device issues</li>
82
+ <li>Any damage caused by misuse of the application</li>
83
+ </ul>
84
+
85
+ <h2>10. Changes to Terms</h2>
86
+ <p>We may update these Terms & Conditions at any time. Updated versions will be posted on our website.</p>
87
+
88
+ <h2>11. Contact Information</h2>
89
+ <p>For any concerns or questions: <a href="mailto:info@pykara.ai">info@pykara.ai</a></p>
90
+ <p>Company: Pykara Technologies</p>
91
+ </section>
92
+ `
93
+ })
94
+ export class TermsComponent {}
src/app/services/app.component.css ADDED
File without changes
src/app/shared/eye-toggle/eye-toggle.component.css ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root { --eye-size:28px; --eye-gap:10px; }
2
+
3
+ /* Base button: neutral by default; variants handle positioning */
4
+ .eye-toggle {
5
+ display: inline-flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ width: var(--eye-size);
9
+ height: var(--eye-size);
10
+ border-radius:6px;
11
+ border: none;
12
+ cursor: pointer;
13
+ color: inherit;
14
+ z-index:3; /* above input */
15
+ padding:0;
16
+ line-height:1;
17
+ }
18
+
19
+ .eye-toggle i { font-size:0.95rem; pointer-events:none; }
20
+ .eye-toggle:focus { outline:2px solid rgba(29,233,182,0.12); outline-offset:2px; }
21
+
22
+ /* Visual variants */
23
+ .eye-toggle.variant-signin { background: rgba(255,255,255,0.06); color:#23395d; }
24
+ .eye-toggle.variant-signup { background: rgba(0,0,0,0.04); color:#23395d; }
25
+
26
+ /* Reserve input right padding when a toggle is present */
27
+ .form-field.has-eye input,
28
+ .signin-field.has-eye input { padding-right: calc(var(--eye-size) + var(--eye-gap) +6px); }
29
+
30
+ /* Fallback when markup doesn't add .has-eye */
31
+ .form-field:has(.eye-toggle) input,
32
+ .signin-field:has(.eye-toggle) input { padding-right: calc(var(--eye-size) + var(--eye-gap) +6px); }
33
+
34
+ /* Small screens */
35
+ @media (max-width:700px) {
36
+ :root { --eye-size:24px; --eye-gap:8px; }
37
+ .eye-toggle { width: var(--eye-size); height: var(--eye-size); }
38
+ }
39
+
40
+ /* Separate positioning by context to avoid cross-page impact */
41
+ /* Sign-in page context: position absolutely inside field */
42
+ :host-context(.signin-popup) .eye-toggle.variant-signin {
43
+ position: absolute;
44
+ right:10px;
45
+ top:50%;
46
+ transform: translateY(-50%);
47
+ }
48
+
49
+ /* Sign-up page context (if used similarly), keep independent positioning */
50
+ :host-context(.signup-popup) .eye-toggle.variant-signup {
51
+ position: absolute;
52
+ right:10px;
53
+ top:50%;
54
+ transform: translateY(-50%);
55
+ }
src/app/shared/eye-toggle/eye-toggle.component.ts ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ @Component({
5
+ selector: 'app-eye-toggle',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ template: `
9
+ <button
10
+ type="button"
11
+ class="eye-toggle"
12
+ [class.variant-signin]="variant === 'signin'"
13
+ [class.variant-signup]="variant === 'signup'"
14
+ (click)="onToggle()"
15
+ [attr.aria-label]="pressed ? 'Hide password' : 'Show password'"
16
+ [attr.aria-pressed]="pressed"
17
+ >
18
+ <i [ngClass]="pressed ? 'fa-solid fa-eye' : 'fa-solid fa-eye-slash'" aria-hidden="true"></i>
19
+ </button>
20
+ `,
21
+ styleUrls: ['./eye-toggle.component.css']
22
+ })
23
+ export class EyeToggleComponent {
24
+ @Input() pressed = false;
25
+ // new input variant: 'signin' | 'signup'
26
+ @Input() variant: 'signin' | 'signup' = 'signin';
27
+ @Output() toggle = new EventEmitter<void>();
28
+
29
+ onToggle() {
30
+ this.toggle.emit();
31
+ }
32
+ }
src/assets/background-21.jpg ADDED

Git LFS Details

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

Git LFS Details

  • SHA256: edb3629b8b1cc4f1d22cdc586b1c704e82686ec5b8772d23448245038aaab6be
  • Pointer size: 132 Bytes
  • Size of remote file: 4.07 MB
src/assets/side-2scale-right.jpg ADDED

Git LFS Details

  • SHA256: 5348ab8d6addd57a8647fdb78ba2abac73d55c975394dd19e2c910051d161f3d
  • Pointer size: 130 Bytes
  • Size of remote file: 30 kB
src/assets/side-scale-right.jpg ADDED

Git LFS Details

  • SHA256: 23e213b3c4cb4aedd9686ff2888641808ea7f26341d75421702d73a23779d38a
  • Pointer size: 133 Bytes
  • Size of remote file: 13.1 MB
src/environments/environment.ts CHANGED
@@ -9,7 +9,7 @@ const isLocal =
9
 
10
  // Decide API URL based on where the app is running
11
  const pyDetectApiUrl = isLocal
12
- ? 'http://127.0.0.1:5000' // your local Flask
13
  : 'https://pykara-Py-detect-backend.hf.space'; // Hugging Face backend
14
 
15
  export const environment = {
 
9
 
10
  // Decide API URL based on where the app is running
11
  const pyDetectApiUrl = isLocal
12
+ ? 'http://127.0.0.1:5002' // your local Flask
13
  : 'https://pykara-Py-detect-backend.hf.space'; // Hugging Face backend
14
 
15
  export const environment = {
src/favicon.ico CHANGED
tsconfig.app.json CHANGED
@@ -1,14 +1,15 @@
1
  /* To learn more about this file see: https://angular.io/config/tsconfig. */
2
  {
3
- "extends": "./tsconfig.json",
4
- "compilerOptions": {
5
- "outDir": "./out-tsc/app",
6
- "types": []
7
- },
8
- "files": [
9
- "src/main.ts"
10
- ],
11
- "include": [
12
- "src/**/*.d.ts"
13
- ]
 
14
  }
 
1
  /* To learn more about this file see: https://angular.io/config/tsconfig. */
2
  {
3
+ "extends": "./tsconfig.json",
4
+ "compilerOptions": {
5
+ "outDir": "./out-tsc/app",
6
+ "types": []
7
+ },
8
+ "include": [
9
+ "src/**/*.ts"
10
+ ],
11
+ "exclude": [
12
+ "src/test.ts",
13
+ "src/**/*.spec.ts"
14
+ ]
15
  }