RajalashmiNagarajan
commited on
Commit
·
ed79486
1
Parent(s):
e9a0b07
login
Browse files- Py-Detect.esproj.user +7 -2
- obj/Debug/Py-Detect.esproj.FileListAbsolute.txt +1 -0
- obj/Debug/package.g.props +4 -0
- package-lock.json +1 -29
- src.zip +3 -0
- src/app/app-routing.module.ts +3 -0
- src/app/app.component.css +4 -0
- src/app/homepage/auth-card/auth-card.component.ts +5 -1
- src/app/homepage/auth-wrapper.component.ts +5 -0
- src/app/homepage/homepage.component.css +89 -25
- src/app/homepage/homepage.component.html +70 -101
- src/app/homepage/homepage.component.ts +3 -3
- src/app/homepage/sign-in/sign-in.component.css +452 -363
- src/app/homepage/sign-in/sign-in.component.html +124 -79
- src/app/homepage/sign-in/sign-in.component.ts +153 -47
- src/app/homepage/sign-in/sign-in.service.ts +1 -4
- src/app/homepage/sign-up/sign-up.component.css +660 -320
- src/app/homepage/sign-up/sign-up.component.html +130 -81
- src/app/homepage/sign-up/sign-up.component.ts +597 -252
- src/app/homepage/sign-up/sign-up.service.ts +4 -5
- src/app/legal/privacy.component.ts +80 -0
- src/app/legal/terms.component.ts +94 -0
- src/app/services/app.component.css +0 -0
- src/app/shared/eye-toggle/eye-toggle.component.css +55 -0
- src/app/shared/eye-toggle/eye-toggle.component.ts +32 -0
- src/assets/background-21.jpg +3 -0
- src/assets/background1.jpg +3 -0
- src/assets/side-2scale-right.jpg +3 -0
- src/assets/side-scale-right.jpg +3 -0
- src/environments/environment.ts +1 -1
- src/favicon.ico +0 -0
- tsconfig.app.json +12 -11
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 893 |
}
|
| 894 |
|
| 895 |
.use-card {
|
| 896 |
background: #fff;
|
| 897 |
color: #000;
|
| 898 |
-
flex:
|
| 899 |
-
min-width:
|
| 900 |
border-radius: 12px;
|
| 901 |
-
padding:
|
| 902 |
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
| 903 |
transition: transform 0.3s;
|
| 904 |
}
|
| 905 |
|
| 906 |
-
|
| 907 |
-
|
| 908 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 909 |
|
| 910 |
-
|
| 911 |
-
|
| 912 |
-
|
| 913 |
-
|
| 914 |
-
}
|
| 915 |
|
| 916 |
-
|
| 917 |
-
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
| 921 |
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 927 |
|
| 928 |
.social-icons {
|
| 929 |
display: flex;
|
|
@@ -1353,11 +1379,49 @@ footer .social-icons {
|
|
| 1353 |
color: #0d9de3;
|
| 1354 |
}
|
| 1355 |
|
| 1356 |
-
|
| 1357 |
-
|
| 1358 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 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 (
|
| 202 |
-
<section id="how-it-works" class="how-it-works
|
| 203 |
<h2 class="section-title how-title">How It Works</h2>
|
| 204 |
-
<div class="how-
|
| 205 |
-
|
| 206 |
-
<
|
| 207 |
-
|
| 208 |
-
<
|
| 209 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
</p>
|
| 211 |
-
</
|
| 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 |
-
<
|
| 221 |
-
<
|
| 222 |
-
<
|
| 223 |
-
<
|
| 224 |
-
<
|
| 225 |
-
<li>
|
|
|
|
| 226 |
<li>Sudden pitch changes</li>
|
| 227 |
</ul>
|
| 228 |
-
<p class="how-text">These cues help identify emotional states and potential
|
| 229 |
-
</
|
| 230 |
-
|
| 231 |
-
<!-- Facial
|
| 232 |
-
<
|
| 233 |
-
<
|
| 234 |
-
<
|
| 235 |
-
<
|
| 236 |
-
<
|
| 237 |
-
<li>
|
| 238 |
-
<li>
|
|
|
|
| 239 |
</ul>
|
| 240 |
-
<p class="how-text">
|
| 241 |
-
</
|
| 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 |
-
<!--
|
| 263 |
-
<
|
| 264 |
-
<
|
| 265 |
-
<
|
| 266 |
-
<
|
| 267 |
-
<
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
</ul>
|
| 272 |
-
</article>
|
| 273 |
|
| 274 |
-
<!--
|
| 275 |
-
<
|
| 276 |
-
<
|
| 277 |
-
<
|
| 278 |
-
<
|
| 279 |
-
<
|
| 280 |
-
<li>
|
| 281 |
-
<li>
|
| 282 |
-
<li>
|
| 283 |
</ul>
|
| 284 |
-
</
|
| 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 |
-
<
|
| 301 |
-
<
|
| 302 |
-
<
|
| 303 |
-
<
|
| 304 |
-
<
|
| 305 |
-
<li>
|
| 306 |
-
<li>
|
| 307 |
-
<li>
|
| 308 |
-
<li>Admin
|
| 309 |
</ul>
|
| 310 |
-
</
|
|
|
|
| 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 & Micro‑Expression 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 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 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: #
|
| 101 |
font-weight: 600;
|
| 102 |
-
margin-bottom:
|
| 103 |
font-size: 1rem;
|
| 104 |
letter-spacing: 0.5px;
|
| 105 |
}
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
background: #fff;
|
| 108 |
color: #18314a;
|
| 109 |
border: none;
|
| 110 |
-
border-radius: 8px;
|
| 111 |
-
padding:
|
| 112 |
font-size: 1rem;
|
| 113 |
-
margin-bottom:
|
| 114 |
-
box-shadow: 0 1px
|
| 115 |
transition: border 0.2s, box-shadow 0.2s;
|
| 116 |
}
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 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:
|
| 137 |
-
margin-top: -
|
| 138 |
}
|
| 139 |
.remember-me {
|
| 140 |
display: flex;
|
| 141 |
align-items: center;
|
| 142 |
-
gap:
|
| 143 |
font-size: 1rem;
|
| 144 |
-
color: #
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 166 |
padding: 14px 0;
|
| 167 |
-
font-size: 1.
|
| 168 |
font-weight: 700;
|
| 169 |
margin-bottom: 18px;
|
| 170 |
cursor: pointer;
|
| 171 |
transition: background 0.2s, color 0.2s;
|
| 172 |
}
|
| 173 |
-
|
| 174 |
-
|
| 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 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 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 |
-
|
| 214 |
-
|
| 215 |
-
|
| 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:
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 260 |
}
|
| 261 |
|
| 262 |
@keyframes spin {
|
|
@@ -299,7 +358,7 @@ form {
|
|
| 299 |
font-weight: 600;
|
| 300 |
}
|
| 301 |
.signin-welcome {
|
| 302 |
-
color: #
|
| 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: #
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
max-width:
|
| 409 |
-
|
| 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 |
-
|
| 433 |
-
|
| 434 |
-
border: none;
|
| 435 |
border-radius: 8px;
|
| 436 |
-
|
| 437 |
-
font-size: 1rem;
|
| 438 |
margin-bottom: 12px;
|
| 439 |
-
box-shadow: 0 1px 4px #0002;
|
| 440 |
-
width: 100%;
|
| 441 |
}
|
| 442 |
-
.modal-close {
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
}
|
| 456 |
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 463 |
display: flex;
|
| 464 |
-
|
|
|
|
|
|
|
|
|
|
| 465 |
margin-bottom: 12px;
|
| 466 |
}
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
justify-content: center;
|
| 471 |
}
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 484 |
display: flex;
|
| 485 |
align-items: center;
|
| 486 |
-
|
|
|
|
|
|
|
| 487 |
}
|
| 488 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 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 |
-
.
|
| 525 |
-
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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:
|
| 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
|
| 887 |
}
|
| 888 |
.divider {
|
| 889 |
flex:1;
|
| 890 |
height:1px;
|
| 891 |
background: #b0b8c1;
|
| 892 |
-
margin:
|
| 893 |
}
|
| 894 |
.divider-or {
|
| 895 |
color: #23395d;
|
| 896 |
font-size:1.08em;
|
| 897 |
font-weight:600;
|
| 898 |
-
margin:
|
| 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:
|
| 913 |
display: flex;
|
| 914 |
align-items: center;
|
| 915 |
justify-content: center;
|
| 916 |
gap: 12px;
|
| 917 |
transition: background 0.2s, color 0.2s;
|
| 918 |
}
|
| 919 |
-
|
| 920 |
-
|
| 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:
|
| 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:
|
| 953 |
-
opacity:
|
| 954 |
}
|
| 955 |
-
|
|
|
|
|
|
|
|
|
|
| 956 |
position: absolute;
|
| 957 |
-
top:0;
|
|
|
|
| 958 |
width:100%;
|
| 959 |
height:100%;
|
| 960 |
-
|
| 961 |
-
|
| 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 |
-
.
|
| 1001 |
-
|
| 1002 |
-
|
| 1003 |
-
|
| 1004 |
-
|
| 1005 |
-
|
| 1006 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1007 |
}
|
| 1008 |
|
| 1009 |
-
.
|
| 1010 |
-
|
| 1011 |
-
|
| 1012 |
-
|
| 1013 |
-
|
| 1014 |
-
|
| 1015 |
-
|
|
|
|
| 1016 |
}
|
| 1017 |
|
| 1018 |
-
.
|
| 1019 |
-
|
| 1020 |
-
|
| 1021 |
-
|
| 1022 |
-
|
| 1023 |
-
|
| 1024 |
-
|
| 1025 |
}
|
| 1026 |
|
| 1027 |
-
.
|
| 1028 |
-
|
| 1029 |
-
|
| 1030 |
-
|
| 1031 |
-
|
| 1032 |
-
|
| 1033 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1034 |
}
|
| 1035 |
|
| 1036 |
-
.
|
| 1037 |
-
|
| 1038 |
-
|
| 1039 |
-
|
| 1040 |
-
|
| 1041 |
-
background: #18314a;
|
| 1042 |
-
opacity: 0.7;
|
| 1043 |
}
|
| 1044 |
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
|
| 1048 |
-
|
| 1049 |
-
|
| 1050 |
-
|
| 1051 |
-
opacity: 0.9;
|
| 1052 |
}
|
| 1053 |
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1061 |
}
|
| 1062 |
|
| 1063 |
-
.
|
| 1064 |
-
|
| 1065 |
-
|
| 1066 |
-
|
| 1067 |
-
|
| 1068 |
-
|
| 1069 |
-
|
| 1070 |
-
}
|
| 1071 |
|
| 1072 |
-
|
| 1073 |
-
.
|
| 1074 |
-
|
| 1075 |
-
|
| 1076 |
-
|
| 1077 |
-
|
| 1078 |
-
|
| 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 |
-
|
| 1111 |
-
.
|
| 1112 |
-
|
| 1113 |
-
|
| 1114 |
-
|
| 1115 |
-
|
| 1116 |
-
|
| 1117 |
-
|
| 1118 |
-
|
| 1119 |
-
|
| 1120 |
-
|
| 1121 |
-
|
| 1122 |
-
|
| 1123 |
-
|
| 1124 |
-
|
| 1125 |
-
|
| 1126 |
-
|
| 1127 |
-
.ring9 { width:60px; height:60px; top:65%; left:36%; border-color: #fff; opacity:0.7; }
|
| 1128 |
|
| 1129 |
-
.side-
|
| 1130 |
-
|
| 1131 |
-
|
| 1132 |
-
|
| 1133 |
-
|
| 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 |
-
.
|
| 1153 |
-
|
| 1154 |
-
|
| 1155 |
-
|
| 1156 |
-
|
| 1157 |
-
|
| 1158 |
-
|
| 1159 |
-
|
| 1160 |
-
|
| 1161 |
-
|
| 1162 |
-
margin-
|
|
|
|
|
|
|
| 1163 |
display: inline-block;
|
| 1164 |
-
|
| 1165 |
-
|
| 1166 |
-
|
| 1167 |
-
|
| 1168 |
-
|
| 1169 |
-
|
| 1170 |
-
|
| 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 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
</div>
|
| 49 |
-
<!--
|
|
|
|
| 50 |
</div>
|
| 51 |
|
| 52 |
-
<div class="main-panel" style="display: flex; flex-direction: column; align-items: center; justify-content: center; min-height:
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
| 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 |
-
|
| 68 |
-
<
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
</button>
|
| 71 |
-
|
|
|
|
|
|
|
| 72 |
</div>
|
| 73 |
</div>
|
|
|
|
| 74 |
<div class="signin-row signin-options-row">
|
| 75 |
<div class="remember-me">
|
| 76 |
-
<label
|
| 77 |
-
<
|
| 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 |
-
|
| 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 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 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="
|
| 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;">×</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';
|
| 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 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
|
|
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 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]],
|
| 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 |
-
//
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
},
|
| 101 |
(error) => {
|
| 102 |
this.loading = false;
|
| 103 |
console.log('Sign-in error:', error);
|
| 104 |
-
if (error && error.status ===
|
| 105 |
this.errorMessage = 'Password is incorrect';
|
| 106 |
} else {
|
| 107 |
this.errorMessage = 'An error occurred. Please try again.';
|
| 108 |
}
|
|
|
|
| 109 |
setTimeout(() => {
|
| 110 |
this.errorMessage = '';
|
| 111 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
| 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 |
-
|
|
|
|
| 71 |
}
|
| 72 |
|
| 73 |
@keyframes logoGlow {
|
|
@@ -82,12 +113,14 @@
|
|
| 82 |
|
| 83 |
.create-form {
|
| 84 |
width: 100%;
|
| 85 |
-
max-width:
|
| 86 |
display: grid;
|
| 87 |
grid-template-columns: 1fr 1fr;
|
| 88 |
-
gap:
|
| 89 |
align-items: start;
|
| 90 |
-
margin-bottom:
|
|
|
|
|
|
|
| 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 |
-
|
| 116 |
-
font-
|
|
|
|
| 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:
|
| 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:
|
| 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:
|
| 154 |
align-items: center;
|
| 155 |
-
|
|
|
|
|
|
|
|
|
|
| 156 |
margin-top: 8px;
|
|
|
|
| 157 |
}
|
| 158 |
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
}
|
| 164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
.create-btn {
|
| 166 |
-
|
| 167 |
-
width:
|
| 168 |
background: #23395d;
|
| 169 |
color: #fff;
|
| 170 |
-
padding:
|
| 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:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
}
|
| 179 |
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 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:
|
| 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: #
|
| 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 |
-
|
| 312 |
-
.
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
line-height: 1;
|
| 324 |
-
opacity: 0.9;
|
| 325 |
-
transition: color 0.2s, opacity 0.2s;
|
| 326 |
}
|
| 327 |
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
}
|
| 332 |
-
|
| 333 |
-
.form-field .eye-toggle:focus {
|
| 334 |
-
outline: none;
|
| 335 |
-
}
|
| 336 |
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
width: 22px;
|
| 340 |
-
height: 22px;
|
| 341 |
-
}
|
| 342 |
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
top: 28px;
|
| 347 |
-
}
|
| 348 |
}
|
| 349 |
|
| 350 |
-
.
|
| 351 |
-
|
| 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 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
|
|
|
|
|
|
| 370 |
}
|
| 371 |
|
| 372 |
-
.side-panel.side-right
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
background: linear-gradient(135deg, #38bdf8 0%, #7b2ff2 100%);
|
| 379 |
-
min-height: 100%;
|
| 380 |
}
|
| 381 |
|
| 382 |
-
.side-panel.side-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
min-height: 100%;
|
| 390 |
}
|
| 391 |
|
| 392 |
-
.
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
overflow: hidden;
|
| 399 |
}
|
| 400 |
|
| 401 |
-
.
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
|
|
|
|
|
|
|
|
|
| 408 |
}
|
| 409 |
|
| 410 |
-
.
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 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 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
| 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 |
-
.
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
}
|
| 448 |
|
| 449 |
-
.
|
| 450 |
-
position:
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
|
|
|
| 454 |
display: flex;
|
| 455 |
-
flex-direction: column;
|
| 456 |
align-items: center;
|
| 457 |
justify-content: center;
|
| 458 |
-
margin-top: 80px;
|
| 459 |
}
|
| 460 |
|
| 461 |
-
.
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
}
|
| 468 |
|
| 469 |
-
.
|
| 470 |
font-size: 1.08rem;
|
| 471 |
-
|
| 472 |
-
margin-bottom:
|
| 473 |
-
|
| 474 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
}
|
| 476 |
|
| 477 |
-
.
|
| 478 |
-
|
|
|
|
|
|
|
|
|
|
| 479 |
color: #fff;
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 483 |
}
|
| 484 |
|
| 485 |
-
/*
|
| 486 |
-
.
|
| 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:
|
| 495 |
-
|
| 496 |
-
animation: fadeInModalBg 0.3s;
|
| 497 |
}
|
| 498 |
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
|
|
|
|
|
|
| 507 |
}
|
| 508 |
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
|
|
|
|
|
|
|
|
|
| 519 |
}
|
| 520 |
|
| 521 |
-
.
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
padding:
|
| 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 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 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 |
-
.
|
| 567 |
-
|
| 568 |
-
|
| 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 |
-
.
|
| 582 |
-
|
|
|
|
|
|
|
| 583 |
}
|
| 584 |
|
| 585 |
-
/*
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 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 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 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 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 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 |
-
|
| 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 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
<div class="form-row">
|
| 25 |
<div class="form-field">
|
| 26 |
-
<label for="firstName">First Name
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
<
|
| 30 |
-
|
| 31 |
</div>
|
| 32 |
<div class="form-field">
|
| 33 |
-
<label for="lastName">Last Name
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
<
|
| 37 |
-
|
| 38 |
</div>
|
| 39 |
</div>
|
| 40 |
<div class="form-row">
|
| 41 |
-
<div class="form-field">
|
| 42 |
-
<label for="email">Email
|
| 43 |
-
|
| 44 |
-
<
|
| 45 |
-
<small *ngIf="controlHasError('email','
|
|
|
|
|
|
|
| 46 |
</div>
|
| 47 |
<div class="form-field role-field-wrapper">
|
| 48 |
-
<label for="
|
| 49 |
-
|
| 50 |
-
<button class="info-btn" type="button" (click)="showInfo = true">i</button>
|
| 51 |
</label>
|
| 52 |
-
<select id="
|
| 53 |
-
<option value="">-- Select
|
| 54 |
-
<option value="
|
| 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('
|
| 61 |
</div>
|
| 62 |
</div>
|
|
|
|
| 63 |
<div class="form-row">
|
| 64 |
-
<div class="form-field
|
| 65 |
-
<label for="password">Create Password
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
</small>
|
| 74 |
</div>
|
| 75 |
-
|
| 76 |
-
<div class="form-field
|
| 77 |
-
<label for="confirmPassword">Confirm Password
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
</div>
|
| 84 |
</div>
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
</div>
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
<ng-container *ngIf="!loading; else creatingAccount">
|
| 94 |
-
|
| 95 |
</ng-container>
|
| 96 |
<ng-template #creatingAccount>
|
| 97 |
-
|
| 98 |
</ng-template>
|
| 99 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
<div
|
| 104 |
-
<div
|
| 105 |
-
<div class="g-signin2" data-width="240" data-height="50" data-longtitle="true"></div>
|
| 106 |
-
</div>
|
| 107 |
</div>
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
</
|
| 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">
|
| 121 |
<ul class="role-help-list">
|
| 122 |
-
<li><strong>
|
| 123 |
-
<li><strong>
|
| 124 |
-
<li><strong>
|
| 125 |
-
<li><strong>
|
| 126 |
-
<li><strong>Others:</strong> General or limited access usage.</li>
|
| 127 |
</ul>
|
| 128 |
-
<p class="role-help-tip">
|
| 129 |
</div>
|
| 130 |
</div>
|
| 131 |
|
|
@@ -134,20 +187,16 @@
|
|
| 134 |
<div class="info-popup">
|
| 135 |
<button class="info-close" type="button" (click)="showInfo = false">×</button>
|
| 136 |
<div class="info-title">Role Information</div>
|
|
|
|
| 137 |
<div class="info-text">
|
| 138 |
<ul>
|
| 139 |
-
<li><strong>
|
| 140 |
-
<li><strong>
|
| 141 |
-
<li><strong>
|
| 142 |
-
<li><strong>
|
| 143 |
-
<li><strong>Others:</strong> General or limited access usage.</li>
|
| 144 |
</ul>
|
| 145 |
-
<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 & 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">×</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,
|
| 5 |
import { Router, RouterLink } from '@angular/router';
|
| 6 |
-
import { SignUpService } from './sign-up.service';
|
| 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 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
}
|
| 18 |
|
| 19 |
@Component({
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
})
|
| 37 |
export class SignUpComponent implements OnInit, OnDestroy {
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
}
|
| 258 |
|
|
|
|
| 259 |
function passwordPolicyValidator(control: AbstractControl): ValidationErrors | null {
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 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 =
|
| 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
|
src/assets/background1.jpg
ADDED
|
Git LFS Details
|
src/assets/side-2scale-right.jpg
ADDED
|
Git LFS Details
|
src/assets/side-scale-right.jpg
ADDED
|
Git LFS Details
|
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:
|
| 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 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 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 |
}
|