Swetha commited on
Commit ·
4c34168
1
Parent(s): edd1151
frontend update
Browse files- package-lock.json +0 -0
- package.json +3 -1
- src/app/api.service.ts +3 -2
- src/app/app-routing.module.ts +21 -5
- src/app/app.component.css +331 -0
- src/app/app.component.html +70 -0
- src/app/app.component.ts +117 -68
- src/app/auth/sign-in/sign-in.component.css +1 -0
- src/app/auth/sign-up/sign-up.component.css +1 -0
- src/app/auth/sign-up/sign-up.component.ts +14 -3
- src/app/auth/sign-up/sign-up.service.ts +19 -0
- src/app/intro-page/intro-page.component.css +872 -674
- src/app/intro-page/intro-page.component.html +244 -187
- src/app/intro-page/intro-page.component.ts +145 -19
- src/app/llm-quiz/llm-quiz.component.css +634 -176
- src/app/llm-quiz/llm-quiz.component.html +104 -108
- src/app/llm-quiz/llm-quiz.component.ts +133 -170
- src/app/matching-list/matching-list.component.css +586 -0
- src/app/matching-list/matching-list.component.html +169 -0
- src/app/{quiz/quiz.component.spec.ts → matching-list/matching-list.component.spec.ts} +6 -6
- src/app/matching-list/matching-list.component.ts +481 -0
- src/app/matching.service.spec.ts +59 -0
- src/app/matching.service.ts +65 -0
- src/app/question-answer/question-answer-service.service.ts +18 -12
- src/app/question-answer/question-answer.component.css +602 -226
- src/app/question-answer/question-answer.component.html +161 -53
- src/app/question-answer/question-answer.component.ts +455 -91
- src/app/quiz/quiz.component.css +0 -126
- src/app/quiz/quiz.component.html +0 -35
- src/app/quiz/quiz.component.ts +0 -107
- src/app/quiz/quiz.service.spec.ts +0 -16
- src/app/quiz/quiz.service.ts +0 -36
- src/app/services/llm-qa.service.ts +11 -13
- src/app/user-preferences/user-preferences.component.css +438 -0
- src/app/user-preferences/user-preferences.component.html +135 -0
- src/app/user-preferences/user-preferences.component.ts +280 -0
- src/app/user-preferences/user-preferences.service.ts +63 -0
- src/assets/favicon.ico +0 -0
- src/assets/interview1.png +0 -3
- src/assets/marriage-match/{11.JPG → 11.png} +0 -0
- src/assets/marriage-match/{14.JPG → 14.png} +0 -0
- src/assets/marriage-match/{16.JPG → 16.png} +0 -0
- src/assets/quiz-bg.png +0 -3
- src/assets/signup.png +0 -3
- src/assets/{1.png → wedding.png} +2 -2
- src/index.html +4 -3
- src/main.ts +15 -6
- src/styles.css +18 -62
package-lock.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
CHANGED
|
@@ -10,11 +10,13 @@
|
|
| 10 |
},
|
| 11 |
"private": true,
|
| 12 |
"dependencies": {
|
| 13 |
-
"@angular/animations": "^16.
|
|
|
|
| 14 |
"@angular/common": "^16.1.0",
|
| 15 |
"@angular/compiler": "^16.1.0",
|
| 16 |
"@angular/core": "^16.1.0",
|
| 17 |
"@angular/forms": "^16.1.0",
|
|
|
|
| 18 |
"@angular/platform-browser": "^16.1.0",
|
| 19 |
"@angular/platform-browser-dynamic": "^16.1.0",
|
| 20 |
"@angular/router": "^16.1.0",
|
|
|
|
| 10 |
},
|
| 11 |
"private": true,
|
| 12 |
"dependencies": {
|
| 13 |
+
"@angular/animations": "^16.2.12",
|
| 14 |
+
"@angular/cdk": "^16.2.14",
|
| 15 |
"@angular/common": "^16.1.0",
|
| 16 |
"@angular/compiler": "^16.1.0",
|
| 17 |
"@angular/core": "^16.1.0",
|
| 18 |
"@angular/forms": "^16.1.0",
|
| 19 |
+
"@angular/material": "^16.2.14",
|
| 20 |
"@angular/platform-browser": "^16.1.0",
|
| 21 |
"@angular/platform-browser-dynamic": "^16.1.0",
|
| 22 |
"@angular/router": "^16.1.0",
|
src/app/api.service.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { environment } from '../environments/environment';
|
|
| 5 |
@Injectable({ providedIn: 'root' })
|
| 6 |
export class ApiService {
|
| 7 |
|
|
|
|
| 8 |
// Decide backend base URL at runtime
|
| 9 |
private readonly base = location.hostname.endsWith('hf.space')
|
| 10 |
? 'https://pykara-py-match-backend.hf.space'
|
|
@@ -14,10 +15,10 @@ export class ApiService {
|
|
| 14 |
constructor(private http: HttpClient) { }
|
| 15 |
|
| 16 |
start(body = { n_questions: 10, batch_size: 5, domain: 'marriage' }) {
|
| 17 |
-
return this.http.post<any>(`${this.base}/
|
| 18 |
}
|
| 19 |
|
| 20 |
next(session_id: string, selected_color: 'blue' | 'green' | 'red' | 'yellow') {
|
| 21 |
-
return this.http.post<any>(`${this.base}/
|
| 22 |
}
|
| 23 |
}
|
|
|
|
| 5 |
@Injectable({ providedIn: 'root' })
|
| 6 |
export class ApiService {
|
| 7 |
|
| 8 |
+
|
| 9 |
// Decide backend base URL at runtime
|
| 10 |
private readonly base = location.hostname.endsWith('hf.space')
|
| 11 |
? 'https://pykara-py-match-backend.hf.space'
|
|
|
|
| 15 |
constructor(private http: HttpClient) { }
|
| 16 |
|
| 17 |
start(body = { n_questions: 10, batch_size: 5, domain: 'marriage' }) {
|
| 18 |
+
return this.http.post<any>(`${this.base}/q/start`, body);
|
| 19 |
}
|
| 20 |
|
| 21 |
next(session_id: string, selected_color: 'blue' | 'green' | 'red' | 'yellow') {
|
| 22 |
+
return this.http.post<any>(`${this.base}/q/next`, { session_id, selected_color });
|
| 23 |
}
|
| 24 |
}
|
src/app/app-routing.module.ts
CHANGED
|
@@ -2,9 +2,13 @@ import { NgModule } from '@angular/core';
|
|
| 2 |
import { RouterModule, Routes } from '@angular/router';
|
| 3 |
import { IntroPageComponent } from './intro-page/intro-page.component';
|
| 4 |
import { QuestionAnswerComponent } from './question-answer/question-answer.component';
|
| 5 |
-
import {
|
|
|
|
| 6 |
|
| 7 |
const routes: Routes = [
|
|
|
|
|
|
|
|
|
|
| 8 |
{
|
| 9 |
path: 'auth',
|
| 10 |
children: [
|
|
@@ -21,12 +25,24 @@ const routes: Routes = [
|
|
| 21 |
],
|
| 22 |
},
|
| 23 |
|
| 24 |
-
{ path: '', component: IntroPageComponent, pathMatch: 'full' },
|
| 25 |
-
|
| 26 |
{ path: 'sign-in', redirectTo: 'auth/signin', pathMatch: 'full' },
|
| 27 |
{ path: 'sign-up', redirectTo: 'auth/signup', pathMatch: 'full' },
|
|
|
|
| 28 |
{ path: 'question-answer', component: QuestionAnswerComponent },
|
| 29 |
-
{ path: '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
{ path: '**', redirectTo: '' },
|
| 31 |
];
|
| 32 |
|
|
@@ -34,4 +50,4 @@ const routes: Routes = [
|
|
| 34 |
imports: [RouterModule.forRoot(routes)],
|
| 35 |
exports: [RouterModule],
|
| 36 |
})
|
| 37 |
-
export class AppRoutingModule {
|
|
|
|
| 2 |
import { RouterModule, Routes } from '@angular/router';
|
| 3 |
import { IntroPageComponent } from './intro-page/intro-page.component';
|
| 4 |
import { QuestionAnswerComponent } from './question-answer/question-answer.component';
|
| 5 |
+
import { LlmQuizComponent } from './llm-quiz/llm-quiz.component';
|
| 6 |
+
import { MatchingListComponent } from './matching-list/matching-list.component';
|
| 7 |
|
| 8 |
const routes: Routes = [
|
| 9 |
+
{ path: '', component: IntroPageComponent, pathMatch: 'full' },
|
| 10 |
+
|
| 11 |
+
// Auth lazy standalone components
|
| 12 |
{
|
| 13 |
path: 'auth',
|
| 14 |
children: [
|
|
|
|
| 25 |
],
|
| 26 |
},
|
| 27 |
|
|
|
|
|
|
|
| 28 |
{ path: 'sign-in', redirectTo: 'auth/signin', pathMatch: 'full' },
|
| 29 |
{ path: 'sign-up', redirectTo: 'auth/signup', pathMatch: 'full' },
|
| 30 |
+
|
| 31 |
{ path: 'question-answer', component: QuestionAnswerComponent },
|
| 32 |
+
{ path: 'llmquiz', component: LlmQuizComponent },
|
| 33 |
+
|
| 34 |
+
// Preferences (standalone component lazy-loaded)
|
| 35 |
+
{
|
| 36 |
+
path: 'user-preferences',
|
| 37 |
+
loadComponent: () =>
|
| 38 |
+
import('./user-preferences/user-preferences.component').then(m => m.UserPreferencesComponent),
|
| 39 |
+
},
|
| 40 |
+
|
| 41 |
+
// Matching list routes (must be BEFORE wildcard). Provide optional id.
|
| 42 |
+
{ path: 'matchinglist', component: MatchingListComponent },
|
| 43 |
+
{ path: 'matchinglist/:id', component: MatchingListComponent },
|
| 44 |
+
|
| 45 |
+
// Wildcard LAST
|
| 46 |
{ path: '**', redirectTo: '' },
|
| 47 |
];
|
| 48 |
|
|
|
|
| 50 |
imports: [RouterModule.forRoot(routes)],
|
| 51 |
exports: [RouterModule],
|
| 52 |
})
|
| 53 |
+
export class AppRoutingModule {}
|
src/app/app.component.css
CHANGED
|
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.header-bar {
|
| 2 |
+
background: #000 !important;
|
| 3 |
+
box-shadow: 0 2px 0 rgba(255,255,255,.04);
|
| 4 |
+
display: flex !important;
|
| 5 |
+
align-items: center !important;
|
| 6 |
+
justify-content: flex-start;
|
| 7 |
+
min-height: 82px;
|
| 8 |
+
padding: 14px clamp(1.4rem,4vw,3rem) !important;
|
| 9 |
+
gap: clamp(1.5rem,3vw,4rem);
|
| 10 |
+
position: fixed;
|
| 11 |
+
top: 0;
|
| 12 |
+
z-index: 2002;
|
| 13 |
+
width:100%;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
.header-bar .brand {
|
| 17 |
+
display: inline-flex;
|
| 18 |
+
align-items: center;
|
| 19 |
+
gap: 1.5vw;
|
| 20 |
+
cursor: pointer;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
.header-bar .brand__logo-img {
|
| 24 |
+
background: #fff;
|
| 25 |
+
width: 74px;
|
| 26 |
+
min-width: 74px;
|
| 27 |
+
max-width: 74px;
|
| 28 |
+
padding: 6px;
|
| 29 |
+
border-radius: 50%;
|
| 30 |
+
box-shadow: 0 4px 10px rgba(0,0,0,.25);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.header-bar .brand__name {
|
| 34 |
+
font-family: Roliana;
|
| 35 |
+
font-size: 2.5vw;
|
| 36 |
+
font-weight: 300;
|
| 37 |
+
letter-spacing: 3.2px;
|
| 38 |
+
color: #fff;
|
| 39 |
+
text-shadow: 0 2px 4px rgba(0,0,0,.35);
|
| 40 |
+
display: flex;
|
| 41 |
+
align-items: center;
|
| 42 |
+
gap: 2px;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.header-bar .brand__name strong {
|
| 46 |
+
color: #f3a54c;
|
| 47 |
+
font-weight: 300;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.header__nav {
|
| 51 |
+
flex: 1 1 auto !important;
|
| 52 |
+
display: flex !important;
|
| 53 |
+
align-items: center;
|
| 54 |
+
justify-content: center;
|
| 55 |
+
gap: clamp(2.2rem,3.4vw,4rem) !important;
|
| 56 |
+
margin: 0 !important;
|
| 57 |
+
white-space: nowrap;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
.nav-link--icon {
|
| 61 |
+
display: inline-flex;
|
| 62 |
+
align-items: center;
|
| 63 |
+
gap: 8px;
|
| 64 |
+
font-size: 0.9vw;
|
| 65 |
+
letter-spacing: 1px;
|
| 66 |
+
font-weight: 700;
|
| 67 |
+
color: #fff;
|
| 68 |
+
opacity: .92;
|
| 69 |
+
text-decoration: none;
|
| 70 |
+
padding: 6px 10px;
|
| 71 |
+
position: relative;
|
| 72 |
+
transition: .25s ease;
|
| 73 |
+
height: 46px;
|
| 74 |
+
line-height: 1;
|
| 75 |
+
cursor: pointer;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.nav-link--icon i {
|
| 79 |
+
font-size: .85rem;
|
| 80 |
+
opacity: .95;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
.nav-link--icon:hover {
|
| 84 |
+
color: #f3a54c;
|
| 85 |
+
opacity: 1;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
.nav-link--icon::after {
|
| 89 |
+
content: "";
|
| 90 |
+
position: absolute;
|
| 91 |
+
left: 8px;
|
| 92 |
+
right: 8px;
|
| 93 |
+
bottom: 2px;
|
| 94 |
+
height: 2px;
|
| 95 |
+
background: linear-gradient(90deg,#f3a54c,#ffcb7a);
|
| 96 |
+
transform: scaleX(0);
|
| 97 |
+
transform-origin: left;
|
| 98 |
+
transition: transform .35s ease;
|
| 99 |
+
border-radius: 2px;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
.nav-link--icon:hover::after {
|
| 103 |
+
transform: scaleX(1);
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.header__actions {
|
| 107 |
+
flex: 0 0 auto;
|
| 108 |
+
display: flex;
|
| 109 |
+
align-items: center;
|
| 110 |
+
justify-content: flex-end;
|
| 111 |
+
gap: 1rem;
|
| 112 |
+
margin: 0 !important;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.btn-signup, .btn-signin {
|
| 116 |
+
border-radius: 10px;
|
| 117 |
+
font-size: .8rem;
|
| 118 |
+
padding: .65rem 1.25rem;
|
| 119 |
+
font-weight: 600;
|
| 120 |
+
letter-spacing: .5px;
|
| 121 |
+
line-height: 1;
|
| 122 |
+
cursor: pointer;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
.btn-signup {
|
| 126 |
+
background: #f3a54c;
|
| 127 |
+
color: #0b0f1a;
|
| 128 |
+
border: none;
|
| 129 |
+
box-shadow: 0 4px 10px -2px rgba(243,165,76,.4);
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
.btn-signup:hover {
|
| 133 |
+
filter: brightness(1.08);
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
.btn-signin {
|
| 137 |
+
background: #0b0f1a;
|
| 138 |
+
color: #fff;
|
| 139 |
+
border: 2px solid #f3a54c;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.btn-signin:hover {
|
| 143 |
+
background: #121b27;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
/* Marriage Sub Navigation */
|
| 147 |
+
.nav-item-wrapper {
|
| 148 |
+
position: relative;
|
| 149 |
+
display: inline-block;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
.sub-nav {
|
| 153 |
+
position: absolute;
|
| 154 |
+
top: 100%;
|
| 155 |
+
left: 0;
|
| 156 |
+
background: #0b0f1a;
|
| 157 |
+
border: 1px solid rgba(255,255,255,.12);
|
| 158 |
+
border-radius: 8px;
|
| 159 |
+
padding: 8px 0;
|
| 160 |
+
min-width: 200px;
|
| 161 |
+
box-shadow: 0 8px 24px rgba(0,0,0,.4);
|
| 162 |
+
backdrop-filter: blur(14px);
|
| 163 |
+
z-index: 1000;
|
| 164 |
+
animation: subnavFadeIn 0.2s ease-out;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.sub-nav-link {
|
| 168 |
+
display: flex;
|
| 169 |
+
align-items: center;
|
| 170 |
+
gap: 10px;
|
| 171 |
+
padding: 10px 16px;
|
| 172 |
+
color: #e5e7eb;
|
| 173 |
+
text-decoration: none;
|
| 174 |
+
font-size: 0.75vw;
|
| 175 |
+
font-weight: 600;
|
| 176 |
+
letter-spacing: 0.5px;
|
| 177 |
+
transition: all 0.25s ease;
|
| 178 |
+
border: none;
|
| 179 |
+
background: none;
|
| 180 |
+
width: 100%;
|
| 181 |
+
text-align: left;
|
| 182 |
+
cursor: pointer;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
.sub-nav-link:hover {
|
| 186 |
+
background: rgba(243, 165, 76, 0.1);
|
| 187 |
+
color: #f3a54c;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.sub-nav-link i {
|
| 191 |
+
font-size: 0.85rem;
|
| 192 |
+
width: 16px;
|
| 193 |
+
text-align: center;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
@keyframes subnavFadeIn {
|
| 197 |
+
from {
|
| 198 |
+
opacity: 0;
|
| 199 |
+
transform: translateY(-8px);
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
to {
|
| 203 |
+
opacity: 1;
|
| 204 |
+
transform: translateY(0);
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
/* Ensure the main nav items have proper z-index */
|
| 209 |
+
.header__nav {
|
| 210 |
+
position: relative;
|
| 211 |
+
z-index: 1001;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
@media (max-width:1100px) {
|
| 215 |
+
.header__nav {
|
| 216 |
+
gap: 1.6rem;
|
| 217 |
+
}
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
@media (max-width:880px) {
|
| 221 |
+
.header-bar {
|
| 222 |
+
flex-wrap: wrap;
|
| 223 |
+
min-height: unset;
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
.header__nav {
|
| 227 |
+
order: 3;
|
| 228 |
+
flex: 1 1 100%;
|
| 229 |
+
justify-content: center;
|
| 230 |
+
flex-wrap: wrap;
|
| 231 |
+
gap: .85rem 1.4rem;
|
| 232 |
+
padding-top: .35rem;
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
.header__actions {
|
| 236 |
+
order: 2;
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
@media (max-width:900px) {
|
| 241 |
+
.header-bar .brand__name {
|
| 242 |
+
font-size: 8vw;
|
| 243 |
+
}
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
@media (max-width:640px) {
|
| 247 |
+
.nav-link--icon {
|
| 248 |
+
font-size: .64rem;
|
| 249 |
+
}
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
/* Auth Modal (global) */
|
| 253 |
+
.modal-backdrop {
|
| 254 |
+
position: fixed;
|
| 255 |
+
inset: 0;
|
| 256 |
+
background: rgba(10,14,25,0.72);
|
| 257 |
+
backdrop-filter: blur(6px);
|
| 258 |
+
display: flex;
|
| 259 |
+
align-items: center; /* center vertically */
|
| 260 |
+
justify-content: center; /* center horizontally */
|
| 261 |
+
z-index: 4000; /* above header */
|
| 262 |
+
padding: 2rem 1.2rem;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
.modal-panel {
|
| 266 |
+
/* width: 100%;
|
| 267 |
+
max-width: 540px;
|
| 268 |
+
background: #111a24;
|
| 269 |
+
border: 1px solid rgba(255,255,255,0.08);
|
| 270 |
+
border-radius: 22px;*/
|
| 271 |
+
/* box-shadow: 0 20px 48px -12px rgba(0,0,0,.65), 0 4px 18px -4px rgba(0,0,0,.45);*/
|
| 272 |
+
position: relative;
|
| 273 |
+
padding: 2.25rem 2.4rem 2.4rem;
|
| 274 |
+
color: #fff;
|
| 275 |
+
animation: modalPop .35s cubic-bezier(.4,.8,.2,1);
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
/*.modal-panel--white {*/ /* allow reuse if needed */
|
| 279 |
+
/*background: linear-gradient(145deg,#ffffff,#f7f9fb);
|
| 280 |
+
color: #0b0f1a;
|
| 281 |
+
}*/
|
| 282 |
+
|
| 283 |
+
.modal-close {
|
| 284 |
+
position: absolute;
|
| 285 |
+
top: 10px;
|
| 286 |
+
right: 12px;
|
| 287 |
+
background: none;
|
| 288 |
+
border: none;
|
| 289 |
+
font-size: 1.6rem;
|
| 290 |
+
line-height: 1;
|
| 291 |
+
cursor: pointer;
|
| 292 |
+
color: inherit;
|
| 293 |
+
opacity: .6;
|
| 294 |
+
transition: .25s ease;
|
| 295 |
+
}
|
| 296 |
+
.modal-close:hover { opacity: 1; }
|
| 297 |
+
|
| 298 |
+
@keyframes modalPop {
|
| 299 |
+
0% { opacity: 0; transform: translateY(28px) scale(.94); }
|
| 300 |
+
60% { opacity: 1; transform: translateY(-4px) scale(1.01); }
|
| 301 |
+
100% { opacity: 1; transform: translateY(0) scale(1); }
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
/* Make forms inside auth modal look nice if not already styled */
|
| 305 |
+
.modal-panel form .field label { font-weight: 600; letter-spacing:.4px; }
|
| 306 |
+
.modal-panel form input { width:100%; border:1px solid #d6dde3; border-radius:8px; padding:.7rem .85rem; font-size:.95rem; }
|
| 307 |
+
.modal-panel form input:focus { outline:2px solid #f3a54c; border-color:#f3a54c; }
|
| 308 |
+
.modal-panel h2.title { margin-top:0; font-size:1.65rem; font-weight:700; letter-spacing:.5px; }
|
| 309 |
+
|
| 310 |
+
/* Override dark text if using white variant */
|
| 311 |
+
.modal-panel--white form input { background:#fff; }
|
| 312 |
+
|
| 313 |
+
@media (max-width:640px) {
|
| 314 |
+
.modal-panel { padding:1.75rem 1.4rem 2rem; border-radius:18px; }
|
| 315 |
+
.modal-panel h2.title { font-size:1.4rem; }
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
/* Liked button on matching page */
|
| 319 |
+
.btn-liked {
|
| 320 |
+
display: inline-flex;
|
| 321 |
+
align-items: center;
|
| 322 |
+
gap: .5rem;
|
| 323 |
+
background: rgba(243,165,76,.12);
|
| 324 |
+
color: #f3a54c;
|
| 325 |
+
border: 1px solid rgba(243,165,76,.4);
|
| 326 |
+
padding: .55rem .9rem;
|
| 327 |
+
border-radius: 10px;
|
| 328 |
+
font-weight: 700;
|
| 329 |
+
text-decoration: none;
|
| 330 |
+
}
|
| 331 |
+
.btn-liked:hover { background: rgba(243,165,76,.18); }
|
src/app/app.component.html
CHANGED
|
@@ -1 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
<router-outlet></router-outlet>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- Top Navigation -->
|
| 2 |
+
<header class="topbar header-bar" role="banner" aria-label="Top navigation">
|
| 3 |
+
<a class="brand brand__link" (click)="goHome()" style="cursor: pointer;">
|
| 4 |
+
<img src="assets/pykara-logo.png" alt="Pykara Technologies logo" class="brand__logo-img" />
|
| 5 |
+
<span class="brand__name"><strong>Py</strong>-Match</span>
|
| 6 |
+
</a>
|
| 7 |
+
|
| 8 |
+
<!-- Center Navigation -->
|
| 9 |
+
<nav class="primary-nav header__nav" aria-label="Primary">
|
| 10 |
+
<div class="nav-item-wrapper">
|
| 11 |
+
<a (click)="toggleMarriageSubnav()" class="nav-link nav-link--icon">
|
| 12 |
+
<i class="fa-solid fa-ring"></i>
|
| 13 |
+
<span>MARRIAGE</span>
|
| 14 |
+
</a>
|
| 15 |
+
|
| 16 |
+
<!-- Marriage Sub Navigation -->
|
| 17 |
+
<div class="sub-nav" *ngIf="showMarriageSubnav" (mouseleave)="hideMarriageSubnav()">
|
| 18 |
+
<a (click)="selectRole('marriage'); navigateToProfile()" class="sub-nav-link">
|
| 19 |
+
<i class="fa-solid fa-user"></i>
|
| 20 |
+
<span>Profile</span>
|
| 21 |
+
</a>
|
| 22 |
+
<a (click)="navigateToExpectations()" class="sub-nav-link">
|
| 23 |
+
<i class="fa-solid fa-bullseye"></i>
|
| 24 |
+
<span>Your Expectations</span>
|
| 25 |
+
</a>
|
| 26 |
+
<a (click)="navigateToAssessment()" class="sub-nav-link">
|
| 27 |
+
<i class="fa-solid fa-clipboard-list"></i>
|
| 28 |
+
<span>Take Assessment</span>
|
| 29 |
+
</a>
|
| 30 |
+
<a (click)="navigateToMatchingProfile()" class="sub-nav-link">
|
| 31 |
+
<i class="fa-solid fa-chart-pie"></i>
|
| 32 |
+
<span>Your Matching Profile</span>
|
| 33 |
+
</a>
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
<a (click)="onSelectRole('interview')" class="nav-link nav-link--icon"><i class="fa-solid fa-user-tie"></i><span>INTERVIEW</span></a>
|
| 37 |
+
<a (click)="onSelectRole('partnership')" class="nav-link nav-link--icon"><i class="fa-solid fa-handshake"></i><span>PARTNERSHIP</span></a>
|
| 38 |
+
<a *ngIf="isIntroPage" (click)="scrollToFeatures()" class="nav-link nav-link--icon" aria-label="Jump to features section"><i class="fa-solid fa-grid"></i><span>FEATURES</span></a>
|
| 39 |
+
<a *ngIf="isIntroPage" (click)="scrollToJourney()" class="nav-link nav-link--icon" aria-label="Jump to how it works section"><i class="fa-solid fa-timeline"></i><span>HOW IT WORKS</span></a>
|
| 40 |
+
</nav>
|
| 41 |
+
|
| 42 |
+
<!-- Right Actions -->
|
| 43 |
+
<nav class="actions header__actions" aria-label="User actions">
|
| 44 |
+
<ng-container *ngIf="!isSignedIn; else signedInBlock">
|
| 45 |
+
<button type="button" class="btn btn-signup" (click)="onOpenSignUp()">Sign up</button>
|
| 46 |
+
<button type="button" class="btn btn-signin" (click)="onOpenSignIn()">Sign in</button>
|
| 47 |
+
</ng-container>
|
| 48 |
+
<ng-template #signedInBlock>
|
| 49 |
+
<button type="button" class="profile-btn" aria-label="Profile">
|
| 50 |
+
<i class="fa-solid fa-user" aria-hidden="true"></i>
|
| 51 |
+
</button>
|
| 52 |
+
<button type="button" class="btn btn-signin" (click)="onSignOut()">Sign out</button>
|
| 53 |
+
</ng-template>
|
| 54 |
+
</nav>
|
| 55 |
+
</header>
|
| 56 |
+
|
| 57 |
+
<!-- Main Content -->
|
| 58 |
<router-outlet></router-outlet>
|
| 59 |
+
|
| 60 |
+
<!-- Global Auth Modal -->
|
| 61 |
+
<div *ngIf="authModal" class="modal-backdrop" (click)="closeAuthModal($event)">
|
| 62 |
+
<div class="modal-panel modal-panel--white" role="dialog" aria-modal="true" [attr.aria-label]="authModal === 'signin' ? 'Sign in' : 'Sign up'" (click)="$event.stopPropagation()">
|
| 63 |
+
<button type="button" class="modal-close" (click)="closeAuthModal()" aria-label="Close">×</button>
|
| 64 |
+
<app-sign-in *ngIf="authModal === 'signin'"
|
| 65 |
+
(switchToSignUp)="onOpenSignUp()"
|
| 66 |
+
(signInSuccess)="onSignInSuccess()"></app-sign-in>
|
| 67 |
+
<app-sign-up *ngIf="authModal === 'signup'"
|
| 68 |
+
(switchToSignIn)="onOpenSignIn()"
|
| 69 |
+
(signUpSuccess)="onSignUpSuccess()"></app-sign-up>
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
src/app/app.component.ts
CHANGED
|
@@ -1,79 +1,128 @@
|
|
| 1 |
-
|
| 2 |
-
//import { Component } from '@angular/core';
|
| 3 |
-
//import { FormsModule } from '@angular/forms'; // Import FormsModule
|
| 4 |
-
//import { CommonModule } from '@angular/common'; // Import CommonModule
|
| 5 |
-
|
| 6 |
-
//import { IntroPageComponent } from './intro-page/intro-page.component';
|
| 7 |
-
//import { SignInComponent } from './auth/sign-in/sign-in.component';
|
| 8 |
-
//import { SignUpComponent } from './auth/sign-up/sign-up.component';
|
| 9 |
-
//import { QuestionAnswerComponent } from './question-answer/question-answer.component';
|
| 10 |
-
//import { QuizComponent } from './quiz/quiz.component';
|
| 11 |
-
//import { ApiService } from './api.service';
|
| 12 |
-
//import { RouterOutlet } from '@angular/router';
|
| 13 |
-
//type Color = 'blue' | 'green' | 'red' | 'yellow';
|
| 14 |
-
|
| 15 |
-
//@Component({
|
| 16 |
-
// selector: 'app-root',
|
| 17 |
-
// standalone: true,
|
| 18 |
-
// imports: [RouterOutlet, QuestionAnswerComponent, IntroPageComponent, QuizComponent,
|
| 19 |
-
// SignInComponent,
|
| 20 |
-
// SignUpComponent,
|
| 21 |
-
// FormsModule, // Add FormsModule here
|
| 22 |
-
// CommonModule] , // Add CommonModule here],
|
| 23 |
-
// templateUrl: './app.component.html',
|
| 24 |
-
// styleUrls: ['./app.component.css']
|
| 25 |
-
//})
|
| 26 |
-
//export class AppComponent {
|
| 27 |
-
// title = 'py-match';
|
| 28 |
-
// sessionId: string | null = null;
|
| 29 |
-
// index = 0;
|
| 30 |
-
// total = 0;
|
| 31 |
-
// question = '';
|
| 32 |
-
// options: { text: string, color: Color }[] = [];
|
| 33 |
-
// progress?: Record<Color, number>; // percentages
|
| 34 |
-
|
| 35 |
-
// constructor(private api: ApiService) { }
|
| 36 |
-
|
| 37 |
-
// start() {
|
| 38 |
-
// this.api.start({ n_questions: 10, batch_size: 5, domain: 'marriage' })
|
| 39 |
-
// .subscribe(res => {
|
| 40 |
-
// this.sessionId = res.session_id;
|
| 41 |
-
// this.index = res.index;
|
| 42 |
-
// this.total = res.total;
|
| 43 |
-
// this.question = res.question;
|
| 44 |
-
// this.options = res.options;
|
| 45 |
-
// this.progress = undefined; // first question has no progress yet
|
| 46 |
-
// });
|
| 47 |
-
// }
|
| 48 |
-
|
| 49 |
-
// answer(color: Color) {
|
| 50 |
-
// if (!this.sessionId) { return; }
|
| 51 |
-
// this.api.next(this.sessionId, color).subscribe(res => {
|
| 52 |
-
// if (res.done) {
|
| 53 |
-
// alert('Finished! Check console for final mix.');
|
| 54 |
-
// console.log('Final mix (percent):', res.mix);
|
| 55 |
-
// return;
|
| 56 |
-
// }
|
| 57 |
-
// this.index = res.index;
|
| 58 |
-
// this.total = res.total;
|
| 59 |
-
// this.question = res.question;
|
| 60 |
-
// this.options = res.options;
|
| 61 |
-
// this.progress = res.progress; // already in percentages (from your backend change)
|
| 62 |
-
// });
|
| 63 |
-
// }
|
| 64 |
-
//}
|
| 65 |
-
|
| 66 |
-
|
| 67 |
import { Component } from '@angular/core';
|
| 68 |
-
import { RouterOutlet } from '@angular/router';
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
@Component({
|
| 71 |
selector: 'app-root',
|
| 72 |
standalone: true,
|
| 73 |
-
imports: [RouterOutlet],
|
| 74 |
templateUrl: './app.component.html',
|
| 75 |
styleUrls: ['./app.component.css']
|
| 76 |
})
|
| 77 |
export class AppComponent {
|
| 78 |
title = 'py-match';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import { Component } from '@angular/core';
|
| 2 |
+
import { Router, RouterOutlet } from '@angular/router';
|
| 3 |
+
import { CommonModule } from '@angular/common';
|
| 4 |
+
import { SignupService } from './auth/sign-up/sign-up.service';
|
| 5 |
+
import { SignInComponent } from './auth/sign-in/sign-in.component';
|
| 6 |
+
import { SignUpComponent } from './auth/sign-up/sign-up.component';
|
| 7 |
|
| 8 |
@Component({
|
| 9 |
selector: 'app-root',
|
| 10 |
standalone: true,
|
| 11 |
+
imports: [RouterOutlet, CommonModule, SignInComponent, SignUpComponent],
|
| 12 |
templateUrl: './app.component.html',
|
| 13 |
styleUrls: ['./app.component.css']
|
| 14 |
})
|
| 15 |
export class AppComponent {
|
| 16 |
title = 'py-match';
|
| 17 |
+
showMarriageSubnav = false;
|
| 18 |
+
isSignedIn = false;
|
| 19 |
+
authModal: 'signin' | 'signup' | null = null; // global auth modal state
|
| 20 |
+
|
| 21 |
+
constructor(private router: Router, private signupService: SignupService) {
|
| 22 |
+
// Check if user is signed in
|
| 23 |
+
this.isSignedIn = !!localStorage.getItem('auth_token');
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
// Show intro-only nav items (Features, How it works) only on intro page
|
| 27 |
+
get isIntroPage(): boolean {
|
| 28 |
+
const url = (this.router.url || '').split('?')[0];
|
| 29 |
+
return url === '/' || url.startsWith('/intro');
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
// Show liked button only on matching list page
|
| 33 |
+
get isMatchingPage(): boolean {
|
| 34 |
+
const url = (this.router.url || '').split('?')[0];
|
| 35 |
+
return url.startsWith('/matchinglist');
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
toggleMarriageSubnav(): void {
|
| 39 |
+
this.showMarriageSubnav = !this.showMarriageSubnav;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
hideMarriageSubnav(): void {
|
| 43 |
+
this.showMarriageSubnav = false;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
onSelectRole(role: string): void {
|
| 47 |
+
const userId = localStorage.getItem('user_id');
|
| 48 |
+
if (!userId) {
|
| 49 |
+
alert('Please sign in first.');
|
| 50 |
+
return;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
// Navigate based on role
|
| 54 |
+
if (role === 'marriage') {
|
| 55 |
+
this.router.navigate(['/question-answer'], { queryParams: { role: 'marriage' } });
|
| 56 |
+
} else {
|
| 57 |
+
this.router.navigate(['/question-answer'], { queryParams: { role } });
|
| 58 |
+
}
|
| 59 |
+
this.hideMarriageSubnav();
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
// Open auth modals instead of routing
|
| 63 |
+
onOpenSignIn(): void { this.authModal = 'signin'; }
|
| 64 |
+
onOpenSignUp(): void { this.authModal = 'signup'; }
|
| 65 |
+
closeAuthModal(evt?: MouseEvent): void {
|
| 66 |
+
if (evt && (evt.target as HTMLElement).classList.contains('modal-backdrop')) {
|
| 67 |
+
this.authModal = null;
|
| 68 |
+
} else if (!evt) {
|
| 69 |
+
this.authModal = null;
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
// Sign out
|
| 74 |
+
onSignOut(): void {
|
| 75 |
+
this.isSignedIn = false;
|
| 76 |
+
localStorage.removeItem('auth_token');
|
| 77 |
+
localStorage.removeItem('user_id');
|
| 78 |
+
this.router.navigate(['/']);
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
navigateToProfile(): void {
|
| 82 |
+
const userId = localStorage.getItem('user_id');
|
| 83 |
+
if (!userId) { alert('Please sign in first.'); return; }
|
| 84 |
+
this.router.navigate(['/question-answer'], { queryParams: { role: 'marriage' } });
|
| 85 |
+
this.hideMarriageSubnav();
|
| 86 |
+
}
|
| 87 |
+
navigateToExpectations(): void { this.hideMarriageSubnav(); this.router.navigate(['/userPreference']); }
|
| 88 |
+
navigateToAssessment(): void { this.hideMarriageSubnav(); this.router.navigate(['/llmquiz']); }
|
| 89 |
+
navigateToMatchingProfile(): void { this.hideMarriageSubnav(); this.router.navigate(['/matchinglist']); }
|
| 90 |
+
|
| 91 |
+
navigateToLikedProfiles(): void {
|
| 92 |
+
this.router.navigate(['/matchinglist'], { queryParams: { view: 'liked' } });
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
scrollToFeatures(): void { document.getElementById('features')?.scrollIntoView({ behavior: 'smooth' }); }
|
| 96 |
+
scrollToJourney(): void { document.getElementById('journey')?.scrollIntoView({ behavior: 'smooth' }); }
|
| 97 |
+
goHome(): void { this.router.navigate(['/']); }
|
| 98 |
+
|
| 99 |
+
selectRole(role: string): void {
|
| 100 |
+
const userId = localStorage.getItem('user_id');
|
| 101 |
+
if (!userId) { alert('Please sign in first.'); return; }
|
| 102 |
+
this.signupService.assignRole(+userId, role).subscribe({
|
| 103 |
+
next: (res: any) => {
|
| 104 |
+
console.log('Role saved:', res);
|
| 105 |
+
this.router.navigate(['/question-answer'], { queryParams: { role } });
|
| 106 |
+
},
|
| 107 |
+
error: (err: any) => {
|
| 108 |
+
console.error('Role assign failed:', err);
|
| 109 |
+
alert('Failed to assign role.');
|
| 110 |
+
}
|
| 111 |
+
});
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
// Auth modal child callbacks
|
| 115 |
+
onSignInSuccess(): void {
|
| 116 |
+
this.isSignedIn = true;
|
| 117 |
+
// Placeholder: set token if backend integrates
|
| 118 |
+
if (!localStorage.getItem('auth_token')) {
|
| 119 |
+
localStorage.setItem('auth_token', 'true');
|
| 120 |
+
}
|
| 121 |
+
this.authModal = null;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
onSignUpSuccess(): void {
|
| 125 |
+
// After signup, show sign-in form
|
| 126 |
+
this.authModal = 'signin';
|
| 127 |
+
}
|
| 128 |
}
|
src/app/auth/sign-in/sign-in.component.css
CHANGED
|
@@ -43,6 +43,7 @@
|
|
| 43 |
margin-bottom: 14px;
|
| 44 |
border: 2px solid #b1b1b17d;
|
| 45 |
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
|
|
|
|
| 46 |
}
|
| 47 |
|
| 48 |
.title {
|
|
|
|
| 43 |
margin-bottom: 14px;
|
| 44 |
border: 2px solid #b1b1b17d;
|
| 45 |
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
|
| 46 |
+
border-radius:50%;
|
| 47 |
}
|
| 48 |
|
| 49 |
.title {
|
src/app/auth/sign-up/sign-up.component.css
CHANGED
|
@@ -56,6 +56,7 @@
|
|
| 56 |
margin-bottom: 14px;
|
| 57 |
border: 2px solid #b1b1b17d;
|
| 58 |
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
|
|
|
|
| 59 |
}
|
| 60 |
|
| 61 |
.title {
|
|
|
|
| 56 |
margin-bottom: 14px;
|
| 57 |
border: 2px solid #b1b1b17d;
|
| 58 |
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
|
| 59 |
+
border-radius:50%;
|
| 60 |
}
|
| 61 |
|
| 62 |
.title {
|
src/app/auth/sign-up/sign-up.component.ts
CHANGED
|
@@ -67,7 +67,13 @@ export class SignUpComponent {
|
|
| 67 |
const res = await this.signupService.signup(payload).toPromise();
|
| 68 |
if (res && res.message) {
|
| 69 |
alert('Success: ' + res.message);
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
} else if (res && res.error) {
|
| 72 |
alert('Signup failed: ' + res.error);
|
| 73 |
} else {
|
|
@@ -87,14 +93,19 @@ export class SignUpComponent {
|
|
| 87 |
return error ? !!(c.touched && c.errors?.[error]) : !!(c.touched && c.invalid);
|
| 88 |
}
|
| 89 |
|
| 90 |
-
navigateHome() {
|
|
|
|
|
|
|
| 91 |
|
| 92 |
goToLogin() {
|
| 93 |
this.switchToSignIn.emit();
|
| 94 |
}
|
| 95 |
|
| 96 |
tr(key: string): string {
|
| 97 |
-
const map: Record<string, string> = {
|
|
|
|
|
|
|
|
|
|
| 98 |
return map[key] || '';
|
| 99 |
}
|
| 100 |
}
|
|
|
|
| 67 |
const res = await this.signupService.signup(payload).toPromise();
|
| 68 |
if (res && res.message) {
|
| 69 |
alert('Success: ' + res.message);
|
| 70 |
+
|
| 71 |
+
// Save user_id from backend for role assignment
|
| 72 |
+
if (res.user_id) {
|
| 73 |
+
localStorage.setItem('user_id', res.user_id.toString());
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
this.signUpSuccess.emit(); // Emit success
|
| 77 |
} else if (res && res.error) {
|
| 78 |
alert('Signup failed: ' + res.error);
|
| 79 |
} else {
|
|
|
|
| 93 |
return error ? !!(c.touched && c.errors?.[error]) : !!(c.touched && c.invalid);
|
| 94 |
}
|
| 95 |
|
| 96 |
+
navigateHome() {
|
| 97 |
+
this.router.navigateByUrl('/');
|
| 98 |
+
}
|
| 99 |
|
| 100 |
goToLogin() {
|
| 101 |
this.switchToSignIn.emit();
|
| 102 |
}
|
| 103 |
|
| 104 |
tr(key: string): string {
|
| 105 |
+
const map: Record<string, string> = {
|
| 106 |
+
title: 'Create your account',
|
| 107 |
+
subtitle: 'Join to continue'
|
| 108 |
+
};
|
| 109 |
return map[key] || '';
|
| 110 |
}
|
| 111 |
}
|
src/app/auth/sign-up/sign-up.service.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface SignupPayload {
|
|
| 13 |
export interface SignupResponse {
|
| 14 |
message?: string; // success message from backend
|
| 15 |
error?: string; // error message from backend
|
|
|
|
| 16 |
}
|
| 17 |
|
| 18 |
@Injectable({
|
|
@@ -20,6 +21,7 @@ export interface SignupResponse {
|
|
| 20 |
})
|
| 21 |
export class SignupService {
|
| 22 |
|
|
|
|
| 23 |
// Exactly the same style as your other service:
|
| 24 |
// hf.space => Hugging Face backend; otherwise => local Flask
|
| 25 |
private baseUrl = location.hostname.endsWith('hf.space')
|
|
@@ -36,8 +38,25 @@ export class SignupService {
|
|
| 36 |
catchError(this.handleError)
|
| 37 |
);
|
| 38 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
private handleError(error: HttpErrorResponse) {
|
| 41 |
return throwError(() => new Error(error.message || 'Something went wrong'));
|
| 42 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
}
|
|
|
|
| 13 |
export interface SignupResponse {
|
| 14 |
message?: string; // success message from backend
|
| 15 |
error?: string; // error message from backend
|
| 16 |
+
user_id?: number; // <-- add this line
|
| 17 |
}
|
| 18 |
|
| 19 |
@Injectable({
|
|
|
|
| 21 |
})
|
| 22 |
export class SignupService {
|
| 23 |
|
| 24 |
+
public user_id: number | null = null;
|
| 25 |
// Exactly the same style as your other service:
|
| 26 |
// hf.space => Hugging Face backend; otherwise => local Flask
|
| 27 |
private baseUrl = location.hostname.endsWith('hf.space')
|
|
|
|
| 38 |
catchError(this.handleError)
|
| 39 |
);
|
| 40 |
}
|
| 41 |
+
assignRole(userId: number, roleName: string) {
|
| 42 |
+
return this.http.post(`${this.apiUrl}/questions/select-role`, {
|
| 43 |
+
user_id: userId,
|
| 44 |
+
role_name: roleName,
|
| 45 |
+
assigned_at: new Date().toISOString()
|
| 46 |
+
});
|
| 47 |
+
}
|
| 48 |
|
| 49 |
private handleError(error: HttpErrorResponse) {
|
| 50 |
return throwError(() => new Error(error.message || 'Something went wrong'));
|
| 51 |
}
|
| 52 |
+
|
| 53 |
+
// Method to store user_id after successful signup
|
| 54 |
+
setUserId(userId: number): void {
|
| 55 |
+
this.user_id = userId;
|
| 56 |
+
}
|
| 57 |
+
// Get user_id (to use in other components)
|
| 58 |
+
getUserId(): number | null {
|
| 59 |
+
console.log(this.user_id)
|
| 60 |
+
return this.user_id;
|
| 61 |
+
}
|
| 62 |
}
|
src/app/intro-page/intro-page.component.css
CHANGED
|
@@ -2,904 +2,1102 @@
|
|
| 2 |
display: block;
|
| 3 |
}
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
}
|
| 8 |
-
|
| 9 |
-
.
|
| 10 |
-
position: absolute;
|
| 11 |
width: 1px;
|
| 12 |
height: 1px;
|
| 13 |
-
margin: -1px;
|
| 14 |
padding: 0;
|
|
|
|
| 15 |
overflow: hidden;
|
| 16 |
-
clip: rect(0
|
|
|
|
| 17 |
border: 0;
|
| 18 |
}
|
| 19 |
-
|
| 20 |
-
.
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
top: auto;
|
| 24 |
-
width: 1px;
|
| 25 |
-
height: 1px;
|
| 26 |
-
overflow: hidden;
|
| 27 |
}
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
top: 12px;
|
| 32 |
-
width: auto;
|
| 33 |
-
height: auto;
|
| 34 |
-
padding: 8px 10px;
|
| 35 |
-
background: #fff;
|
| 36 |
-
color: #000;
|
| 37 |
-
border-radius: 6px;
|
| 38 |
-
z-index: 10;
|
| 39 |
-
}
|
| 40 |
-
|
| 41 |
-
.page {
|
| 42 |
height: 100vh;
|
| 43 |
display: flex;
|
| 44 |
-
|
| 45 |
-
background:
|
| 46 |
-
background-size: cover;
|
| 47 |
-
color: #e5e7eb;
|
| 48 |
-
justify-content: space-evenly;
|
| 49 |
-
gap: 12vw;
|
| 50 |
}
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
top: 0;
|
| 56 |
-
left: 0;
|
| 57 |
-
z-index: 100;
|
| 58 |
-
background: #000000d4;
|
| 59 |
-
-webkit-backdrop-filter: blur(8px);
|
| 60 |
-
backdrop-filter: blur(8px);
|
| 61 |
-
padding: 17px 97px;
|
| 62 |
-
justify-content: space-between;
|
| 63 |
-
display: flex;
|
| 64 |
-
width: 100%;
|
| 65 |
align-items: center;
|
|
|
|
| 66 |
}
|
| 67 |
|
| 68 |
-
.
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
gap: 1.5vw;
|
| 72 |
-
text-decoration: none;
|
| 73 |
-
cursor: pointer;
|
| 74 |
-
user-select: none;
|
| 75 |
}
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
transition: transform .25s ease;
|
| 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 |
display: flex;
|
| 114 |
-
|
| 115 |
-
|
| 116 |
}
|
| 117 |
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
border:
|
| 123 |
-
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
}
|
| 126 |
|
| 127 |
-
.
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
align-items: center;
|
| 130 |
justify-content: center;
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
}
|
| 141 |
|
| 142 |
-
.
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
-
.
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
|
|
|
|
|
|
| 151 |
}
|
| 152 |
|
| 153 |
-
.
|
| 154 |
-
background: #
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
}
|
| 156 |
|
| 157 |
-
.
|
| 158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
}
|
| 160 |
|
| 161 |
-
.
|
| 162 |
-
margin
|
| 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 |
display: flex;
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
}
|
| 194 |
|
| 195 |
-
|
| 196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
display: flex;
|
| 198 |
-
|
| 199 |
-
gap:
|
| 200 |
-
margin-left: 5vw;
|
| 201 |
-
font-size: 0.7vw;
|
| 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 |
-
inset: 0;
|
| 249 |
-
background: rgb(255 255 255 / 34%);
|
| 250 |
-
display: grid;
|
| 251 |
-
place-items: center;
|
| 252 |
-
z-index: 1000;
|
| 253 |
-
animation: fadeIn 0.2s ease-out;
|
| 254 |
-
backdrop-filter: blur(8px);
|
| 255 |
}
|
| 256 |
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
|
|
|
|
|
|
| 260 |
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
opacity: 0;
|
| 264 |
}
|
| 265 |
|
| 266 |
-
|
| 267 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
}
|
| 269 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
|
|
|
| 275 |
}
|
| 276 |
|
| 277 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
opacity: 1;
|
| 279 |
-
transform: translateY(0);
|
| 280 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
}
|
| 282 |
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
z-index: 11;
|
| 290 |
-
border-radius: 50%;
|
| 291 |
-
cursor: pointer;
|
| 292 |
-
font-size: 1vw;
|
| 293 |
-
font-weight: bold;
|
| 294 |
}
|
| 295 |
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
}
|
| 302 |
|
| 303 |
-
|
| 304 |
-
.about-backdrop {
|
| 305 |
-
position: fixed;
|
| 306 |
-
inset: 0;
|
| 307 |
-
background: linear-gradient(135deg, rgba(255, 255, 255, 0.4) 0%, rgba(255, 255, 255, 0.2) 100%);
|
| 308 |
-
backdrop-filter: blur(8px);
|
| 309 |
-
-webkit-backdrop-filter: blur(8px);
|
| 310 |
-
z-index: 1100; /* above auth backdrop */
|
| 311 |
-
animation: fadeIn 0.3s ease-out;
|
| 312 |
display: flex;
|
| 313 |
align-items: center;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 314 |
justify-content: center;
|
| 315 |
}
|
| 316 |
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
position:
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
pointer-events: none;
|
| 324 |
-
}
|
| 325 |
-
|
| 326 |
-
/* Add a subtle animation to the backdrop pattern */
|
| 327 |
-
@keyframes subtleShift {
|
| 328 |
-
0%, 100% {
|
| 329 |
-
transform: translate(0, 0);
|
| 330 |
}
|
| 331 |
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
}
|
| 336 |
|
| 337 |
-
.
|
| 338 |
-
|
| 339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
transform: translate(-50%, -50%);
|
| 347 |
-
width: min(720px, 92vw);
|
| 348 |
-
max-height: 80vh;
|
| 349 |
-
overflow: auto;
|
| 350 |
-
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
| 351 |
-
color: #111;
|
| 352 |
-
border-radius: 16px;
|
| 353 |
-
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.1) inset;
|
| 354 |
-
outline: none;
|
| 355 |
-
animation: modalSlideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
| 356 |
}
|
| 357 |
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
}
|
| 363 |
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
}
|
| 368 |
-
}
|
| 369 |
|
| 370 |
-
.
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
align-items: center;
|
| 375 |
-
background: #dedee7;
|
| 376 |
-
color: #0b0f1a;
|
| 377 |
-
border-radius: 16px 16px 0 0;
|
| 378 |
-
}
|
| 379 |
|
| 380 |
-
.
|
| 381 |
-
|
| 382 |
-
font-size: 1.5rem;
|
| 383 |
-
font-weight: 700;
|
| 384 |
}
|
| 385 |
|
| 386 |
-
.
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 391 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 392 |
|
| 393 |
-
.
|
| 394 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 395 |
}
|
| 396 |
|
| 397 |
-
.
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
}
|
| 406 |
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
margin-left: auto;
|
| 410 |
-
border: none;
|
| 411 |
-
color: #000000;
|
| 412 |
-
border: 2px solid #f3a54c;
|
| 413 |
-
width: 28px;
|
| 414 |
-
height: 28px;
|
| 415 |
-
border-radius: 50%;
|
| 416 |
-
font-size: 22px;
|
| 417 |
-
cursor: pointer;
|
| 418 |
-
display: flex;
|
| 419 |
align-items: center;
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
}
|
| 425 |
|
| 426 |
-
.
|
| 427 |
-
|
| 428 |
-
|
| 429 |
}
|
| 430 |
|
| 431 |
-
.
|
| 432 |
-
|
| 433 |
-
|
| 434 |
}
|
| 435 |
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
|
| 441 |
-
.
|
| 442 |
-
|
| 443 |
}
|
| 444 |
|
| 445 |
-
.
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
|
|
|
|
|
|
|
|
|
| 449 |
}
|
| 450 |
|
| 451 |
-
.
|
| 452 |
-
border-
|
| 453 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 454 |
}
|
| 455 |
|
| 456 |
-
.
|
| 457 |
-
|
| 458 |
-
|
|
|
|
|
|
|
| 459 |
}
|
| 460 |
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
.about-backdrop,
|
| 464 |
-
.about-modal {
|
| 465 |
-
animation: none;
|
| 466 |
}
|
| 467 |
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
|
|
|
|
|
|
| 471 |
|
| 472 |
-
.
|
| 473 |
-
|
| 474 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
}
|
| 476 |
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
--t-shadow: rgba(0,0,0,.25);
|
| 486 |
-
--t-glow: rgba(0,255,136,.18); /* brand neon */
|
| 487 |
-
}
|
| 488 |
-
|
| 489 |
-
/* Bubble positioned to the right of trigger */
|
| 490 |
-
.tooltip__bubble {
|
| 491 |
-
transform: translateY(-50%) translateX(-6px) scale(0.98);
|
| 492 |
-
padding: 10px 14px;
|
| 493 |
-
opacity: 0;
|
| 494 |
-
pointer-events: none;
|
| 495 |
-
transition: opacity .18s ease, transform .18s ease;
|
| 496 |
-
transition-delay: .06s;
|
| 497 |
-
z-index: 20;
|
| 498 |
backdrop-filter: blur(6px);
|
| 499 |
-webkit-backdrop-filter: blur(6px);
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
border-radius: 12px;
|
| 507 |
-
font-size: 10px;
|
| 508 |
-
transition: opacity .18s ease, transform .18s ease;
|
| 509 |
-
width: max-content;
|
| 510 |
}
|
| 511 |
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
position: absolute;
|
| 516 |
-
inset: 0;
|
| 517 |
-
border-radius: inherit;
|
| 518 |
-
pointer-events: none;
|
| 519 |
-
background: radial-gradient(120px 40px at 50% -20px, rgba(255,255,255,.35), transparent 70%);
|
| 520 |
-
}
|
| 521 |
-
|
| 522 |
-
/* Arrow on the left side pointing to trigger */
|
| 523 |
-
.tooltip__bubble::after {
|
| 524 |
-
content: "";
|
| 525 |
-
position: absolute;
|
| 526 |
-
top: 50%;
|
| 527 |
-
left: -16px;
|
| 528 |
-
transform: translateY(-50%);
|
| 529 |
-
border: 8px solid transparent;
|
| 530 |
-
border-right-color: var(--t-bg);
|
| 531 |
-
filter: drop-shadow(0 2px 0 rgba(0,0,0,.12));
|
| 532 |
-
}
|
| 533 |
-
|
| 534 |
-
.tooltip.tooltip--active:hover .tooltip__bubble,
|
| 535 |
-
.tooltip.tooltip--active:focus-within .tooltip__bubble,
|
| 536 |
-
.tooltip.tooltip--active .tooltip__bubble:hover,
|
| 537 |
-
.tooltip.tooltip--active .tooltip__bubble:focus {
|
| 538 |
-
opacity: 1;
|
| 539 |
-
transform: translateY(-50%) translateX(0) scale(1);
|
| 540 |
-
pointer-events: auto;
|
| 541 |
-
animation: tooltip-pop-right .22s cubic-bezier(.2,.8,.25,1);
|
| 542 |
-
}
|
| 543 |
-
|
| 544 |
-
@keyframes tooltip-pop-right {
|
| 545 |
-
0% {
|
| 546 |
-
opacity: 0;
|
| 547 |
-
transform: translateY(-50%) translateX(-10px) scale(.96);
|
| 548 |
-
}
|
| 549 |
-
|
| 550 |
-
60% {
|
| 551 |
-
opacity: 1;
|
| 552 |
-
transform: translateY(-50%) translateX(2px) scale(1.02);
|
| 553 |
}
|
| 554 |
|
| 555 |
-
|
| 556 |
-
opacity: 1
|
| 557 |
-
transform: translateY(-50%) translateX(0) scale(1);
|
| 558 |
}
|
| 559 |
}
|
| 560 |
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
|
|
|
|
|
|
| 567 |
}
|
| 568 |
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
|
|
|
| 572 |
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
}
|
| 578 |
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
}
|
| 583 |
}
|
| 584 |
|
| 585 |
-
|
| 586 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 587 |
display: flex;
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
flex-wrap: wrap;
|
| 592 |
}
|
| 593 |
|
| 594 |
-
.
|
| 595 |
-
width: 30%;
|
| 596 |
background: #fff;
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
border-radius: 12px;
|
| 600 |
-
overflow: hidden;
|
| 601 |
-
text-align: left;
|
| 602 |
}
|
| 603 |
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
object-fit: cover;
|
| 608 |
-
}
|
| 609 |
-
|
| 610 |
-
.card h3 {
|
| 611 |
-
margin: 15px;
|
| 612 |
-
font-size: 20px;
|
| 613 |
-
color: #444;
|
| 614 |
-
}
|
| 615 |
-
|
| 616 |
-
.card p {
|
| 617 |
-
margin: 0 15px 20px 15px;
|
| 618 |
-
font-size: 0.89vw;
|
| 619 |
-
line-height: 1.5;
|
| 620 |
-
text-align: justify;
|
| 621 |
-
color: black;
|
| 622 |
-
}
|
| 623 |
-
|
| 624 |
-
/* Scrolling Images */
|
| 625 |
-
.scroll-container {
|
| 626 |
-
overflow: hidden;
|
| 627 |
-
white-space: nowrap;
|
| 628 |
-
padding: 20px 0;
|
| 629 |
-
position: relative;
|
| 630 |
}
|
| 631 |
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
align-items: center;
|
| 636 |
-
animation: scroll-left 40s linear infinite;
|
| 637 |
-
width: max-content;
|
| 638 |
}
|
| 639 |
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
height: 10vw;
|
| 643 |
-
margin: 0 10px;
|
| 644 |
-
border-radius: 8px;
|
| 645 |
-
object-fit: cover;
|
| 646 |
-
flex: 0 0 auto;
|
| 647 |
-
}
|
| 648 |
-
|
| 649 |
-
/* Pause on hover (optional UX improvement) */
|
| 650 |
-
.scroll-container:hover .scroll-images {
|
| 651 |
-
animation-play-state: paused;
|
| 652 |
}
|
| 653 |
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
}
|
| 658 |
-
|
| 659 |
-
100% {
|
| 660 |
-
transform: translateX(-50%);
|
| 661 |
-
}
|
| 662 |
-
/* shift exactly one set width (because duplicated) */
|
| 663 |
}
|
| 664 |
|
| 665 |
-
|
| 666 |
-
.
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
text-align: center;
|
| 670 |
}
|
| 671 |
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
|
|
|
| 675 |
}
|
| 676 |
|
| 677 |
-
.
|
| 678 |
-
|
| 679 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 680 |
}
|
| 681 |
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
color: #fff;
|
| 686 |
-
text-align: center;
|
| 687 |
-
padding: 15px 10px;
|
| 688 |
}
|
| 689 |
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
margin: 0 10px;
|
| 694 |
}
|
|
|
|
| 695 |
|
| 696 |
-
|
| 697 |
-
|
|
|
|
| 698 |
}
|
|
|
|
| 699 |
|
| 700 |
-
footer .social-icons img {
|
| 701 |
-
width: 25px;
|
| 702 |
-
margin: 0 8px;
|
| 703 |
-
vertical-align: middle;
|
| 704 |
-
}
|
| 705 |
-
|
| 706 |
-
/* Responsive adjustments */
|
| 707 |
-
@media (max-width: 768px) {
|
| 708 |
-
.card {
|
| 709 |
-
width: 100%;
|
| 710 |
-
}
|
| 711 |
|
| 712 |
-
.brand__name {
|
| 713 |
-
font-size: 1.5rem;
|
| 714 |
-
}
|
| 715 |
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
}
|
| 720 |
|
| 721 |
-
.cards {
|
| 722 |
-
padding: 30px 5%;
|
| 723 |
-
}
|
| 724 |
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
}
|
| 729 |
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
min-height: 100vh;
|
| 736 |
}
|
| 737 |
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 741 |
}
|
| 742 |
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
|
|
|
| 746 |
}
|
| 747 |
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
|
|
|
|
|
|
| 751 |
}
|
| 752 |
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
padding-bottom: 70px;
|
| 763 |
-
}
|
| 764 |
-
|
| 765 |
-
.cards {
|
| 766 |
-
padding: 70px 5% 30px;
|
| 767 |
-
}
|
| 768 |
|
| 769 |
-
.
|
| 770 |
-
|
|
|
|
| 771 |
}
|
| 772 |
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
}
|
| 777 |
}
|
| 778 |
|
| 779 |
-
|
| 780 |
-
.social-icons {
|
| 781 |
display: flex;
|
| 782 |
-
justify-content:
|
| 783 |
-
|
| 784 |
-
|
| 785 |
}
|
| 786 |
|
| 787 |
-
.
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
text-decoration: none;
|
| 797 |
-
font-size: 18px;
|
| 798 |
-
transition: all 0.3s ease;
|
| 799 |
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 800 |
}
|
| 801 |
|
| 802 |
-
.
|
| 803 |
-
transform: translateY(-
|
| 804 |
-
background: rgba(255, 255, 255, 0.2);
|
| 805 |
}
|
| 806 |
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
}
|
| 812 |
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
}
|
|
|
|
| 817 |
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
|
|
|
| 821 |
}
|
| 822 |
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
}
|
|
|
|
| 827 |
|
| 828 |
-
/*
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 839 |
}
|
| 840 |
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 850 |
text-align: left;
|
|
|
|
| 851 |
}
|
| 852 |
|
| 853 |
-
.
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
| 861 |
text-align: center;
|
| 862 |
}
|
| 863 |
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
| 868 |
}
|
| 869 |
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
|
|
|
| 873 |
}
|
| 874 |
|
| 875 |
-
|
|
|
|
| 876 |
position: relative;
|
| 877 |
-
|
| 878 |
-
letter-spacing: 1.5px;
|
| 879 |
-
text-transform: uppercase;
|
| 880 |
-
font-weight: 600;
|
| 881 |
-
color: #e5e7eb;
|
| 882 |
-
text-decoration: none;
|
| 883 |
-
padding: 6px 4px;
|
| 884 |
-
transition: color .25s ease;
|
| 885 |
}
|
| 886 |
-
|
| 887 |
-
.nav-link::after {
|
| 888 |
-
content: "";
|
| 889 |
-
position: absolute;
|
| 890 |
-
left: 0;
|
| 891 |
-
bottom: 0;
|
| 892 |
-
width: 0;
|
| 893 |
-
height: 2px;
|
| 894 |
-
background: linear-gradient(90deg,#f3a54c,#ffcb7a);
|
| 895 |
-
transition: width .3s ease;
|
| 896 |
-
border-radius: 2px;
|
| 897 |
-
}
|
| 898 |
-
|
| 899 |
-
.nav-link:hover {
|
| 900 |
-
color: #f3a54c;
|
| 901 |
-
}
|
| 902 |
-
|
| 903 |
-
.nav-link:hover::after {
|
| 904 |
-
width: 100%;
|
| 905 |
-
}
|
|
|
|
| 2 |
display: block;
|
| 3 |
}
|
| 4 |
|
| 5 |
+
#journeyTitle {
|
| 6 |
+
color: black !important;
|
| 7 |
}
|
| 8 |
+
/* Utility */
|
| 9 |
+
.visually-hidden {
|
| 10 |
+
position: absolute !important;
|
| 11 |
width: 1px;
|
| 12 |
height: 1px;
|
|
|
|
| 13 |
padding: 0;
|
| 14 |
+
margin: -1px;
|
| 15 |
overflow: hidden;
|
| 16 |
+
clip: rect(0 0 0 0);
|
| 17 |
+
white-space: nowrap;
|
| 18 |
border: 0;
|
| 19 |
}
|
| 20 |
+
/* Layout container */
|
| 21 |
+
.container {
|
| 22 |
+
width: 80%;
|
| 23 |
+
margin: 0 auto;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
}
|
| 25 |
+
/* HERO */
|
| 26 |
+
.hero {
|
| 27 |
+
position: relative;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
height: 100vh;
|
| 29 |
display: flex;
|
| 30 |
+
align-items: center;
|
| 31 |
+
background: radial-gradient(circle at 20% 30%,rgba(243,165,76,.18),transparent 60%),radial-gradient(circle at 75% 55%,rgba(0,255,136,.12),transparent 55%);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
}
|
| 33 |
|
| 34 |
+
.hero__layout {
|
| 35 |
+
display: grid;
|
| 36 |
+
grid-template-columns: repeat(auto-fit,minmax(320px,1fr));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
align-items: center;
|
| 38 |
+
gap: clamp(2.2rem,5vw,5rem);
|
| 39 |
}
|
| 40 |
|
| 41 |
+
.hero__content {
|
| 42 |
+
position: relative;
|
| 43 |
+
z-index: 2;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
}
|
| 45 |
|
| 46 |
+
.hero__title {
|
| 47 |
+
font-size: clamp(2.2rem,3.6vw,3.8rem);
|
| 48 |
+
line-height: 1.1;
|
| 49 |
+
margin: 0 0 1.2rem;
|
| 50 |
+
font-weight: 700;
|
| 51 |
+
background: linear-gradient(90deg,#fff,#ffe2c5 45%,#fff);
|
| 52 |
+
-webkit-background-clip: text;
|
| 53 |
+
color: transparent;
|
| 54 |
+
filter: drop-shadow(0 4px 18px rgba(0,0,0,.35));
|
|
|
|
| 55 |
}
|
| 56 |
|
| 57 |
+
.hero__text {
|
| 58 |
+
font-size: clamp(.95rem,1.15vw,1.2rem);
|
| 59 |
+
max-width: 46ch;
|
| 60 |
+
line-height: 1.55;
|
| 61 |
+
margin: 0 0 1.6rem;
|
| 62 |
+
color: #d8dee7;
|
| 63 |
+
font-weight: 500;
|
| 64 |
}
|
| 65 |
|
| 66 |
+
.hero__badges {
|
| 67 |
+
display: flex;
|
| 68 |
+
flex-wrap: wrap;
|
| 69 |
+
gap: .5rem .75rem;
|
| 70 |
+
margin: 0 0 1.75rem;
|
| 71 |
}
|
| 72 |
|
| 73 |
+
.badge {
|
| 74 |
+
position: relative;
|
| 75 |
+
padding: .4rem .85rem .45rem;
|
| 76 |
+
border-radius: 2rem;
|
| 77 |
+
font-size: .65rem;
|
| 78 |
+
letter-spacing: .8px;
|
| 79 |
+
font-weight: 600;
|
| 80 |
+
text-transform: uppercase;
|
| 81 |
+
background: #f3a54c;
|
| 82 |
+
color: #0b0f1a;
|
| 83 |
+
box-shadow: 0 4px 12px -2px rgba(0,0,0,.4);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.hero__sub-cta {
|
| 87 |
+
margin: 1.25rem 0 0;
|
| 88 |
+
font-size: .8rem;
|
| 89 |
+
letter-spacing: .6px;
|
| 90 |
+
color: #b8c4d2;
|
| 91 |
+
}
|
| 92 |
+
/* Features */
|
| 93 |
+
.features {
|
| 94 |
+
padding: 2vw;
|
| 95 |
+
background: linear-gradient(180deg,#0b0f1a,#111c29);
|
| 96 |
+
position: relative;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.features::before {
|
| 100 |
+
content: "";
|
| 101 |
+
position: absolute;
|
| 102 |
+
inset: 0;
|
| 103 |
+
background: radial-gradient(circle at 70% 30%,rgba(0,255,136,.12),transparent 60%);
|
| 104 |
+
pointer-events: none;
|
| 105 |
+
opacity: .65;
|
| 106 |
}
|
| 107 |
|
| 108 |
+
.section-title {
|
| 109 |
+
font-size: clamp(1.8rem,2.9vw,2.6rem);
|
| 110 |
+
text-align: center;
|
| 111 |
+
font-weight: 700;
|
| 112 |
+
letter-spacing: .5px;
|
| 113 |
+
background: linear-gradient(90deg,#fff,#e6f9ff);
|
| 114 |
+
-webkit-background-clip: text;
|
| 115 |
+
color: transparent;
|
| 116 |
+
line-height: 4vw;
|
| 117 |
}
|
| 118 |
|
| 119 |
+
.features__grid {
|
| 120 |
display: flex;
|
| 121 |
+
gap: 2vw;
|
| 122 |
+
width: 100%;
|
| 123 |
}
|
| 124 |
|
| 125 |
+
.feature {
|
| 126 |
+
background: linear-gradient(135deg,rgba(255,255,255,.07),rgba(255,255,255,.02));
|
| 127 |
+
border: 1px solid rgba(255,255,255,.12);
|
| 128 |
+
padding: 1.2rem 1.15rem 1.25rem;
|
| 129 |
+
border-radius: 1.1rem;
|
| 130 |
+
backdrop-filter: blur(14px);
|
| 131 |
+
position: relative;
|
| 132 |
+
overflow: hidden;
|
| 133 |
+
display: flex;
|
| 134 |
+
flex-direction: column;
|
| 135 |
+
gap: .55rem;
|
| 136 |
+
min-height: 170px;
|
| 137 |
}
|
| 138 |
|
| 139 |
+
.feature::after {
|
| 140 |
+
content: "";
|
| 141 |
+
position: absolute;
|
| 142 |
+
inset: 0;
|
| 143 |
+
background: radial-gradient(circle at 25% 15%,rgba(243,165,76,.15),transparent 70%);
|
| 144 |
+
opacity: 0;
|
| 145 |
+
transition: opacity .4s ease;
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
.feature:hover::after {
|
| 149 |
+
opacity: 1;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
.feature h3 {
|
| 153 |
+
margin: .2rem 0 .15rem;
|
| 154 |
+
font-size: .95rem;
|
| 155 |
+
letter-spacing: .6px;
|
| 156 |
+
font-weight: 600;
|
| 157 |
+
color: #fff;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
.feature p {
|
| 161 |
+
margin: 0;
|
| 162 |
+
font-size: .72rem;
|
| 163 |
+
line-height: 1.35;
|
| 164 |
+
color: #d1d8e3;
|
| 165 |
+
max-width: 36ch;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.feature__icon {
|
| 169 |
+
width: 44px;
|
| 170 |
+
height: 44px;
|
| 171 |
+
display: flex;
|
| 172 |
align-items: center;
|
| 173 |
justify-content: center;
|
| 174 |
+
font-size: 1.1rem;
|
| 175 |
+
border-radius: 14px;
|
| 176 |
+
background: linear-gradient(135deg,#182330,#1f2f40);
|
| 177 |
+
color: #ffe2c5;
|
| 178 |
+
box-shadow: 0 4px 14px -4px rgba(0,0,0,.65),0 0 0 1px rgba(255,255,255,.06);
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
.gradient-ring {
|
| 182 |
+
position: relative;
|
| 183 |
}
|
| 184 |
|
| 185 |
+
.gradient-ring::before {
|
| 186 |
+
content: "";
|
| 187 |
+
position: absolute;
|
| 188 |
+
inset: -2px;
|
| 189 |
+
border-radius: inherit;
|
| 190 |
+
padding: 1px;
|
| 191 |
+
background: linear-gradient(120deg,#f3a54c,#00ff88);
|
| 192 |
+
-webkit-mask: linear-gradient(#000,#000) content-box,linear-gradient(#000,#000);
|
| 193 |
+
-webkit-mask-composite: xor;
|
| 194 |
+
mask-composite: exclude;
|
| 195 |
}
|
| 196 |
+
/* Journey */
|
| 197 |
+
.journey {
|
| 198 |
+
padding-top: 4vw;
|
| 199 |
+
background: #f7f9fc;
|
| 200 |
+
}
|
| 201 |
|
| 202 |
+
.journey__steps {
|
| 203 |
+
list-style: none;
|
| 204 |
+
margin: 0;
|
| 205 |
+
padding: 0;
|
| 206 |
+
display: grid;
|
| 207 |
+
gap: 1.4rem;
|
| 208 |
+
grid-template-columns: repeat(auto-fit,minmax(180px,1fr));
|
| 209 |
}
|
| 210 |
|
| 211 |
+
.step {
|
| 212 |
+
background: #fff;
|
| 213 |
+
border: 1px solid #eceff3;
|
| 214 |
+
padding: 1.15rem 1rem 1.3rem;
|
| 215 |
+
border-radius: 1rem;
|
| 216 |
+
display: flex;
|
| 217 |
+
flex-direction: column;
|
| 218 |
+
gap: .45rem;
|
| 219 |
+
box-shadow: 0 6px 16px -6px rgba(28,40,56,.18);
|
| 220 |
+
transition: transform .35s ease,box-shadow .35s ease;
|
| 221 |
}
|
| 222 |
|
| 223 |
+
.step:hover {
|
| 224 |
+
transform: translateY(-4px);
|
| 225 |
+
box-shadow: 0 10px 28px -10px rgba(28,40,56,.35);
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
.step__num {
|
| 229 |
+
width: 38px;
|
| 230 |
+
height: 38px;
|
| 231 |
+
border-radius: 50%;
|
| 232 |
+
display: flex;
|
| 233 |
+
align-items: center;
|
| 234 |
+
justify-content: center;
|
| 235 |
+
font-weight: 600;
|
| 236 |
+
font-size: .85rem;
|
| 237 |
+
background: linear-gradient(135deg,#f3a54c,#ffcb7a);
|
| 238 |
+
color: #111;
|
| 239 |
+
box-shadow: 0 4px 10px -2px rgba(0,0,0,.25);
|
| 240 |
}
|
| 241 |
|
| 242 |
+
.step h3 {
|
| 243 |
+
margin: 0;
|
| 244 |
+
font-size: .85rem;
|
| 245 |
+
letter-spacing: .5px;
|
| 246 |
+
color: #222;
|
| 247 |
+
font-weight: 600;
|
| 248 |
}
|
| 249 |
|
| 250 |
+
.step p {
|
| 251 |
+
margin: 0;
|
| 252 |
+
font-size: .68rem;
|
| 253 |
+
line-height: 1.35;
|
| 254 |
+
color: #4a5663;
|
| 255 |
+
}
|
| 256 |
+
/* Buttons */
|
| 257 |
+
.btn-secondary {
|
| 258 |
+
background: linear-gradient(135deg,#f3a54c,#ffcb7a);
|
| 259 |
+
color: #111;
|
| 260 |
+
border: none;
|
| 261 |
+
font-weight: 600;
|
| 262 |
}
|
| 263 |
|
| 264 |
+
.btn-secondary.disabled, .btn-secondary[aria-disabled='true'] {
|
| 265 |
+
filter: grayscale(.7) brightness(.6);
|
| 266 |
+
cursor: not-allowed;
|
| 267 |
+
}
|
| 268 |
+
/* Solutions */
|
| 269 |
+
.cards {
|
| 270 |
+
background: #f7f7f7;
|
| 271 |
+
padding: 70px clamp(2.5rem,6vw,5rem);
|
| 272 |
+
display: grid !important;
|
| 273 |
+
grid-template-columns: repeat(auto-fit,minmax(340px,1fr));
|
| 274 |
+
gap: 60px clamp(2rem,3.2vw,3rem);
|
| 275 |
}
|
| 276 |
|
| 277 |
+
.cards > .card {
|
| 278 |
+
background: #fff;
|
| 279 |
+
border: 1px solid #e3e5e8;
|
| 280 |
+
border-radius: 8px;
|
| 281 |
+
padding: 36px 34px 42px;
|
| 282 |
+
display: flex;
|
| 283 |
+
flex-direction: column;
|
| 284 |
+
gap: 1px;
|
| 285 |
+
box-shadow: 0 6px 22px -8px rgba(0,0,0,.12);
|
| 286 |
+
transition: .35s ease;
|
| 287 |
+
position: relative;
|
| 288 |
+
isolation: isolate;
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
.cards > .card::before {
|
| 292 |
+
content: "";
|
| 293 |
+
position: absolute;
|
| 294 |
+
inset: 0;
|
| 295 |
+
border-radius: inherit;
|
| 296 |
+
background: linear-gradient(135deg,rgba(255,255,255,.2),rgba(255,255,255,0));
|
| 297 |
+
opacity: 0;
|
| 298 |
+
transition: opacity .45s ease;
|
| 299 |
+
pointer-events: none;
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
.cards > .card:hover {
|
| 303 |
+
transform: translateY(-6px);
|
| 304 |
+
box-shadow: 0 14px 38px -12px rgba(0,0,0,.22);
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
.cards > .card:hover::before {
|
| 308 |
+
opacity: 1;
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
.cards > .card img {
|
| 312 |
+
width: 100%;
|
| 313 |
+
height: 200px;
|
| 314 |
+
object-fit: cover;
|
| 315 |
+
border-radius: 4px;
|
| 316 |
+
margin: -8px 0 6px;
|
| 317 |
+
box-shadow: 0 4px 10px -4px rgba(0,0,0,.18);
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
.cards > .card h3 {
|
| 321 |
+
margin: 10px 0 6px;
|
| 322 |
+
font-size: 22px;
|
| 323 |
+
font-weight: 700;
|
| 324 |
+
letter-spacing: .4px;
|
| 325 |
+
color: #1d2433;
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
.cards > .card p {
|
| 329 |
+
margin: 0;
|
| 330 |
+
font-size: 14px;
|
| 331 |
+
line-height: 1.55;
|
| 332 |
+
color: #2a313d;
|
| 333 |
+
letter-spacing: .25px;
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
@media (max-width:880px) {
|
| 337 |
+
.cards {
|
| 338 |
+
gap: 40px 28px;
|
| 339 |
+
padding: 60px clamp(1.5rem,5vw,3rem)
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
.cards > .card {
|
| 343 |
+
padding: 30px 26px 36px
|
| 344 |
+
}
|
| 345 |
}
|
| 346 |
|
| 347 |
+
@media (max-width:560px) {
|
| 348 |
+
.cards {
|
| 349 |
+
grid-template-columns: 1fr;
|
| 350 |
+
padding: 50px 1.25rem 60px
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
.cards > .card {
|
| 354 |
+
padding: 26px 22px 32px
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
.cards > .card img {
|
| 358 |
+
height: 180px
|
| 359 |
+
}
|
| 360 |
+
}
|
| 361 |
+
/* Special + bottom CTA */
|
| 362 |
+
.special-content {
|
| 363 |
+
background: #e5e5ea;
|
| 364 |
+
position: relative;
|
| 365 |
+
padding: 110px clamp(2rem,6vw,5rem) 60px;
|
| 366 |
display: flex;
|
| 367 |
+
flex-direction: column;
|
| 368 |
+
align-items: center;
|
| 369 |
+
gap: 70px;
|
| 370 |
}
|
| 371 |
|
| 372 |
+
.special-card {
|
| 373 |
+
max-width: 860px;
|
| 374 |
+
width: 100%;
|
| 375 |
+
border-radius: 26px;
|
| 376 |
+
padding: 54px 56px 60px;
|
| 377 |
+
background: #fff;
|
| 378 |
+
box-shadow: 0 18px 42px -14px rgba(0, 0, 0, .12);
|
| 379 |
display: flex;
|
| 380 |
+
flex-direction: column;
|
| 381 |
+
gap: 2vw;
|
|
|
|
|
|
|
| 382 |
}
|
| 383 |
|
| 384 |
+
.special-card h2 {
|
| 385 |
+
font-size: 2.05rem;
|
| 386 |
+
text-align: center;
|
| 387 |
+
color: #111;
|
| 388 |
+
font-weight: 700;
|
| 389 |
}
|
| 390 |
|
| 391 |
+
.special-card p {
|
| 392 |
+
font-size: 1rem;
|
| 393 |
+
line-height: 1.55;
|
| 394 |
+
color: #111;
|
| 395 |
+
margin: 0;
|
| 396 |
+
}
|
| 397 |
+
.secondaryCard {
|
| 398 |
+
display: flex;
|
| 399 |
+
flex-direction: column;
|
| 400 |
+
gap: 1vw;
|
| 401 |
}
|
| 402 |
|
| 403 |
+
|
| 404 |
+
.secondaryCard button {
|
| 405 |
+
width: max-content;
|
| 406 |
+
color: #f3a54c;
|
| 407 |
+
background: black;
|
| 408 |
+
padding: 0.8vw;
|
| 409 |
+
border-radius: 1vw;
|
| 410 |
+
font-weight: 900;
|
| 411 |
}
|
| 412 |
|
| 413 |
+
|
| 414 |
+
.cta-bottom {
|
| 415 |
+
text-align: center;
|
| 416 |
+
max-width: 760px;
|
| 417 |
+
width: 100%;
|
| 418 |
+
background: #fff;
|
| 419 |
+
padding: 54px 48px 60px;
|
| 420 |
+
border-radius: 26px;
|
| 421 |
+
box-shadow: 0 18px 42px -14px rgba(0,0,0,.12);
|
| 422 |
+
display: flex;
|
| 423 |
+
flex-direction: column;
|
| 424 |
+
gap: 22px;
|
| 425 |
}
|
| 426 |
|
| 427 |
+
.cta-bottom__title {
|
| 428 |
+
margin: 0;
|
| 429 |
+
font-size: 1.9rem;
|
| 430 |
+
font-weight: 700;
|
| 431 |
+
letter-spacing: .5px;
|
| 432 |
+
color: #111;
|
| 433 |
+
}
|
| 434 |
|
| 435 |
+
.cta-bottom__text {
|
| 436 |
+
margin: 0;
|
| 437 |
+
font-size: 1.05rem;
|
| 438 |
+
line-height: 1.55;
|
| 439 |
+
color: #222;
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
.cta-bottom__btn {
|
| 443 |
+
align-self: center;
|
| 444 |
+
margin-top: 6px;
|
| 445 |
+
font-size: .95rem;
|
| 446 |
+
padding: .9rem 2.2rem;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
@media (max-width:860px) {
|
| 450 |
+
.special-card, .cta-bottom {
|
| 451 |
+
padding: 46px 40px 52px
|
| 452 |
}
|
| 453 |
|
| 454 |
+
.special-card h2 {
|
| 455 |
+
font-size: 1.85rem
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
.cta-bottom__title {
|
| 459 |
+
font-size: 1.75rem
|
| 460 |
}
|
| 461 |
|
| 462 |
+
.cta-bottom__text {
|
| 463 |
+
font-size: 1rem
|
| 464 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 465 |
}
|
| 466 |
|
| 467 |
+
@media (max-width:560px) {
|
| 468 |
+
.special-content {
|
| 469 |
+
padding: 80px 1.25rem 50px;
|
| 470 |
+
gap: 50px
|
| 471 |
+
}
|
| 472 |
|
| 473 |
+
.special-card, .cta-bottom {
|
| 474 |
+
padding: 40px 30px 46px
|
|
|
|
| 475 |
}
|
| 476 |
|
| 477 |
+
.special-card h2 {
|
| 478 |
+
font-size: 1.6rem
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
.cta-bottom__title {
|
| 482 |
+
font-size: 1.55rem
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
.cta-bottom__text {
|
| 486 |
+
font-size: .95rem
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
.cta-bottom__btn {
|
| 490 |
+
width: 100%
|
| 491 |
}
|
| 492 |
}
|
| 493 |
+
/* Footer */
|
| 494 |
+
footer {
|
| 495 |
+
position: relative;
|
| 496 |
+
background: #05080d;
|
| 497 |
+
padding: 2.2rem 1rem 2.6rem;
|
| 498 |
+
margin: 0;
|
| 499 |
+
}
|
| 500 |
|
| 501 |
+
footer p {
|
| 502 |
+
font-size: .65rem;
|
| 503 |
+
letter-spacing: .5px;
|
| 504 |
+
margin: 0 0 .9rem;
|
| 505 |
+
color: #9aa6b5;
|
| 506 |
}
|
| 507 |
|
| 508 |
+
.footer-links a {
|
| 509 |
+
font-size: .6rem;
|
| 510 |
+
letter-spacing: .6px;
|
| 511 |
+
color: #d5dee9;
|
| 512 |
+
opacity: .85;
|
| 513 |
+
transition: opacity .3s ease;
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
.footer-links a:hover {
|
| 517 |
opacity: 1;
|
|
|
|
| 518 |
}
|
| 519 |
+
/* Footer redesign */
|
| 520 |
+
.site-footer {
|
| 521 |
+
background: #000;
|
| 522 |
+
padding: 38px clamp(1.5rem,4vw,3.5rem) 54px;
|
| 523 |
+
text-align: center;
|
| 524 |
+
display: flex;
|
| 525 |
+
flex-direction: column;
|
| 526 |
+
align-items: center;
|
| 527 |
+
gap: 26px;
|
| 528 |
}
|
| 529 |
|
| 530 |
+
.site-footer__copy {
|
| 531 |
+
margin: 0;
|
| 532 |
+
font-size: .78rem;
|
| 533 |
+
letter-spacing: .5px;
|
| 534 |
+
color: #e5e5e5;
|
| 535 |
+
font-weight: 500;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 536 |
}
|
| 537 |
|
| 538 |
+
.site-footer__row {
|
| 539 |
+
display: flex;
|
| 540 |
+
flex-direction: column;
|
| 541 |
+
align-items: center;
|
| 542 |
+
gap: 22px;
|
| 543 |
}
|
| 544 |
|
| 545 |
+
.site-footer__links {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 546 |
display: flex;
|
| 547 |
align-items: center;
|
| 548 |
+
gap: 30px;
|
| 549 |
+
font-size: .8rem;
|
| 550 |
+
font-weight: 500;
|
| 551 |
+
flex-wrap: wrap;
|
| 552 |
justify-content: center;
|
| 553 |
}
|
| 554 |
|
| 555 |
+
.site-footer__links a {
|
| 556 |
+
color: #e5e5e5;
|
| 557 |
+
text-decoration: none;
|
| 558 |
+
position: relative;
|
| 559 |
+
letter-spacing: .4px;
|
| 560 |
+
transition: color .25s ease;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 561 |
}
|
| 562 |
|
| 563 |
+
.site-footer__links a:hover {
|
| 564 |
+
color: #f3a54c;
|
| 565 |
+
}
|
|
|
|
| 566 |
|
| 567 |
+
.site-footer__links a + a::before {
|
| 568 |
+
content: "";
|
| 569 |
+
position: absolute;
|
| 570 |
+
left: -15px;
|
| 571 |
+
top: 50%;
|
| 572 |
+
width: 2px;
|
| 573 |
+
height: 16px;
|
| 574 |
+
background: #333;
|
| 575 |
+
transform: translateY(-50%);
|
| 576 |
+
border-radius: 2px;
|
| 577 |
+
}
|
| 578 |
|
| 579 |
+
.site-footer__social {
|
| 580 |
+
display: flex;
|
| 581 |
+
gap: 34px;
|
| 582 |
+
align-items: center;
|
| 583 |
+
justify-content: center;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
}
|
| 585 |
|
| 586 |
+
.site-footer__social a {
|
| 587 |
+
width: 42px;
|
| 588 |
+
height: 42px;
|
| 589 |
+
border-radius: 50%;
|
| 590 |
+
display: flex;
|
| 591 |
+
align-items: center;
|
| 592 |
+
justify-content: center;
|
| 593 |
+
background: #111;
|
| 594 |
+
border: 1px solid #32363d;
|
| 595 |
+
color: #e5e5e5;
|
| 596 |
+
font-size: 16px;
|
| 597 |
+
transition: .3s ease;
|
| 598 |
+
box-shadow: 0 4px 10px -4px rgba(0,0,0,.45);
|
| 599 |
}
|
| 600 |
|
| 601 |
+
.site-footer__social a:hover {
|
| 602 |
+
color: #f3a54c;
|
| 603 |
+
border-color: #f3a54c;
|
| 604 |
+
transform: translateY(-3px);
|
| 605 |
+
}
|
| 606 |
+
|
| 607 |
+
@media (max-width:680px) {
|
| 608 |
+
.site-footer {
|
| 609 |
+
padding: 46px 1.25rem 70px;
|
| 610 |
}
|
|
|
|
| 611 |
|
| 612 |
+
.site-footer__links {
|
| 613 |
+
gap: 20px;
|
| 614 |
+
font-size: .72rem;
|
| 615 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 616 |
|
| 617 |
+
.site-footer__social {
|
| 618 |
+
gap: 20px;
|
|
|
|
|
|
|
| 619 |
}
|
| 620 |
|
| 621 |
+
.site-footer__social a {
|
| 622 |
+
width: 38px;
|
| 623 |
+
height: 38px;
|
| 624 |
+
font-size: 15px;
|
| 625 |
+
}
|
| 626 |
+
}
|
| 627 |
+
/* Background */
|
| 628 |
+
.page {
|
| 629 |
+
display: block;
|
| 630 |
+
min-height: 100vh;
|
| 631 |
+
background: linear-gradient(rgba(5,8,13,.65),rgba(5,8,13,.65)),url('/assets/11.png') center/cover fixed no-repeat;
|
| 632 |
+
}
|
| 633 |
+
/* Header */
|
| 634 |
+
.header-bar {
|
| 635 |
+
background: #000 !important;
|
| 636 |
+
box-shadow: 0 2px 0 rgba(255,255,255,.04);
|
| 637 |
+
display: flex !important;
|
| 638 |
+
align-items: center !important;
|
| 639 |
+
justify-content: flex-start;
|
| 640 |
+
min-height: 82px;
|
| 641 |
+
padding: 14px clamp(1.4rem,4vw,3rem) !important;
|
| 642 |
+
gap: clamp(1.5rem,3vw,4rem);
|
| 643 |
+
position: sticky;
|
| 644 |
+
top: 0;
|
| 645 |
+
z-index: 2002;
|
| 646 |
}
|
| 647 |
+
.header-bar .brand {
|
| 648 |
+
display: inline-flex;
|
| 649 |
+
align-items: center;
|
| 650 |
+
gap: 1.5vw;
|
| 651 |
+
}
|
| 652 |
|
| 653 |
+
.header-bar .brand__logo-img {
|
| 654 |
+
background: #fff;
|
| 655 |
+
width: 74px;
|
| 656 |
+
min-width: 74px;
|
| 657 |
+
max-width: 74px;
|
| 658 |
+
padding: 6px;
|
| 659 |
+
border-radius: 50%;
|
| 660 |
+
box-shadow: 0 4px 10px rgba(0,0,0,.25);
|
| 661 |
}
|
| 662 |
|
| 663 |
+
.header-bar .brand__name {
|
| 664 |
+
font-family: Roliana;
|
| 665 |
+
font-size: 2.5vw;
|
| 666 |
+
font-weight: 300;
|
| 667 |
+
letter-spacing: 3.2px;
|
| 668 |
+
color: #fff;
|
| 669 |
+
text-shadow: 0 2px 4px rgba(0,0,0,.35);
|
| 670 |
+
display: flex;
|
| 671 |
+
align-items: center;
|
| 672 |
+
gap: 2px;
|
| 673 |
+
}
|
| 674 |
+
|
| 675 |
+
.header-bar .brand__name strong {
|
| 676 |
+
color: #f3a54c;
|
| 677 |
+
font-weight: 300;
|
| 678 |
+
}
|
| 679 |
+
|
| 680 |
+
.header__nav {
|
| 681 |
+
flex: 1 1 auto !important;
|
| 682 |
+
display: flex !important;
|
| 683 |
+
align-items: center;
|
| 684 |
+
justify-content: center;
|
| 685 |
+
gap: clamp(2.2rem,3.4vw,4rem) !important;
|
| 686 |
+
margin: 0 !important;
|
| 687 |
+
white-space: nowrap;
|
| 688 |
}
|
| 689 |
|
| 690 |
+
.nav-link--icon {
|
| 691 |
+
display: inline-flex;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 692 |
align-items: center;
|
| 693 |
+
gap: 8px;
|
| 694 |
+
font-size: 0.9vw;
|
| 695 |
+
letter-spacing: 1px;
|
| 696 |
+
font-weight: 700;
|
| 697 |
+
color: #fff;
|
| 698 |
+
opacity: .92;
|
| 699 |
+
text-decoration: none;
|
| 700 |
+
padding: 6px 10px;
|
| 701 |
+
position: relative;
|
| 702 |
+
transition: .25s ease;
|
| 703 |
+
height: 46px;
|
| 704 |
+
line-height: 1;
|
| 705 |
}
|
| 706 |
|
| 707 |
+
.nav-link--icon i {
|
| 708 |
+
font-size: .85rem;
|
| 709 |
+
opacity: .95;
|
| 710 |
}
|
| 711 |
|
| 712 |
+
.nav-link--icon:hover {
|
| 713 |
+
color: #f3a54c;
|
| 714 |
+
opacity: 1;
|
| 715 |
}
|
| 716 |
|
| 717 |
+
.nav-link--icon::after {
|
| 718 |
+
content: "";
|
| 719 |
+
position: absolute;
|
| 720 |
+
left: 8px;
|
| 721 |
+
right: 8px;
|
| 722 |
+
bottom: 2px;
|
| 723 |
+
height: 2px;
|
| 724 |
+
background: linear-gradient(90deg,#f3a54c,#ffcb7a);
|
| 725 |
+
transform: scaleX(0);
|
| 726 |
+
transform-origin: left;
|
| 727 |
+
transition: transform .35s ease;
|
| 728 |
+
border-radius: 2px;
|
| 729 |
+
}
|
| 730 |
|
| 731 |
+
.nav-link--icon:hover::after {
|
| 732 |
+
transform: scaleX(1);
|
| 733 |
}
|
| 734 |
|
| 735 |
+
.header__actions {
|
| 736 |
+
flex: 0 0 auto;
|
| 737 |
+
display: flex;
|
| 738 |
+
align-items: center;
|
| 739 |
+
justify-content: flex-end;
|
| 740 |
+
gap: 1rem;
|
| 741 |
+
margin: 0 !important;
|
| 742 |
}
|
| 743 |
|
| 744 |
+
.btn-signup, .btn-signin {
|
| 745 |
+
border-radius: 10px;
|
| 746 |
+
font-size: .8rem;
|
| 747 |
+
padding: .65rem 1.25rem;
|
| 748 |
+
font-weight: 600;
|
| 749 |
+
letter-spacing: .5px;
|
| 750 |
+
line-height: 1;
|
| 751 |
}
|
| 752 |
|
| 753 |
+
.btn-signup {
|
| 754 |
+
background: #f3a54c;
|
| 755 |
+
color: #0b0f1a;
|
| 756 |
+
border: none;
|
| 757 |
+
box-shadow: 0 4px 10px -2px rgba(243,165,76,.4);
|
| 758 |
}
|
| 759 |
|
| 760 |
+
.btn-signup:hover {
|
| 761 |
+
filter: brightness(1.08);
|
|
|
|
|
|
|
|
|
|
| 762 |
}
|
| 763 |
|
| 764 |
+
.btn-signin {
|
| 765 |
+
background: #0b0f1a;
|
| 766 |
+
color: #fff;
|
| 767 |
+
border: 2px solid #f3a54c;
|
| 768 |
+
}
|
| 769 |
|
| 770 |
+
.btn-signin:hover {
|
| 771 |
+
background: #121b27;
|
| 772 |
}
|
| 773 |
+
/* Tagline line break styling */
|
| 774 |
+
.tagline-line2 {
|
| 775 |
+
display: inline-block;
|
| 776 |
+
margin-top: .35rem;
|
| 777 |
+
font-weight: 700;
|
| 778 |
+
background: linear-gradient(90deg,#ffe2c5,#ffffff);
|
| 779 |
+
-webkit-background-clip: text;
|
| 780 |
+
color: transparent;
|
| 781 |
}
|
| 782 |
|
| 783 |
+
.hero__title br {
|
| 784 |
+
content: "";
|
| 785 |
+
}
|
| 786 |
+
/* Auth Modal */
|
| 787 |
+
.modal-backdrop {
|
| 788 |
+
position: fixed;
|
| 789 |
+
inset: 0;
|
| 790 |
+
background: rgba(0,0,0,.55);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 791 |
backdrop-filter: blur(6px);
|
| 792 |
-webkit-backdrop-filter: blur(6px);
|
| 793 |
+
display: flex;
|
| 794 |
+
align-items: center;
|
| 795 |
+
justify-content: center;
|
| 796 |
+
padding: 24px clamp(12px,4vw,40px);
|
| 797 |
+
z-index: 2000;
|
| 798 |
+
animation: modalFade .25s ease;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 799 |
}
|
| 800 |
|
| 801 |
+
@keyframes modalFade {
|
| 802 |
+
from {
|
| 803 |
+
opacity: 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 804 |
}
|
| 805 |
|
| 806 |
+
to {
|
| 807 |
+
opacity: 1
|
|
|
|
| 808 |
}
|
| 809 |
}
|
| 810 |
|
| 811 |
+
.modal-panel {
|
| 812 |
+
position: relative;
|
| 813 |
+
width: 56%;
|
| 814 |
+
border-radius: 22px;
|
| 815 |
+
background: #0f1522;
|
| 816 |
+
color: #e5e7eb;
|
| 817 |
+
padding: 52px 44px 48px;
|
| 818 |
+
animation: panelIn .35s cubic-bezier(.16,1,.3,1);
|
| 819 |
}
|
| 820 |
|
| 821 |
+
.modal-panel--white {
|
| 822 |
+
background: transparent;
|
| 823 |
+
color: #111;
|
| 824 |
+
}
|
| 825 |
|
| 826 |
+
@keyframes panelIn {
|
| 827 |
+
from {
|
| 828 |
+
transform: translateY(22px) scale(.95);
|
| 829 |
+
opacity: 0
|
| 830 |
}
|
| 831 |
|
| 832 |
+
to {
|
| 833 |
+
transform: translateY(0) scale(1);
|
| 834 |
+
opacity: 1
|
| 835 |
}
|
| 836 |
}
|
| 837 |
|
| 838 |
+
button.modal-close {
|
| 839 |
+
position: absolute;
|
| 840 |
+
top: 14px;
|
| 841 |
+
right: 14px;
|
| 842 |
+
width: 38px;
|
| 843 |
+
height: 38px;
|
| 844 |
+
border-radius: 50%;
|
| 845 |
+
background: #0b0f1a;
|
| 846 |
+
color: #fff;
|
| 847 |
+
border: 2px solid #f3a54c;
|
| 848 |
+
font-size: 20px;
|
| 849 |
+
font-weight: 600;
|
| 850 |
+
cursor: pointer;
|
| 851 |
display: flex;
|
| 852 |
+
align-items: center;
|
| 853 |
+
justify-content: center;
|
| 854 |
+
transition: .25s ease;
|
|
|
|
| 855 |
}
|
| 856 |
|
| 857 |
+
.modal-panel--white button.modal-close {
|
|
|
|
| 858 |
background: #fff;
|
| 859 |
+
color: #111;
|
| 860 |
+
border: 2px solid #f3a54c;
|
|
|
|
|
|
|
|
|
|
| 861 |
}
|
| 862 |
|
| 863 |
+
button.modal-close:hover {
|
| 864 |
+
border-color: #00ff88;
|
| 865 |
+
transform: scale(.92);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 866 |
}
|
| 867 |
|
| 868 |
+
button.modal-close:focus-visible {
|
| 869 |
+
outline: 3px solid #00ff88;
|
| 870 |
+
outline-offset: 2px;
|
|
|
|
|
|
|
|
|
|
| 871 |
}
|
| 872 |
|
| 873 |
+
.modal-panel app-sign-in, .modal-panel app-sign-up {
|
| 874 |
+
display: block;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 875 |
}
|
| 876 |
|
| 877 |
+
.page, .hero, .features, .journey, .cards, .special-content {
|
| 878 |
+
position: relative;
|
| 879 |
+
z-index: 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 880 |
}
|
| 881 |
|
| 882 |
+
@media (max-width:1100px) {
|
| 883 |
+
.header__nav {
|
| 884 |
+
gap: 1.6rem;
|
| 885 |
+
}
|
|
|
|
| 886 |
}
|
| 887 |
|
| 888 |
+
@media (max-width:880px) {
|
| 889 |
+
.header-bar {
|
| 890 |
+
flex-wrap: wrap;
|
| 891 |
+
min-height: unset;
|
| 892 |
}
|
| 893 |
|
| 894 |
+
.header__nav {
|
| 895 |
+
order: 3;
|
| 896 |
+
flex: 1 1 100%;
|
| 897 |
+
justify-content: center;
|
| 898 |
+
flex-wrap: wrap;
|
| 899 |
+
gap: .85rem 1.4rem;
|
| 900 |
+
padding-top: .35rem;
|
| 901 |
}
|
| 902 |
|
| 903 |
+
.header__actions {
|
| 904 |
+
order: 2;
|
| 905 |
+
}
|
|
|
|
|
|
|
|
|
|
| 906 |
}
|
| 907 |
|
| 908 |
+
@media (max-width:900px) {
|
| 909 |
+
.header-bar .brand__name {
|
| 910 |
+
font-size: 8vw;
|
|
|
|
| 911 |
}
|
| 912 |
+
}
|
| 913 |
|
| 914 |
+
@media (max-width:640px) {
|
| 915 |
+
.nav-link--icon {
|
| 916 |
+
font-size: .64rem;
|
| 917 |
}
|
| 918 |
+
}
|
| 919 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 920 |
|
|
|
|
|
|
|
|
|
|
| 921 |
|
| 922 |
+
a.brand.brand__link {
|
| 923 |
+
text-decoration: unset;
|
| 924 |
+
}
|
|
|
|
| 925 |
|
|
|
|
|
|
|
|
|
|
| 926 |
|
| 927 |
+
.cta {
|
| 928 |
+
display: flex;
|
| 929 |
+
gap: 1vw;
|
| 930 |
}
|
| 931 |
|
| 932 |
+
.about-backdrop {
|
| 933 |
+
position: fixed;
|
| 934 |
+
inset: 0;
|
| 935 |
+
background: rgba(0, 0, 0, 0.7);
|
| 936 |
+
z-index: 1000;
|
|
|
|
| 937 |
}
|
| 938 |
|
| 939 |
+
.about-modal {
|
| 940 |
+
position: fixed;
|
| 941 |
+
top: 50%;
|
| 942 |
+
left: 50%;
|
| 943 |
+
transform: translate(-50%, -50%);
|
| 944 |
+
background: #ffffff;
|
| 945 |
+
border-radius: 12px;
|
| 946 |
+
padding: 2.5rem;
|
| 947 |
+
width: 45%;
|
| 948 |
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
| 949 |
+
z-index: 1001;
|
| 950 |
+
display: flex;
|
| 951 |
+
flex-direction: column;
|
| 952 |
+
gap: 1.5rem;
|
| 953 |
+
animation: _ngcontent-ng-c221623785_modalFadeIn 0.4s ease-out;
|
| 954 |
+
text-align: justify;
|
| 955 |
+
margin: 0 auto;
|
| 956 |
}
|
| 957 |
|
| 958 |
+
.about-modal__header {
|
| 959 |
+
display: flex;
|
| 960 |
+
justify-content: space-between;
|
| 961 |
+
align-items: center;
|
| 962 |
}
|
| 963 |
|
| 964 |
+
.about-modal__title {
|
| 965 |
+
font-size: 1.5rem;
|
| 966 |
+
font-weight: 700;
|
| 967 |
+
margin: 0;
|
| 968 |
+
color: #f3a54c;
|
| 969 |
}
|
| 970 |
|
| 971 |
+
.about-modal__close {
|
| 972 |
+
background: transparent;
|
| 973 |
+
border: 2px solid black;
|
| 974 |
+
color: #f3a54c;
|
| 975 |
+
font-size: 1.2rem;
|
| 976 |
+
cursor: pointer;
|
| 977 |
+
transition: color 0.3s ease;
|
| 978 |
+
border-radius: 0.3vw;
|
| 979 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 980 |
|
| 981 |
+
.about-modal__close:hover {
|
| 982 |
+
background: #f3a54c;
|
| 983 |
+
color: black;
|
| 984 |
}
|
| 985 |
|
| 986 |
+
.about-modal__content {
|
| 987 |
+
color: #e5e7eb;
|
| 988 |
+
line-height: 1.6;
|
|
|
|
| 989 |
}
|
| 990 |
|
| 991 |
+
.about-modal__footer {
|
|
|
|
| 992 |
display: flex;
|
| 993 |
+
justify-content: flex-end;
|
| 994 |
+
align-items: center;
|
| 995 |
+
gap: 1rem;
|
| 996 |
}
|
| 997 |
|
| 998 |
+
.about-modal__btn {
|
| 999 |
+
background: linear-gradient(135deg, #f3a54c, #ffcb7a);
|
| 1000 |
+
color: #111;
|
| 1001 |
+
border: none;
|
| 1002 |
+
padding: 0.8rem 1.5rem;
|
| 1003 |
+
border-radius: 8px;
|
| 1004 |
+
font-weight: 600;
|
| 1005 |
+
cursor: pointer;
|
| 1006 |
+
transition: transform 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1007 |
}
|
| 1008 |
|
| 1009 |
+
.about-modal__btn:hover {
|
| 1010 |
+
transform: translateY(-2px);
|
|
|
|
| 1011 |
}
|
| 1012 |
|
| 1013 |
+
@keyframes modalFadeIn {
|
| 1014 |
+
from {
|
| 1015 |
+
opacity: 0;
|
| 1016 |
+
transform: translate(-50%, -40%);
|
| 1017 |
}
|
| 1018 |
|
| 1019 |
+
to {
|
| 1020 |
+
opacity: 1;
|
| 1021 |
+
transform: translate(-50%, -50%);
|
| 1022 |
}
|
| 1023 |
+
}
|
| 1024 |
|
| 1025 |
+
@keyframes modalFadeOut {
|
| 1026 |
+
from {
|
| 1027 |
+
opacity: 1;
|
| 1028 |
+
transform: translate(-50%, -50%);
|
| 1029 |
}
|
| 1030 |
|
| 1031 |
+
to {
|
| 1032 |
+
opacity: 0;
|
| 1033 |
+
transform: translate(-50%, -60%);
|
| 1034 |
}
|
| 1035 |
+
}
|
| 1036 |
|
| 1037 |
+
/* Marriage Sub Navigation */
|
| 1038 |
+
.nav-item-wrapper {
|
| 1039 |
+
position: relative;
|
| 1040 |
+
display: inline-block;
|
| 1041 |
+
}
|
| 1042 |
|
| 1043 |
+
.sub-nav {
|
| 1044 |
+
position: absolute;
|
| 1045 |
+
top: 100%;
|
| 1046 |
+
left: 0;
|
| 1047 |
+
background: #0b0f1a;
|
| 1048 |
+
border: 1px solid rgba(255,255,255,.12);
|
| 1049 |
+
border-radius: 8px;
|
| 1050 |
+
padding: 8px 0;
|
| 1051 |
+
min-width: 200px;
|
| 1052 |
+
box-shadow: 0 8px 24px rgba(0,0,0,.4);
|
| 1053 |
+
backdrop-filter: blur(14px);
|
| 1054 |
+
z-index: 1000;
|
| 1055 |
+
animation: subnavFadeIn 0.2s ease-out;
|
| 1056 |
}
|
| 1057 |
|
| 1058 |
+
.sub-nav-link {
|
| 1059 |
+
display: flex;
|
| 1060 |
+
align-items: center;
|
| 1061 |
+
gap: 10px;
|
| 1062 |
+
padding: 10px 16px;
|
| 1063 |
+
color: #e5e7eb;
|
| 1064 |
+
text-decoration: none;
|
| 1065 |
+
font-size: 0.75vw;
|
| 1066 |
+
font-weight: 600;
|
| 1067 |
+
letter-spacing: 0.5px;
|
| 1068 |
+
transition: all 0.25s ease;
|
| 1069 |
+
border: none;
|
| 1070 |
+
background: none;
|
| 1071 |
+
width: 100%;
|
| 1072 |
text-align: left;
|
| 1073 |
+
cursor: pointer;
|
| 1074 |
}
|
| 1075 |
|
| 1076 |
+
.sub-nav-link:hover {
|
| 1077 |
+
background: rgba(243, 165, 76, 0.1);
|
| 1078 |
+
color: #f3a54c;
|
| 1079 |
+
}
|
| 1080 |
+
|
| 1081 |
+
.sub-nav-link i {
|
| 1082 |
+
font-size: 0.85rem;
|
| 1083 |
+
width: 16px;
|
| 1084 |
text-align: center;
|
| 1085 |
}
|
| 1086 |
|
| 1087 |
+
@keyframes subnavFadeIn {
|
| 1088 |
+
from {
|
| 1089 |
+
opacity: 0;
|
| 1090 |
+
transform: translateY(-8px);
|
| 1091 |
}
|
| 1092 |
|
| 1093 |
+
to {
|
| 1094 |
+
opacity: 1;
|
| 1095 |
+
transform: translateY(0);
|
| 1096 |
+
}
|
| 1097 |
}
|
| 1098 |
|
| 1099 |
+
/* Ensure the main nav items have proper z-index */
|
| 1100 |
+
.header__nav {
|
| 1101 |
position: relative;
|
| 1102 |
+
z-index: 1001;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1103 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/app/intro-page/intro-page.component.html
CHANGED
|
@@ -1,67 +1,189 @@
|
|
| 1 |
<a class="skip-link" href="#main">Skip to content</a>
|
| 2 |
|
| 3 |
<section class="page">
|
| 4 |
-
<!--
|
| 5 |
-
<
|
| 6 |
-
<a
|
| 7 |
-
|
| 8 |
-
target="_blank"
|
| 9 |
-
rel="noopener noreferrer"
|
| 10 |
-
aria-label="Visit Pykara website">
|
| 11 |
-
<img src="assets/pykara-logo.png" alt="Pykara Technologies logo" class="brand__logo-img" />
|
| 12 |
-
<span class="brand__name"><strong>Py</strong>-Match</span>
|
| 13 |
</a>
|
|
|
|
| 14 |
|
| 15 |
-
|
| 16 |
-
<nav class="primary-nav" aria-label="Assessment navigation">
|
| 17 |
-
<a routerLink="/question-answer" [queryParams]="{ role: 'marriage' }" class="nav-link">
|
| 18 |
-
<i class="fa-solid fa-ring nav-link__icon" aria-hidden="true"></i>
|
| 19 |
-
<span>Marriage</span>
|
| 20 |
-
</a>
|
| 21 |
-
<a routerLink="/question-answer" [queryParams]="{ role: 'interview' }" class="nav-link">
|
| 22 |
-
<i class="fa-solid fa-user-tie nav-link__icon" aria-hidden="true"></i>
|
| 23 |
-
<span>Interview</span>
|
| 24 |
-
</a>
|
| 25 |
-
<a routerLink="/question-answer" [queryParams]="{ role: 'partnership' }" class="nav-link">
|
| 26 |
-
<i class="fa-solid fa-handshake nav-link__icon" aria-hidden="true"></i>
|
| 27 |
-
<span>Partnership</span>
|
| 28 |
-
</a>
|
| 29 |
-
</nav>
|
| 30 |
-
|
| 31 |
-
<nav class="actions" aria-label="Actions">
|
| 32 |
-
<ng-container *ngIf="!isSignedIn; else signedInBlock">
|
| 33 |
-
<button type="button" class="btn btn-primary" (click)="openSignUp()">{{ tr('signup') }}</button>
|
| 34 |
-
<button type="button" class="btn btn-ghost" (click)="openSignIn()">{{ tr('signin') }}</button>
|
| 35 |
-
</ng-container>
|
| 36 |
-
<ng-template #signedInBlock>
|
| 37 |
-
<button type="button" class="profile-btn" aria-label="Profile">
|
| 38 |
-
<i class="fa-solid fa-user" aria-hidden="true"></i>
|
| 39 |
-
</button>
|
| 40 |
-
<button type="button" class="btn btn-ghost" (click)="signOut()">Sign out</button>
|
| 41 |
-
</ng-template>
|
| 42 |
-
</nav>
|
| 43 |
-
</header>
|
| 44 |
-
|
| 45 |
-
<!-- Hero (unchanged) -->
|
| 46 |
<main id="main" class="hero" role="main">
|
| 47 |
-
<div class="
|
| 48 |
-
|
| 49 |
-
<
|
| 50 |
-
|
| 51 |
-
<
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
</div>
|
| 60 |
</div>
|
| 61 |
</main>
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
<div class="modal-backdrop" *ngIf="modal" (click)="backdropClick($event)">
|
| 66 |
<div class="modal-panel modal-panel--white"
|
| 67 |
role="dialog"
|
|
@@ -74,13 +196,12 @@
|
|
| 74 |
(signInSuccess)="handleSignInSuccess()"></app-sign-in>
|
| 75 |
<app-sign-up *ngIf="modal === 'signup'"
|
| 76 |
(switchToSignIn)="openSignIn()"
|
| 77 |
-
(signUpSuccess)="handleSignUpSuccess()"></app-sign-up>
|
| 78 |
</div>
|
| 79 |
</div>
|
| 80 |
|
| 81 |
-
<!-- ABOUT MODAL
|
| 82 |
<div class="about-backdrop" *ngIf="isAboutOpen" (click)="closeAbout()" tabindex="-1"></div>
|
| 83 |
-
|
| 84 |
<div class="about-modal"
|
| 85 |
*ngIf="isAboutOpen"
|
| 86 |
role="dialog"
|
|
@@ -89,147 +210,83 @@
|
|
| 89 |
id="aboutDialog"
|
| 90 |
tabindex="-1">
|
| 91 |
<div class="about-modal__header">
|
| 92 |
-
<h2 id="aboutTitle">About Py-Match</h2>
|
| 93 |
-
<button class="about-
|
| 94 |
</div>
|
| 95 |
-
|
| 96 |
<div class="about-modal__body">
|
| 97 |
-
<
|
| 98 |
<p><strong>Py-Match</strong> is an advanced personality assessment tool designed to help individuals find the ideal match based on their core personality traits. The system uses a series of adaptive questions that are tailored to each user's profile. Each response is mapped to one of four core personality profiles, which represent different dominant behavioral tendencies. As users progress through the assessment, the platform continuously adapts, ensuring that the questions remain relevant and engaging, ultimately providing a more accurate personality profile.</p>
|
| 99 |
-
|
| 100 |
<p>The primary goal of <strong>Py-Match</strong> is to offer data-driven, personalized matches for various scenarios such as marriage compatibility, business partnerships, and leadership fit. By comparing the individual's personality profile to role-specific templates, the platform ensures that the match is based on both complementary and compatible traits. This sophisticated matching engine provides an in-depth, personalized experience while maintaining strict privacy standards. With a focus on both accuracy and user confidentiality, <strong>Py-Match</strong> guarantees that sensitive information is securely stored, offering users peace of mind while they explore the platform's potential.</p>
|
| 101 |
</div>
|
|
|
|
|
|
|
|
|
|
| 102 |
</div>
|
| 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 |
-
<h3><b>Business Partner Match</b></h3>
|
| 132 |
-
<p>
|
| 133 |
-
Choose partners who complement your strengths.<br>
|
| 134 |
-
Identify complementary skills and leadership styles.<br>
|
| 135 |
-
Reduce risks of conflict in decision-making.<br>
|
| 136 |
-
Build stronger, trust-based collaborations.
|
| 137 |
-
</p>
|
| 138 |
</div>
|
| 139 |
-
</section>
|
| 140 |
|
| 141 |
-
<!--
|
| 142 |
-
<div class="
|
| 143 |
-
<div class="
|
| 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 |
-
<!-- Sequence B (duplicate for seamless loop) -->
|
| 171 |
-
<img src="assets/small-img/1.png" alt="smallImage">
|
| 172 |
-
<img src="assets/small-img/2.png" alt="smallImage">
|
| 173 |
-
<img src="assets/small-img/3.png" alt="smallImage">
|
| 174 |
-
<img src="assets/small-img/4.png" alt="smallImage">
|
| 175 |
-
<img src="assets/small-img/5.png" alt="smallImage">
|
| 176 |
-
<img src="assets/small-img/6.png" alt="smallImage">
|
| 177 |
-
<img src="assets/small-img/7.png" alt="smallImage">
|
| 178 |
-
<img src="assets/small-img/8.png" alt="smallImage">
|
| 179 |
-
<img src="assets/small-img/9.png" alt="smallImage">
|
| 180 |
-
<img src="assets/small-img/10.png" alt="smallImage">
|
| 181 |
-
<img src="assets/small-img/11.png" alt="smallImage">
|
| 182 |
-
<img src="assets/small-img/12.png" alt="smallImage">
|
| 183 |
-
<img src="assets/small-img/13.png" alt="smallImage">
|
| 184 |
-
<img src="assets/small-img/14.png" alt="smallImage">
|
| 185 |
-
<img src="assets/small-img/15.png" alt="smallImage">
|
| 186 |
-
<img src="assets/small-img/16.png" alt="smallImage">
|
| 187 |
-
<img src="assets/small-img/17.png" alt="smallImage">
|
| 188 |
-
<img src="assets/small-img/18.png" alt="smallImage">
|
| 189 |
-
<img src="assets/small-img/19.png" alt="smallImage">
|
| 190 |
-
<img src="assets/small-img/20.png" alt="smallImage">
|
| 191 |
-
<img src="assets/small-img/21.png" alt="smallImage">
|
| 192 |
-
<img src="assets/small-img/22.png" alt="smallImage">
|
| 193 |
-
<img src="assets/small-img/24.png" alt="smallImage">
|
| 194 |
-
<img src="assets/small-img/26.png" alt="smallImage">
|
| 195 |
-
<img src="assets/small-img/28.png" alt="smallImage">
|
| 196 |
</div>
|
| 197 |
-
</div>
|
| 198 |
|
| 199 |
-
<!--
|
| 200 |
-
<
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
<
|
| 204 |
-
<strong>Py-Match</strong> isn't just about data — it's about people.<br>
|
| 205 |
-
Using cutting-edge technology and psychology-based personality models, <strong>Py-Match</strong> decodes human compatibility.<br>
|
| 206 |
-
Whether in love, business, or recruitment, <strong>Py-Match</strong> gives you clarity and confidence.<br>
|
| 207 |
-
It's science made simple, to help you make life's most important decisions wisely.
|
| 208 |
-
</p>
|
| 209 |
</div>
|
| 210 |
</section>
|
| 211 |
-
|
| 212 |
-
<!-- Footer (now fixed) -->
|
| 213 |
-
<footer>
|
| 214 |
-
<p>© 2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
|
| 215 |
-
<div class="footer-links">
|
| 216 |
-
<a href="#">About Us</a> |
|
| 217 |
-
<a href="#">Contact</a> |
|
| 218 |
-
<a href="#">Privacy Policy</a> |
|
| 219 |
-
<a href="#">Terms & Conditions</a>
|
| 220 |
-
</div>
|
| 221 |
-
<div class="social-icons">
|
| 222 |
-
<a href="#" class="social-icon facebook" aria-label="Facebook">
|
| 223 |
-
<i class="fab fa-facebook-f"></i>
|
| 224 |
-
</a>
|
| 225 |
-
<a href="#" class="social-icon youtube" aria-label="YouTube">
|
| 226 |
-
<i class="fab fa-youtube"></i>
|
| 227 |
-
</a>
|
| 228 |
-
<a href="#" class="social-icon linkedin" aria-label="LinkedIn">
|
| 229 |
-
<i class="fab fa-linkedin-in"></i>
|
| 230 |
-
</a>
|
| 231 |
-
<a href="#" class="social-icon instagram" aria-label="Instagram">
|
| 232 |
-
<i class="fab fa-instagram"></i>
|
| 233 |
-
</a>
|
| 234 |
-
</div>
|
| 235 |
-
</footer>
|
|
|
|
| 1 |
<a class="skip-link" href="#main">Skip to content</a>
|
| 2 |
|
| 3 |
<section class="page">
|
| 4 |
+
<!-- Logo link added below -->
|
| 5 |
+
<div class="intro-logo">
|
| 6 |
+
<a href="https://pykara.net/" target="_blank" rel="noopener" aria-label="Pykara Technologies Home">
|
| 7 |
+
<img src="assets/logo.png" alt="Pykara Technologies Logo" style="height: 60px;" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
</a>
|
| 9 |
+
</div>
|
| 10 |
|
| 11 |
+
<!-- Hero -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
<main id="main" class="hero" role="main">
|
| 13 |
+
<div class="hero__bg" aria-hidden="true"></div>
|
| 14 |
+
<div class="container hero__layout">
|
| 15 |
+
<div class="hero__content">
|
| 16 |
+
<h1 class="hero__title" [innerHTML]="tr('tagline')"></h1>
|
| 17 |
+
<p class="hero__text">{{ tr('subtext') }}</p>
|
| 18 |
+
<div class="hero__badges">
|
| 19 |
+
<span class="badge">AI Adaptive</span>
|
| 20 |
+
<span class="badge">Psychometrics</span>
|
| 21 |
+
<span class="badge">Privacy First</span>
|
| 22 |
+
</div>
|
| 23 |
+
<div class="cta">
|
| 24 |
+
<button type="button"
|
| 25 |
+
class="btn btn-ghost"
|
| 26 |
+
(click)="openAbout()"
|
| 27 |
+
aria-haspopup="dialog"
|
| 28 |
+
aria-controls="aboutDialog"
|
| 29 |
+
[attr.aria-expanded]="isAboutOpen ? 'true' : 'false'">
|
| 30 |
+
Know more
|
| 31 |
+
</button>
|
| 32 |
+
</div>
|
| 33 |
+
<p class="hero__sub-cta" *ngIf="!isSignedIn">Create a free profile to begin your first assessment.</p>
|
| 34 |
</div>
|
| 35 |
</div>
|
| 36 |
</main>
|
| 37 |
|
| 38 |
+
<!-- Feature Differentiators -->
|
| 39 |
+
<section id="features" class="features" aria-labelledby="featuresTitle">
|
| 40 |
+
<div class="container">
|
| 41 |
+
<h2 id="featuresTitle" class="section-title">What Makes Py-Match Different</h2>
|
| 42 |
+
<div class="features__grid">
|
| 43 |
+
<div class="feature">
|
| 44 |
+
<div class="feature__icon gradient-ring"><i class="fa-solid fa-layer-group"></i></div>
|
| 45 |
+
<h3>Adaptive Questioning</h3>
|
| 46 |
+
<p>Dynamic assessments that evolve with every answer to sharpen accuracy.</p>
|
| 47 |
+
</div>
|
| 48 |
+
<div class="feature">
|
| 49 |
+
<div class="feature__icon gradient-ring"><i class="fa-solid fa-dna"></i></div>
|
| 50 |
+
<h3>Behavioral Modeling</h3>
|
| 51 |
+
<p>Maps responses to validated personality archetypes for deep insight.</p>
|
| 52 |
+
</div>
|
| 53 |
+
<div class="feature">
|
| 54 |
+
<div class="feature__icon gradient-ring"><i class="fa-solid fa-chart-line"></i></div>
|
| 55 |
+
<h3>Role Intelligence</h3>
|
| 56 |
+
<p>Aligns your profile with scenario-specific success templates.</p>
|
| 57 |
+
</div>
|
| 58 |
+
<div class="feature">
|
| 59 |
+
<div class="feature__icon gradient-ring"><i class="fa-solid fa-shield-halved"></i></div>
|
| 60 |
+
<h3>Privacy by Design</h3>
|
| 61 |
+
<p>Secure data handling and minimal exposure at every interaction.</p>
|
| 62 |
+
</div>
|
| 63 |
+
<div class="feature">
|
| 64 |
+
<div class="feature__icon gradient-ring"><i class="fa-solid fa-people-group"></i></div>
|
| 65 |
+
<h3>Multi-Context Matching</h3>
|
| 66 |
+
<p>Marriage, hiring, leadership and partnerships in one unified engine.</p>
|
| 67 |
+
</div>
|
| 68 |
+
<div class="feature">
|
| 69 |
+
<div class="feature__icon gradient-ring"><i class="fa-solid fa-bolt"></i></div>
|
| 70 |
+
<h3>Actionable Insights</h3>
|
| 71 |
+
<p>Clear strengths, gaps and complementarity—not vague labels.</p>
|
| 72 |
+
</div>
|
| 73 |
+
</div>
|
| 74 |
+
</div>
|
| 75 |
+
</section>
|
| 76 |
+
|
| 77 |
+
<!-- Journey / How it works -->
|
| 78 |
+
<section id="journey" class="journey" aria-labelledby="journeyTitle">
|
| 79 |
+
<div class="container">
|
| 80 |
+
<h2 id="journeyTitle" class="section-title">Your Matching Journey</h2>
|
| 81 |
+
<ol class="journey__steps">
|
| 82 |
+
<li class="step">
|
| 83 |
+
<div class="step__num">1</div>
|
| 84 |
+
<h3>Create Profile</h3>
|
| 85 |
+
<p>Sign up and select your objective: marriage, hiring, or partnership.</p>
|
| 86 |
+
</li>
|
| 87 |
+
<li class="step">
|
| 88 |
+
<div class="step__num">2</div>
|
| 89 |
+
<h3>Adaptive Assessment</h3>
|
| 90 |
+
<p>Answer evolving questions tailored to sharpen trait precision.</p>
|
| 91 |
+
</li>
|
| 92 |
+
<li class="step">
|
| 93 |
+
<div class="step__num">3</div>
|
| 94 |
+
<h3>Profile Modeling</h3>
|
| 95 |
+
<p>Your responses map into a multi-dimensional personality structure.</p>
|
| 96 |
+
</li>
|
| 97 |
+
<li class="step">
|
| 98 |
+
<div class="step__num">4</div>
|
| 99 |
+
<h3>Intelligent Matching</h3>
|
| 100 |
+
<p>Compare against role or compatibility templates for data-driven fit.</p>
|
| 101 |
+
</li>
|
| 102 |
+
<li class="step">
|
| 103 |
+
<div class="step__num">5</div>
|
| 104 |
+
<h3>Insight & Action</h3>
|
| 105 |
+
<p>Receive transparent recommendations and next-step guidance.</p>
|
| 106 |
+
</li>
|
| 107 |
+
</ol>
|
| 108 |
+
</div>
|
| 109 |
+
</section>
|
| 110 |
+
|
| 111 |
+
<!-- Solutions (Cards) -->
|
| 112 |
+
<section class="cards" aria-labelledby="solutionsTitle">
|
| 113 |
+
<h2 id="solutionsTitle" class="visually-hidden">Solution Scenarios</h2>
|
| 114 |
+
<div class="card">
|
| 115 |
+
<img src="assets/ring.png" alt="Marriage Match">
|
| 116 |
+
<h3>Marriage Match</h3>
|
| 117 |
+
<p>Find the right life partner, beyond surface-level details.</p>
|
| 118 |
+
<p>This analyzes values, habits, and personality traits such as analytical, organized, decisive, and creative.</p>
|
| 119 |
+
<p>Understand true compatibility.</p>
|
| 120 |
+
<p>See strengths and differences clearly.</p>
|
| 121 |
+
<p>Make informed decisions for a lasting relationship.</p>
|
| 122 |
+
</div>
|
| 123 |
+
<div class="card">
|
| 124 |
+
<img src="assets/Hiring.png" alt="Interview & Hiring Match">
|
| 125 |
+
<h3>Interview & Hiring Match</h3>
|
| 126 |
+
<p>Recruit with confidence, powered by science.</p>
|
| 127 |
+
<p>Spot hidden strengths and leadership potential.</p>
|
| 128 |
+
<p>Match candidates to roles where they'll thrive.</p>
|
| 129 |
+
<p>Build balanced, high-performing teams.</p>
|
| 130 |
+
</div>
|
| 131 |
+
<div class="card">
|
| 132 |
+
<img src="assets/business.png" alt="Business Partner Match">
|
| 133 |
+
<h3>Business Partner Match</h3>
|
| 134 |
+
<p>Choose partners who complement your strengths.</p>
|
| 135 |
+
<p>Identify complementary skills and leadership styles.</p>
|
| 136 |
+
<p>Reduce risks of conflict in decision-making.</p>
|
| 137 |
+
<p>Build stronger, trust-based collaborations.</p>
|
| 138 |
+
</div>
|
| 139 |
+
</section>
|
| 140 |
+
|
| 141 |
+
<!-- Special Content + Bottom CTA -->
|
| 142 |
+
<section class="special-content" aria-labelledby="specialTitle">
|
| 143 |
+
<div class="card special-card">
|
| 144 |
+
<h2 id="specialTitle">Why Py-Match is Special?</h2>
|
| 145 |
+
<p>
|
| 146 |
+
<strong>Py-Match</strong> isn't just about data — it's about people.
|
| 147 |
+
Using cutting-edge technology and psychology-based personality models, <strong>Py-Match</strong> decodes human compatibility.
|
| 148 |
+
Whether in love, business, or recruitment, <strong>Py-Match</strong> gives you clarity and confidence.
|
| 149 |
+
It's science made simple, to help you make life's most important decisions wisely.
|
| 150 |
+
</p>
|
| 151 |
+
<div class="secondaryCard">
|
| 152 |
+
<h3 class="cta-bottom__title">Have a question or need guidance?</h3>
|
| 153 |
+
<p class="cta-bottom__text">Contact us — we’re happy to assist you on your journey.</p>
|
| 154 |
+
<button>Contact Us</button>
|
| 155 |
+
</div>
|
| 156 |
+
</div>
|
| 157 |
|
| 158 |
+
<!--<div class="cta-bottom" *ngIf="!isSignedIn">
|
| 159 |
+
</div>-->
|
| 160 |
+
<div class="cta-bottom" *ngIf="isSignedIn">
|
| 161 |
+
<h3 class="cta-bottom__title">Ready to continue matching?</h3>
|
| 162 |
+
<p class="cta-bottom__text">Jump back in and explore intelligent compatibility insights.</p>
|
| 163 |
+
<a class="btn btn-secondary cta-bottom__btn" [routerLink]="['/matchinglist', userId]" [class.disabled]="!canMatch" [attr.aria-disabled]="!canMatch ? true : null">Continue Matching</a>
|
| 164 |
+
</div>
|
| 165 |
+
</section>
|
| 166 |
+
|
| 167 |
+
<!-- Footer -->
|
| 168 |
+
<footer class="site-footer" role="contentinfo">
|
| 169 |
+
<p class="site-footer__copy">© 2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
|
| 170 |
+
<div class="site-footer__row">
|
| 171 |
+
<nav class="site-footer__links" aria-label="Footer navigation">
|
| 172 |
+
<a href="#" (click)="$event.preventDefault(); openPrivacyModal();">Privacy Policy</a>
|
| 173 |
+
<a href="https://pykara.net/" target="_blank" rel="noopener">About Us</a>
|
| 174 |
+
<a href="https://pykara.net/contact-us/" target="_blank" rel="noopener">Contact</a>
|
| 175 |
+
<a href="#" (click)="$event.preventDefault(); openTermsModal();">Terms & Conditions</a>
|
| 176 |
+
</nav>
|
| 177 |
+
<div class="site-footer__social" aria-label="Social media">
|
| 178 |
+
<a href="https://www.facebook.com/profile.php?id=100087653675803" target="_blank" rel="noopener" aria-label="Facebook"><i class="fa-brands fa-facebook-f"></i></a>
|
| 179 |
+
<a href="https://www.youtube.com/@PykaraTechnologies" target="_blank" rel="noopener" aria-label="YouTube"><i class="fa-brands fa-youtube"></i></a>
|
| 180 |
+
<a href="https://www.linkedin.com/in/pykara-technologies" target="_blank" rel="noopener" aria-label="LinkedIn"><i class="fa-brands fa-linkedin-in"></i></a>
|
| 181 |
+
<a href="https://www.instagram.com/pykaratechnologie/" target="_blank" rel="noopener" aria-label="Instagram"><i class="fa-brands fa-instagram"></i></a>
|
| 182 |
+
</div>
|
| 183 |
+
</div>
|
| 184 |
+
</footer>
|
| 185 |
+
|
| 186 |
+
<!-- AUTH MODAL -->
|
| 187 |
<div class="modal-backdrop" *ngIf="modal" (click)="backdropClick($event)">
|
| 188 |
<div class="modal-panel modal-panel--white"
|
| 189 |
role="dialog"
|
|
|
|
| 196 |
(signInSuccess)="handleSignInSuccess()"></app-sign-in>
|
| 197 |
<app-sign-up *ngIf="modal === 'signup'"
|
| 198 |
(switchToSignIn)="openSignIn()"
|
| 199 |
+
(signUpSuccess)="handleSignUpSuccess()"></app-sign-up>
|
| 200 |
</div>
|
| 201 |
</div>
|
| 202 |
|
| 203 |
+
<!-- ABOUT MODAL (restyled to match Privacy Policy modal) -->
|
| 204 |
<div class="about-backdrop" *ngIf="isAboutOpen" (click)="closeAbout()" tabindex="-1"></div>
|
|
|
|
| 205 |
<div class="about-modal"
|
| 206 |
*ngIf="isAboutOpen"
|
| 207 |
role="dialog"
|
|
|
|
| 210 |
id="aboutDialog"
|
| 211 |
tabindex="-1">
|
| 212 |
<div class="about-modal__header">
|
| 213 |
+
<h2 id="aboutTitle" class="about-modal__title">About Py-Match</h2>
|
| 214 |
+
<button class="about-modal__close" (click)="closeAbout()" aria-label="Close dialog">×</button>
|
| 215 |
</div>
|
|
|
|
| 216 |
<div class="about-modal__body">
|
| 217 |
+
<p><strong>Your Ideal Match Awaits</strong></p>
|
| 218 |
<p><strong>Py-Match</strong> is an advanced personality assessment tool designed to help individuals find the ideal match based on their core personality traits. The system uses a series of adaptive questions that are tailored to each user's profile. Each response is mapped to one of four core personality profiles, which represent different dominant behavioral tendencies. As users progress through the assessment, the platform continuously adapts, ensuring that the questions remain relevant and engaging, ultimately providing a more accurate personality profile.</p>
|
|
|
|
| 219 |
<p>The primary goal of <strong>Py-Match</strong> is to offer data-driven, personalized matches for various scenarios such as marriage compatibility, business partnerships, and leadership fit. By comparing the individual's personality profile to role-specific templates, the platform ensures that the match is based on both complementary and compatible traits. This sophisticated matching engine provides an in-depth, personalized experience while maintaining strict privacy standards. With a focus on both accuracy and user confidentiality, <strong>Py-Match</strong> guarantees that sensitive information is securely stored, offering users peace of mind while they explore the platform's potential.</p>
|
| 220 |
</div>
|
| 221 |
+
<div class="about-modal__footer">
|
| 222 |
+
<button class="about-modal__btn" (click)="closeAbout()">Close</button>
|
| 223 |
+
</div>
|
| 224 |
</div>
|
| 225 |
|
| 226 |
+
<!-- Privacy Policy Modal (styled like About modal) -->
|
| 227 |
+
<div class="about-backdrop" *ngIf="showPrivacyModal" (click)="closePrivacyModal()" tabindex="-1"></div>
|
| 228 |
+
<div class="about-modal"
|
| 229 |
+
*ngIf="showPrivacyModal"
|
| 230 |
+
role="dialog"
|
| 231 |
+
aria-modal="true"
|
| 232 |
+
aria-labelledby="privacyTitle"
|
| 233 |
+
id="privacyDialog"
|
| 234 |
+
tabindex="-1">
|
| 235 |
+
<div class="about-modal__header">
|
| 236 |
+
<h2 id="privacyTitle" class="about-modal__title">Privacy Policy</h2>
|
| 237 |
+
<button class="about-modal__close" (click)="closePrivacyModal()" aria-label="Close dialog">×</button>
|
| 238 |
+
</div>
|
| 239 |
+
<div class="about-modal__body">
|
| 240 |
+
<p><strong>Py-Match Privacy Policy</strong></p>
|
| 241 |
+
<p>Your privacy is important to us. Py-Match securely stores your data and only uses it for personality assessment and matching purposes. We do not sell or share your personal information with third parties. All responses are encrypted and handled according to strict privacy standards. You can request deletion of your data at any time by contacting support.</p>
|
| 242 |
+
<ul>
|
| 243 |
+
<li>We collect only the information necessary for matching and assessment.</li>
|
| 244 |
+
<li>Your data is never sold or shared outside Py-Match.</li>
|
| 245 |
+
<li>All communications are encrypted and confidential.</li>
|
| 246 |
+
<li>You may request data deletion at any time.</li>
|
| 247 |
+
</ul>
|
| 248 |
+
<p>By using Py-Match, you agree to our privacy practices. For questions, contact support@pykara.net.</p>
|
| 249 |
+
</div>
|
| 250 |
+
<div class="about-modal__footer">
|
| 251 |
+
<button class="about-modal__btn" (click)="acceptPrivacyPolicy()">Accept Privacy Policy</button>
|
| 252 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
</div>
|
|
|
|
| 254 |
|
| 255 |
+
<!-- Terms & Conditions Modal (styled like About modal) -->
|
| 256 |
+
<div class="about-backdrop" *ngIf="showTermsModal" (click)="closeTermsModal()" tabindex="-1"></div>
|
| 257 |
+
<div class="about-modal"
|
| 258 |
+
*ngIf="showTermsModal"
|
| 259 |
+
role="dialog"
|
| 260 |
+
aria-modal="true"
|
| 261 |
+
aria-labelledby="termsTitle"
|
| 262 |
+
id="termsDialog"
|
| 263 |
+
tabindex="-1">
|
| 264 |
+
<div class="about-modal__header">
|
| 265 |
+
<h2 id="termsTitle" class="about-modal__title">Terms & Conditions</h2>
|
| 266 |
+
<button class="about-modal__close" (click)="closeTermsModal()" aria-label="Close dialog">×</button>
|
| 267 |
+
</div>
|
| 268 |
+
<div class="about-modal__body">
|
| 269 |
+
<p><strong>Py-Match Terms & Conditions</strong></p>
|
| 270 |
+
<ul>
|
| 271 |
+
<li>By using Py-Match, you agree to provide accurate information for assessments and matching.</li>
|
| 272 |
+
<li>Py-Match is intended for personal, business, and recruitment use only. Misuse or unauthorized access is prohibited.</li>
|
| 273 |
+
<li>All intellectual property, including algorithms, content, and branding, belongs to Pykara Technologies Pvt. Ltd.</li>
|
| 274 |
+
<li>Py-Match does not guarantee match outcomes; results are based on provided data and scientific models.</li>
|
| 275 |
+
<li>Users are responsible for maintaining the confidentiality of their login credentials.</li>
|
| 276 |
+
<li>Py-Match reserves the right to update these terms at any time. Continued use constitutes acceptance of changes.</li>
|
| 277 |
+
<li>For questions or concerns, contact support@pykara.net.</li>
|
| 278 |
+
</ul>
|
| 279 |
+
<p>By continuing to use Py-Match, you acknowledge and accept these terms and conditions.</p>
|
| 280 |
+
</div>
|
| 281 |
+
<div class="about-modal__footer">
|
| 282 |
+
<button class="about-modal__btn" (click)="closeTermsModal()">Close</button>
|
| 283 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
</div>
|
|
|
|
| 285 |
|
| 286 |
+
<!-- Loader Overlay (same as llm-quiz) -->
|
| 287 |
+
<div class="loader-overlay" *ngIf="isLoading">
|
| 288 |
+
<div class="loader-container">
|
| 289 |
+
<img src="assets/Loader.gif" alt="Loading..." class="loader-gif" />
|
| 290 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
</div>
|
| 292 |
</section>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/app/intro-page/intro-page.component.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
| 1 |
-
import { Component, ChangeDetectionStrategy, HostListener, ChangeDetectorRef } from '@angular/core';
|
| 2 |
import { CommonModule } from '@angular/common';
|
| 3 |
-
import { RouterLink } from '@angular/router';
|
|
|
|
| 4 |
import { SignInComponent } from '../auth/sign-in/sign-in.component';
|
| 5 |
import { SignUpComponent } from '../auth/sign-up/sign-up.component';
|
|
|
|
| 6 |
|
| 7 |
type AuthModal = 'signin' | 'signup' | null;
|
| 8 |
|
|
@@ -14,12 +16,87 @@ type AuthModal = 'signin' | 'signup' | null;
|
|
| 14 |
styleUrls: ['./intro-page.component.css'],
|
| 15 |
changeDetection: ChangeDetectionStrategy.OnPush
|
| 16 |
})
|
| 17 |
-
export class IntroPageComponent {
|
| 18 |
modal: AuthModal = null;
|
| 19 |
isSignedIn = false;
|
| 20 |
isAboutOpen = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
constructor(private cdr: ChangeDetectorRef) { }
|
| 23 |
// --- Auth modal handlers ---
|
| 24 |
openSignIn(): void {
|
| 25 |
this.modal = 'signin';
|
|
@@ -37,25 +114,19 @@ export class IntroPageComponent {
|
|
| 37 |
this.cdr.markForCheck();
|
| 38 |
}
|
| 39 |
|
| 40 |
-
|
| 41 |
-
// Called by child components on success
|
| 42 |
handleSignInSuccess(): void {
|
| 43 |
this.isSignedIn = true;
|
| 44 |
this.closeModal();
|
| 45 |
}
|
| 46 |
|
| 47 |
-
// Handle sign-up success - switch to sign-in modal
|
| 48 |
handleSignUpSuccess(): void {
|
| 49 |
-
// Close sign-up and open sign-in
|
| 50 |
this.modal = 'signin';
|
| 51 |
-
// You can optionally show a success message here
|
| 52 |
alert('Sign up successful! Please sign in to continue.');
|
| 53 |
this.cdr.markForCheck();
|
| 54 |
}
|
| 55 |
|
| 56 |
signOut(): void {
|
| 57 |
this.isSignedIn = false;
|
| 58 |
-
// Clear any persisted auth if used later
|
| 59 |
localStorage.removeItem('auth_token');
|
| 60 |
this.cdr.markForCheck();
|
| 61 |
}
|
|
@@ -70,41 +141,96 @@ export class IntroPageComponent {
|
|
| 70 |
openAbout(): void {
|
| 71 |
this.isAboutOpen = true;
|
| 72 |
document.body.style.overflow = 'hidden';
|
| 73 |
-
// Move focus into the dialog for accessibility
|
| 74 |
setTimeout(() => document.getElementById('aboutDialog')?.focus(), 0);
|
| 75 |
this.cdr.markForCheck();
|
| 76 |
}
|
| 77 |
|
| 78 |
closeAbout(): void {
|
| 79 |
this.isAboutOpen = false;
|
| 80 |
-
// If Auth is also open, keep scroll locked. Otherwise restore.
|
| 81 |
if (!this.modal) document.body.style.overflow = '';
|
| 82 |
this.cdr.markForCheck();
|
| 83 |
}
|
| 84 |
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
@HostListener('document:keydown.escape')
|
| 87 |
onEsc(): void {
|
| 88 |
if (this.modal) this.closeModal();
|
| 89 |
else if (this.isAboutOpen) this.closeAbout();
|
|
|
|
| 90 |
}
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
const element = document.getElementById('content-sections');
|
| 93 |
if (element) {
|
| 94 |
element.scrollIntoView({ behavior: 'smooth' });
|
| 95 |
}
|
| 96 |
}
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
tr(key: string): string {
|
| 99 |
const dict = {
|
| 100 |
brand: 'Py-Match',
|
| 101 |
signup: 'Sign up',
|
| 102 |
signin: 'Sign in',
|
| 103 |
-
tagline: 'Smarter matches, powered by personality science.',
|
| 104 |
subtext: 'Discover compatibility in love, business, and careers with AI-driven personality insights.',
|
| 105 |
-
/* getStarted: 'Get started',*/
|
| 106 |
exploreFeatures: 'Explore our features below'
|
| 107 |
-
};
|
| 108 |
-
return dict
|
| 109 |
}
|
| 110 |
}
|
|
|
|
| 1 |
+
import { Component, ChangeDetectionStrategy, HostListener, ChangeDetectorRef, OnInit, AfterViewInit } from '@angular/core';
|
| 2 |
import { CommonModule } from '@angular/common';
|
| 3 |
+
import { Router, RouterLink } from '@angular/router';
|
| 4 |
+
import { HttpClient } from '@angular/common/http';
|
| 5 |
import { SignInComponent } from '../auth/sign-in/sign-in.component';
|
| 6 |
import { SignUpComponent } from '../auth/sign-up/sign-up.component';
|
| 7 |
+
import { SignupService } from '../auth/sign-up/sign-up.service';
|
| 8 |
|
| 9 |
type AuthModal = 'signin' | 'signup' | null;
|
| 10 |
|
|
|
|
| 16 |
styleUrls: ['./intro-page.component.css'],
|
| 17 |
changeDetection: ChangeDetectionStrategy.OnPush
|
| 18 |
})
|
| 19 |
+
export class IntroPageComponent implements OnInit {
|
| 20 |
modal: AuthModal = null;
|
| 21 |
isSignedIn = false;
|
| 22 |
isAboutOpen = false;
|
| 23 |
+
canMatch: boolean = false;
|
| 24 |
+
userId: number | null = null;
|
| 25 |
+
showMarriageSubnav = false;
|
| 26 |
+
showPrivacyModal = false;
|
| 27 |
+
privacyAccepted = false;
|
| 28 |
+
showTermsModal = false;
|
| 29 |
+
isLoading = false;
|
| 30 |
+
|
| 31 |
+
constructor(
|
| 32 |
+
private cdr: ChangeDetectorRef,
|
| 33 |
+
private signupService: SignupService,
|
| 34 |
+
private router: Router,
|
| 35 |
+
private http: HttpClient
|
| 36 |
+
) { }
|
| 37 |
+
|
| 38 |
+
ngOnInit(): void {
|
| 39 |
+
window.scrollTo({ top: 0, left: 0, behavior: 'auto' });
|
| 40 |
+
this.userId = this.signupService.getUserId();
|
| 41 |
+
if (this.userId) {
|
| 42 |
+
// Check if the user has completed LLM questions
|
| 43 |
+
this.http.get(`http://127.0.0.1:5000/api/match/${this.userId}?role=marriage`)
|
| 44 |
+
.subscribe({
|
| 45 |
+
next: () => {
|
| 46 |
+
this.canMatch = true;
|
| 47 |
+
this.cdr.markForCheck();
|
| 48 |
+
},
|
| 49 |
+
error: () => {
|
| 50 |
+
this.canMatch = false;
|
| 51 |
+
this.cdr.markForCheck();
|
| 52 |
+
}
|
| 53 |
+
});
|
| 54 |
+
}
|
| 55 |
+
// Only check if accepted, do not show modal automatically
|
| 56 |
+
this.privacyAccepted = localStorage.getItem('privacyAccepted') === 'true';
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// --- Marriage Sub Navigation Methods ---
|
| 60 |
+
toggleMarriageSubnav(): void {
|
| 61 |
+
this.showMarriageSubnav = !this.showMarriageSubnav;
|
| 62 |
+
this.cdr.markForCheck();
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
hideMarriageSubnav(): void {
|
| 66 |
+
this.showMarriageSubnav = false;
|
| 67 |
+
this.cdr.markForCheck();
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
navigateToProfile(): void {
|
| 71 |
+
const userId = localStorage.getItem('user_id');
|
| 72 |
+
if (!userId) {
|
| 73 |
+
alert('Please sign in first.');
|
| 74 |
+
return;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
this.router.navigate(['/question-answer'], { queryParams: { role: 'marriage' } });
|
| 78 |
+
this.showMarriageSubnav = false;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
navigateToExpectations(): void {
|
| 82 |
+
this.showMarriageSubnav = false;
|
| 83 |
+
this.router.navigate(['/userPreference']);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
navigateToAssessment(): void {
|
| 87 |
+
this.showMarriageSubnav = false;
|
| 88 |
+
this.router.navigate(['/llmquiz']);
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
navigateToMatchingProfile(): void {
|
| 92 |
+
this.showMarriageSubnav = false;
|
| 93 |
+
this.router.navigate(['/matchinglist']);
|
| 94 |
+
}
|
| 95 |
+
//} else {
|
| 96 |
+
// alert('User ID not found. Please sign in first.');
|
| 97 |
+
//}
|
| 98 |
+
//}
|
| 99 |
|
|
|
|
| 100 |
// --- Auth modal handlers ---
|
| 101 |
openSignIn(): void {
|
| 102 |
this.modal = 'signin';
|
|
|
|
| 114 |
this.cdr.markForCheck();
|
| 115 |
}
|
| 116 |
|
|
|
|
|
|
|
| 117 |
handleSignInSuccess(): void {
|
| 118 |
this.isSignedIn = true;
|
| 119 |
this.closeModal();
|
| 120 |
}
|
| 121 |
|
|
|
|
| 122 |
handleSignUpSuccess(): void {
|
|
|
|
| 123 |
this.modal = 'signin';
|
|
|
|
| 124 |
alert('Sign up successful! Please sign in to continue.');
|
| 125 |
this.cdr.markForCheck();
|
| 126 |
}
|
| 127 |
|
| 128 |
signOut(): void {
|
| 129 |
this.isSignedIn = false;
|
|
|
|
| 130 |
localStorage.removeItem('auth_token');
|
| 131 |
this.cdr.markForCheck();
|
| 132 |
}
|
|
|
|
| 141 |
openAbout(): void {
|
| 142 |
this.isAboutOpen = true;
|
| 143 |
document.body.style.overflow = 'hidden';
|
|
|
|
| 144 |
setTimeout(() => document.getElementById('aboutDialog')?.focus(), 0);
|
| 145 |
this.cdr.markForCheck();
|
| 146 |
}
|
| 147 |
|
| 148 |
closeAbout(): void {
|
| 149 |
this.isAboutOpen = false;
|
|
|
|
| 150 |
if (!this.modal) document.body.style.overflow = '';
|
| 151 |
this.cdr.markForCheck();
|
| 152 |
}
|
| 153 |
|
| 154 |
+
openPrivacyModal(): void {
|
| 155 |
+
this.showPrivacyModal = true;
|
| 156 |
+
this.cdr.markForCheck();
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
closePrivacyModal(): void {
|
| 160 |
+
this.showPrivacyModal = false;
|
| 161 |
+
this.cdr.markForCheck();
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
acceptPrivacyPolicy(): void {
|
| 165 |
+
localStorage.setItem('privacyAccepted', 'true');
|
| 166 |
+
this.privacyAccepted = true;
|
| 167 |
+
this.closePrivacyModal();
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
openTermsModal(): void {
|
| 171 |
+
this.showTermsModal = true;
|
| 172 |
+
this.cdr.markForCheck();
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
closeTermsModal(): void {
|
| 176 |
+
this.showTermsModal = false;
|
| 177 |
+
this.cdr.markForCheck();
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
@HostListener('document:keydown.escape')
|
| 181 |
onEsc(): void {
|
| 182 |
if (this.modal) this.closeModal();
|
| 183 |
else if (this.isAboutOpen) this.closeAbout();
|
| 184 |
+
else if (this.showMarriageSubnav) this.hideMarriageSubnav();
|
| 185 |
}
|
| 186 |
+
|
| 187 |
+
@HostListener('document:click', ['$event'])
|
| 188 |
+
onDocumentClick(event: MouseEvent): void {
|
| 189 |
+
// Close subnav when clicking outside
|
| 190 |
+
if (this.showMarriageSubnav) {
|
| 191 |
+
const target = event.target as HTMLElement;
|
| 192 |
+
if (!target.closest('.nav-item-wrapper')) {
|
| 193 |
+
this.hideMarriageSubnav();
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
scrollToContent(): void {
|
| 199 |
const element = document.getElementById('content-sections');
|
| 200 |
if (element) {
|
| 201 |
element.scrollIntoView({ behavior: 'smooth' });
|
| 202 |
}
|
| 203 |
}
|
| 204 |
+
|
| 205 |
+
selectRole(role: string): void {
|
| 206 |
+
const userId = localStorage.getItem('user_id');
|
| 207 |
+
if (!userId) {
|
| 208 |
+
alert('Please sign in first.');
|
| 209 |
+
return;
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
this.signupService.assignRole(+userId, role).subscribe({
|
| 213 |
+
next: (res) => {
|
| 214 |
+
console.log('Role saved:', res);
|
| 215 |
+
this.router.navigate(['/question-answer'], { queryParams: { role } });
|
| 216 |
+
},
|
| 217 |
+
error: (err) => {
|
| 218 |
+
console.error('Role assign failed:', err);
|
| 219 |
+
alert('Failed to assign role.');
|
| 220 |
+
}
|
| 221 |
+
});
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
// i18n helper
|
| 225 |
tr(key: string): string {
|
| 226 |
const dict = {
|
| 227 |
brand: 'Py-Match',
|
| 228 |
signup: 'Sign up',
|
| 229 |
signin: 'Sign in',
|
| 230 |
+
tagline: 'Smarter matches,<br><span class="tagline-line2">powered by personality science.</span>',
|
| 231 |
subtext: 'Discover compatibility in love, business, and careers with AI-driven personality insights.',
|
|
|
|
| 232 |
exploreFeatures: 'Explore our features below'
|
| 233 |
+
} as const;
|
| 234 |
+
return (dict as Record<string, string>)[key] ?? key;
|
| 235 |
}
|
| 236 |
}
|
src/app/llm-quiz/llm-quiz.component.css
CHANGED
|
@@ -1,274 +1,732 @@
|
|
| 1 |
-
/*
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
}
|
| 7 |
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
}
|
| 11 |
|
| 12 |
-
.
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
}
|
| 19 |
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
display: flex;
|
| 22 |
align-items: center;
|
| 23 |
gap: 12px;
|
| 24 |
-
|
| 25 |
}
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
display: flex;
|
| 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 |
display: flex;
|
| 75 |
justify-content: space-between;
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
|
|
|
| 79 |
}
|
| 80 |
|
| 81 |
-
.
|
| 82 |
-
font-
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
}
|
| 86 |
|
|
|
|
| 87 |
.options {
|
| 88 |
display: grid;
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
}
|
| 91 |
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
display: flex;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
align-items: center;
|
| 95 |
-
gap:
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
}
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
padding: 3px 8px;
|
| 108 |
-
border-radius: 999px;
|
| 109 |
-
font-size: 12px;
|
| 110 |
-
color: #fff;
|
| 111 |
}
|
| 112 |
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
|
|
|
| 116 |
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
|
|
|
|
|
|
| 120 |
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
|
|
|
| 124 |
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
}
|
| 132 |
|
| 133 |
.bar {
|
| 134 |
display: grid;
|
| 135 |
-
grid-template-columns:
|
| 136 |
align-items: center;
|
| 137 |
-
gap:
|
| 138 |
-
margin:
|
| 139 |
}
|
| 140 |
|
| 141 |
.bar-label {
|
|
|
|
|
|
|
|
|
|
| 142 |
font-weight: 600;
|
|
|
|
|
|
|
| 143 |
}
|
| 144 |
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
}
|
| 151 |
|
| 152 |
-
.
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
}
|
| 155 |
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
.bar-fill.green {
|
| 161 |
-
background: #86efac;
|
| 162 |
-
}
|
| 163 |
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
background: #fde68a;
|
| 170 |
-
}
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
| 175 |
}
|
| 176 |
-
*/
|
| 177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
-
.
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
}
|
| 185 |
|
| 186 |
-
.
|
| 187 |
-
display: flex;
|
| 188 |
-
gap: 8px;
|
| 189 |
align-items: center;
|
| 190 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
}
|
| 192 |
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
|
|
|
| 196 |
|
| 197 |
-
.
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
}
|
| 202 |
|
|
|
|
| 203 |
.loader-overlay {
|
| 204 |
position: fixed;
|
| 205 |
inset: 0;
|
|
|
|
| 206 |
display: grid;
|
| 207 |
place-items: center;
|
| 208 |
-
|
|
|
|
| 209 |
}
|
| 210 |
|
| 211 |
-
.
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
}
|
| 216 |
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
color: #666;
|
| 221 |
-
margin-bottom: 8px;
|
| 222 |
}
|
| 223 |
|
| 224 |
-
.
|
| 225 |
-
|
|
|
|
|
|
|
| 226 |
}
|
| 227 |
|
| 228 |
-
.
|
| 229 |
-
|
| 230 |
-
|
|
|
|
| 231 |
}
|
| 232 |
|
| 233 |
-
.
|
| 234 |
display: flex;
|
| 235 |
align-items: center;
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
.opt-color {
|
| 240 |
-
color: #888;
|
| 241 |
-
}
|
| 242 |
-
|
| 243 |
-
.progress, .result {
|
| 244 |
-
margin-top: 16px;
|
| 245 |
}
|
| 246 |
|
| 247 |
-
.
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
gap: 8px;
|
| 252 |
-
margin: 6px 0;
|
| 253 |
}
|
| 254 |
|
| 255 |
-
.
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
border
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
| 260 |
}
|
| 261 |
|
| 262 |
-
.
|
| 263 |
-
|
| 264 |
-
|
|
|
|
| 265 |
}
|
| 266 |
|
| 267 |
-
|
| 268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
}
|
| 270 |
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
}
|
|
|
|
|
|
| 1 |
+
/*[file name]: llm-quiz.component.css
|
| 2 |
+
[file content begin]*/
|
| 3 |
+
/* Enhanced brain-themed UI */
|
| 4 |
+
:host {
|
| 5 |
+
display: block;
|
| 6 |
+
min-height: 100vh;
|
| 7 |
+
background:
|
| 8 |
+
radial-gradient(circle at 15% 20%, rgba(45, 75, 120, 0.8) 0%, transparent 40%),
|
| 9 |
+
radial-gradient(circle at 85% 80%, rgba(120, 75, 180, 0.6) 0%, transparent 40%),
|
| 10 |
+
radial-gradient(circle at 50% 50%, rgba(30, 45, 75, 1) 0%, #121a23 100%);
|
| 11 |
+
background-attachment: fixed;
|
| 12 |
+
position: relative;
|
| 13 |
+
overflow-x: hidden;
|
| 14 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
/* Neural network background animation */
|
| 18 |
+
.brain-bg {
|
| 19 |
+
position: fixed;
|
| 20 |
+
top: 0;
|
| 21 |
+
left: 0;
|
| 22 |
+
width: 100%;
|
| 23 |
+
height: 100%;
|
| 24 |
+
pointer-events: none;
|
| 25 |
+
z-index: 0;
|
| 26 |
+
opacity: 0.4;
|
| 27 |
}
|
| 28 |
|
| 29 |
+
.neuron {
|
| 30 |
+
position: absolute;
|
| 31 |
+
width: 6px;
|
| 32 |
+
height: 6px;
|
| 33 |
+
border-radius: 50%;
|
| 34 |
+
background: radial-gradient(circle, #f3a54c, #e67e22);
|
| 35 |
+
box-shadow: 0 0 12px 4px rgba(243, 165, 76, 0.7);
|
| 36 |
+
animation: neuronPulse 3s ease-in-out infinite;
|
| 37 |
}
|
| 38 |
|
| 39 |
+
.neuron-connection {
|
| 40 |
+
position: absolute;
|
| 41 |
+
height: 2px;
|
| 42 |
+
background: linear-gradient(90deg, transparent, rgba(243, 165, 76, 0.6), transparent);
|
| 43 |
+
transform-origin: left center;
|
| 44 |
+
animation: connectionFlow 4s linear infinite;
|
| 45 |
}
|
| 46 |
|
| 47 |
+
@keyframes neuronPulse {
|
| 48 |
+
0%, 100% { transform: scale(1); opacity: 0.7; }
|
| 49 |
+
50% { transform: scale(1.3); opacity: 1; }
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
@keyframes connectionFlow {
|
| 53 |
+
0% { opacity: 0; transform: scaleX(0); }
|
| 54 |
+
50% { opacity: 0.8; transform: scaleX(1); }
|
| 55 |
+
100% { opacity: 0; transform: scaleX(0); }
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
/* Enhanced topbar with brain theme */
|
| 59 |
+
.quiz-topbar {
|
| 60 |
+
position: fixed;
|
| 61 |
+
top: 0;
|
| 62 |
+
left: 0;
|
| 63 |
+
width: 100%;
|
| 64 |
+
padding: 14px 38px;
|
| 65 |
+
display: flex;
|
| 66 |
+
align-items: center;
|
| 67 |
+
justify-content: space-between;
|
| 68 |
+
background: rgba(18, 26, 35, 0.9);
|
| 69 |
+
backdrop-filter: blur(12px);
|
| 70 |
+
z-index: 2000;
|
| 71 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
| 72 |
+
border-bottom: 1px solid rgba(243, 165, 76, 0.2);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.brand {
|
| 76 |
+
display: inline-flex;
|
| 77 |
+
align-items: center;
|
| 78 |
+
gap: 16px;
|
| 79 |
+
cursor: pointer;
|
| 80 |
+
user-select: none;
|
| 81 |
+
transition: transform 0.3s ease;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.brand:hover {
|
| 85 |
+
transform: translateY(-2px);
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
.brand__logo-img {
|
| 89 |
+
background: linear-gradient(135deg, #f3a54c, #e67e22);
|
| 90 |
+
width: 54px;
|
| 91 |
+
height: 54px;
|
| 92 |
+
border-radius: 50%;
|
| 93 |
+
padding: 6px;
|
| 94 |
+
box-shadow: 0 4px 15px rgba(243, 165, 76, 0.4);
|
| 95 |
+
transition: all 0.3s ease;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.brand__name {
|
| 99 |
+
letter-spacing: 2px;
|
| 100 |
+
color: #ffffff;
|
| 101 |
+
font-size: 28px;
|
| 102 |
+
font-weight: 300;
|
| 103 |
+
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.brand__name strong {
|
| 107 |
+
color: #f3a54c;
|
| 108 |
+
font-weight: 400;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.top-progress {
|
| 112 |
display: flex;
|
| 113 |
align-items: center;
|
| 114 |
gap: 12px;
|
| 115 |
+
color: #f0f0f0;
|
| 116 |
}
|
| 117 |
|
| 118 |
+
.tp-label {
|
| 119 |
+
font-size: 14px;
|
| 120 |
+
font-weight: 500;
|
| 121 |
+
}
|
| 122 |
|
| 123 |
+
.tp-track {
|
| 124 |
+
width: 120px;
|
| 125 |
+
height: 6px;
|
| 126 |
+
background: rgba(255, 255, 255, 0.1);
|
| 127 |
+
border-radius: 3px;
|
| 128 |
+
overflow: hidden;
|
| 129 |
+
}
|
| 130 |
|
| 131 |
+
.tp-fill {
|
| 132 |
+
height: 100%;
|
| 133 |
+
background: linear-gradient(90deg, #f3a54c, #e67e22);
|
| 134 |
+
border-radius: 3px;
|
| 135 |
+
transition: width 0.5s ease;
|
| 136 |
+
}
|
| 137 |
|
| 138 |
+
/* Brain-themed hero section */
|
| 139 |
+
.quiz-hero {
|
| 140 |
+
max-width: 880px;
|
| 141 |
+
margin: 100px auto 30px;
|
| 142 |
+
padding: 50px 40px;
|
| 143 |
+
background:
|
| 144 |
+
radial-gradient(circle at 20% 30%, rgba(243, 165, 76, 0.15) 0%, transparent 50%),
|
| 145 |
+
linear-gradient(135deg, rgba(30, 42, 60, 0.9) 0%, rgba(40, 55, 80, 0.9) 100%);
|
| 146 |
+
border-radius: 24px;
|
| 147 |
+
position: relative;
|
| 148 |
+
overflow: hidden;
|
| 149 |
+
box-shadow:
|
| 150 |
+
0 20px 40px rgba(0, 0, 0, 0.4),
|
| 151 |
+
0 8px 24px rgba(0, 0, 0, 0.3);
|
| 152 |
+
border: 1px solid rgba(243, 165, 76, 0.2);
|
| 153 |
+
color: #f5f8fb;
|
| 154 |
+
text-align: center;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
.quiz-hero::before {
|
| 158 |
+
content: "";
|
| 159 |
+
position: absolute;
|
| 160 |
+
inset: 0;
|
| 161 |
+
background:
|
| 162 |
+
radial-gradient(circle at 70% 20%, rgba(243, 165, 76, 0.25) 0%, transparent 40%),
|
| 163 |
+
radial-gradient(circle at 30% 80%, rgba(120, 75, 180, 0.2) 0%, transparent 40%);
|
| 164 |
+
mix-blend-mode: overlay;
|
| 165 |
+
z-index: 1;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.hero-inner {
|
| 169 |
+
position: relative;
|
| 170 |
+
z-index: 2;
|
| 171 |
display: flex;
|
| 172 |
+
flex-direction: column;
|
| 173 |
+
align-items: center;
|
| 174 |
+
gap: 20px;
|
| 175 |
}
|
| 176 |
|
| 177 |
+
.hero-icon {
|
| 178 |
+
width: 100px;
|
| 179 |
+
height: 100px;
|
| 180 |
+
border-radius: 50%;
|
| 181 |
+
background: linear-gradient(135deg, #f3a54c, #e67e22);
|
| 182 |
+
display: flex;
|
| 183 |
+
align-items: center;
|
| 184 |
+
justify-content: center;
|
| 185 |
+
font-size: 46px;
|
| 186 |
+
color: #1e2532;
|
| 187 |
+
box-shadow:
|
| 188 |
+
0 10px 30px rgba(243, 165, 76, 0.5),
|
| 189 |
+
0 0 0 4px rgba(243, 165, 76, 0.2);
|
| 190 |
+
animation: iconFloat 4s ease-in-out infinite;
|
| 191 |
+
}
|
| 192 |
|
| 193 |
+
@keyframes iconFloat {
|
| 194 |
+
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
| 195 |
+
50% { transform: translateY(-10px) rotate(5deg); }
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
.hero-title {
|
| 199 |
+
margin: 0;
|
| 200 |
+
font-size: clamp(36px, 4vw, 48px);
|
| 201 |
+
line-height: 1.1;
|
| 202 |
+
font-weight: 700;
|
| 203 |
+
letter-spacing: 0.5px;
|
| 204 |
+
display: flex;
|
| 205 |
+
align-items: center;
|
| 206 |
+
gap: 16px;
|
| 207 |
+
flex-wrap: wrap;
|
| 208 |
+
justify-content: center;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
.hero-title .fa-brain {
|
| 212 |
+
color: #f3a54c;
|
| 213 |
+
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.5));
|
| 214 |
+
animation: brainPulse 2s ease-in-out infinite;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
@keyframes brainPulse {
|
| 218 |
+
0%, 100% { transform: scale(1); }
|
| 219 |
+
50% { transform: scale(1.1); }
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
.hero-sub {
|
| 223 |
+
margin: 0;
|
| 224 |
+
max-width: 680px;
|
| 225 |
+
font-size: clamp(1rem, 1.1vw, 1.1rem);
|
| 226 |
+
line-height: 1.6;
|
| 227 |
+
opacity: 0.9;
|
| 228 |
+
font-weight: 400;
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
/* Enhanced quiz container */
|
| 232 |
+
.quiz {
|
| 233 |
+
position: absolute;
|
| 234 |
+
z-index: 2;
|
| 235 |
+
width: 80%;
|
| 236 |
+
margin: 0 auto 40px;
|
| 237 |
+
background: rgba(255, 255, 255, 0.88);
|
| 238 |
+
-webkit-backdrop-filter: blur(15px);
|
| 239 |
+
backdrop-filter: blur(15px);
|
| 240 |
+
border-radius: 20px;
|
| 241 |
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25), 0 8px 24px rgba(0, 0, 0, 0.15);
|
| 242 |
+
padding: 30px;
|
| 243 |
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
| 244 |
+
top: 50%;
|
| 245 |
+
left: 50%;
|
| 246 |
+
transform: translate(-50%, -50%);
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
/* Enhanced cards with brain-inspired design */
|
| 250 |
+
.question-card, .result, .panel {
|
| 251 |
+
background: rgba(255, 255, 255, 0.95);
|
| 252 |
+
border-radius: 16px;
|
| 253 |
+
box-shadow:
|
| 254 |
+
0 8px 24px rgba(0, 0, 0, 0.08),
|
| 255 |
+
0 2px 8px rgba(0, 0, 0, 0.04);
|
| 256 |
+
padding: 28px;
|
| 257 |
+
margin-bottom: 24px;
|
| 258 |
+
border: 1px solid rgba(243, 165, 76, 0.1);
|
| 259 |
+
animation: cardAppear 0.5s ease-out;
|
| 260 |
+
position: relative;
|
| 261 |
+
overflow: hidden;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
.question-card::before, .result::before, .panel::before {
|
| 265 |
+
content: "";
|
| 266 |
+
position: absolute;
|
| 267 |
+
top: 0;
|
| 268 |
+
left: 0;
|
| 269 |
+
right: 0;
|
| 270 |
+
height: 4px;
|
| 271 |
+
background: linear-gradient(90deg, #f3a54c, #e67e22, #f3a54c);
|
| 272 |
+
background-size: 200% 100%;
|
| 273 |
+
animation: shimmer 3s ease-in-out infinite;
|
| 274 |
+
}
|
| 275 |
|
| 276 |
+
@keyframes cardAppear {
|
| 277 |
+
from { opacity: 0; transform: translateY(20px) scale(0.98); }
|
| 278 |
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
| 279 |
}
|
| 280 |
|
| 281 |
+
@keyframes shimmer {
|
| 282 |
+
0% { background-position: -200% 0; }
|
| 283 |
+
100% { background-position: 200% 0; }
|
| 284 |
}
|
| 285 |
|
| 286 |
+
/* Question styling */
|
| 287 |
+
.q-meta {
|
| 288 |
display: flex;
|
| 289 |
justify-content: space-between;
|
| 290 |
+
align-items: center;
|
| 291 |
+
margin-bottom: 16px;
|
| 292 |
+
color: #666;
|
| 293 |
+
font-size: 0.9rem;
|
| 294 |
}
|
| 295 |
|
| 296 |
+
.q-count {
|
| 297 |
+
font-weight: 600;
|
| 298 |
+
color: #2C3E50;
|
| 299 |
+
background: rgba(243, 165, 76, 0.1);
|
| 300 |
+
padding: 6px 12px;
|
| 301 |
+
border-radius: 20px;
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
.q-role .badge {
|
| 305 |
+
display: inline-flex;
|
| 306 |
+
align-items: center;
|
| 307 |
+
gap: 6px;
|
| 308 |
+
padding: 6px 14px;
|
| 309 |
+
background: linear-gradient(135deg, rgba(243, 165, 76, 0.15), rgba(230, 126, 34, 0.1));
|
| 310 |
+
color: #e67e22;
|
| 311 |
+
border: 1px solid rgba(230, 126, 34, 0.3);
|
| 312 |
+
border-radius: 20px;
|
| 313 |
+
font-size: 0.85rem;
|
| 314 |
+
font-weight: 500;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
.q-text {
|
| 318 |
+
font-size: 1.7rem;
|
| 319 |
+
font-weight: 600;
|
| 320 |
+
margin: 20px 0;
|
| 321 |
+
line-height: 1.4;
|
| 322 |
+
color: #2C3E50;
|
| 323 |
+
text-align: center;
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
.q-brain {
|
| 327 |
+
color: #f3a54c;
|
| 328 |
+
margin-right: 10px;
|
| 329 |
+
animation: thoughtBubble 2s ease-in-out infinite;
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
@keyframes thoughtBubble {
|
| 333 |
+
0%, 100% { transform: scale(1) rotate(0deg); }
|
| 334 |
+
25% { transform: scale(1.1) rotate(5deg); }
|
| 335 |
+
75% { transform: scale(1.1) rotate(-5deg); }
|
| 336 |
}
|
| 337 |
|
| 338 |
+
/* Enhanced options with brain-inspired colors */
|
| 339 |
.options {
|
| 340 |
display: grid;
|
| 341 |
+
grid-template-columns: repeat(2, 1fr);
|
| 342 |
+
gap: 16px;
|
| 343 |
+
margin: 24px 0;
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
.option {
|
| 347 |
+
position: relative;
|
| 348 |
+
transition: transform 0.3s ease;
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
.option:hover {
|
| 352 |
+
transform: translateY(-3px);
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
.option input[type=radio] {
|
| 356 |
+
position: absolute;
|
| 357 |
+
opacity: 0;
|
| 358 |
+
width: 100%;
|
| 359 |
+
height: 100%;
|
| 360 |
+
cursor: pointer;
|
| 361 |
+
z-index: 2;
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
.opt-text {
|
| 365 |
+
display: block;
|
| 366 |
+
padding: 20px;
|
| 367 |
+
border: 2px solid #e2e7ee;
|
| 368 |
+
border-radius: 12px;
|
| 369 |
+
background: #fbfcfe;
|
| 370 |
+
color: #2C3E50;
|
| 371 |
+
font-weight: 500;
|
| 372 |
+
transition: all 0.3s ease;
|
| 373 |
+
position: relative;
|
| 374 |
+
overflow: hidden;
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
.opt-text::before {
|
| 378 |
+
content: "";
|
| 379 |
+
position: absolute;
|
| 380 |
+
top: 0;
|
| 381 |
+
left: -100%;
|
| 382 |
+
width: 100%;
|
| 383 |
+
height: 100%;
|
| 384 |
+
background: linear-gradient(90deg, transparent, rgba(243, 165, 76, 0.1), transparent);
|
| 385 |
+
transition: left 0.5s ease;
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
.option:hover .opt-text::before {
|
| 389 |
+
left: 100%;
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
.option input:checked + .opt-text {
|
| 393 |
+
border-color: #f3a54c;
|
| 394 |
+
background: rgba(243, 165, 76, 0.05);
|
| 395 |
+
box-shadow: 0 4px 12px rgba(243, 165, 76, 0.2);
|
| 396 |
+
transform: scale(1.02);
|
| 397 |
}
|
| 398 |
|
| 399 |
+
/* Color chips for options */
|
| 400 |
+
.color-chip {
|
| 401 |
+
width: 20px;
|
| 402 |
+
height: 20px;
|
| 403 |
+
border-radius: 6px;
|
| 404 |
+
margin-right: 10px;
|
| 405 |
+
display: inline-block;
|
| 406 |
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
| 407 |
+
vertical-align: middle;
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
/* Enhanced buttons */
|
| 411 |
+
.actions {
|
| 412 |
+
margin-top: 24px;
|
| 413 |
display: flex;
|
| 414 |
+
gap: 14px;
|
| 415 |
+
flex-wrap: wrap;
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
button {
|
| 419 |
+
padding: 14px 24px;
|
| 420 |
+
font-size: 1rem;
|
| 421 |
+
font-weight: 600;
|
| 422 |
+
border: none;
|
| 423 |
+
border-radius: 12px;
|
| 424 |
+
cursor: pointer;
|
| 425 |
+
transition: all 0.3s ease;
|
| 426 |
+
display: inline-flex;
|
| 427 |
align-items: center;
|
| 428 |
+
gap: 8px;
|
| 429 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
| 430 |
+
position: relative;
|
| 431 |
+
overflow: hidden;
|
| 432 |
}
|
| 433 |
|
| 434 |
+
button::before {
|
| 435 |
+
content: "";
|
| 436 |
+
position: absolute;
|
| 437 |
+
top: 0;
|
| 438 |
+
left: -100%;
|
| 439 |
+
width: 100%;
|
| 440 |
+
height: 100%;
|
| 441 |
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
| 442 |
+
transition: left 0.5s ease;
|
| 443 |
+
}
|
| 444 |
|
| 445 |
+
button:hover::before {
|
| 446 |
+
left: 100%;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
}
|
| 448 |
|
| 449 |
+
button:active {
|
| 450 |
+
transform: translateY(2px);
|
| 451 |
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
| 452 |
+
}
|
| 453 |
|
| 454 |
+
button.primary, button.submit-btn {
|
| 455 |
+
background: linear-gradient(135deg, #f3a54c, #e67e22);
|
| 456 |
+
color: white;
|
| 457 |
+
flex: 1;
|
| 458 |
+
}
|
| 459 |
|
| 460 |
+
button.primary:hover, button.submit-btn:hover {
|
| 461 |
+
background: linear-gradient(135deg, #ffb347, #e67e22);
|
| 462 |
+
transform: translateY(-2px);
|
| 463 |
+
}
|
| 464 |
|
| 465 |
+
button[disabled] {
|
| 466 |
+
background: #cfd8e3;
|
| 467 |
+
color: #6b7280;
|
| 468 |
+
cursor: not-allowed;
|
| 469 |
+
box-shadow: none;
|
| 470 |
+
transform: none;
|
| 471 |
+
}
|
| 472 |
|
| 473 |
+
button[disabled]::before {
|
| 474 |
+
display: none;
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
/* Enhanced progress bars */
|
| 478 |
+
.progress {
|
| 479 |
+
margin: 24px 0;
|
| 480 |
}
|
| 481 |
|
| 482 |
.bar {
|
| 483 |
display: grid;
|
| 484 |
+
grid-template-columns: 140px 1fr 60px;
|
| 485 |
align-items: center;
|
| 486 |
+
gap: 14px;
|
| 487 |
+
margin: 14px 0;
|
| 488 |
}
|
| 489 |
|
| 490 |
.bar-label {
|
| 491 |
+
display: inline-flex;
|
| 492 |
+
align-items: center;
|
| 493 |
+
gap: 10px;
|
| 494 |
font-weight: 600;
|
| 495 |
+
color: #2C3E50;
|
| 496 |
+
text-transform: capitalize;
|
| 497 |
}
|
| 498 |
|
| 499 |
+
/* Strengthened specificity for dimension colors */
|
| 500 |
+
.progress .bar .bar-label.dim-blue, .result .bar .bar-label.dim-blue { color:#2980b9 !important; }
|
| 501 |
+
.progress .bar .bar-label.dim-green, .result .bar .bar-label.dim-green { color:#1d7f44 !important; }
|
| 502 |
+
.progress .bar .bar-label.dim-red, .result .bar .bar-label.dim-red { color:#b93528 !important; }
|
| 503 |
+
.progress .bar .bar-label.dim-yellow, .result .bar .bar-label.dim-yellow { color:#b56a00 !important; }
|
|
|
|
| 504 |
|
| 505 |
+
.dot {
|
| 506 |
+
width: 12px;
|
| 507 |
+
height: 12px;
|
| 508 |
+
border-radius: 50%;
|
| 509 |
+
display: inline-block;
|
| 510 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
| 511 |
}
|
| 512 |
|
| 513 |
+
.dot.blue { background:#3498db; }
|
| 514 |
+
.dot.green { background:#2ecc71; }
|
| 515 |
+
.dot.red { background:#e74c3c; }
|
| 516 |
+
.dot.yellow { background:#f1c40f; }
|
|
|
|
|
|
|
|
|
|
| 517 |
|
| 518 |
+
/* Add missing bar-fill color classes */
|
| 519 |
+
.bar-fill.blue { background:linear-gradient(90deg,#5dade2,#2e86c1); }
|
| 520 |
+
.bar-fill.green { background:linear-gradient(90deg,#58d68d,#239b56); }
|
| 521 |
+
.bar-fill.red { background:linear-gradient(90deg,#ec7063,#cb4335); }
|
| 522 |
+
.bar-fill.yellow { background:linear-gradient(90deg,#f8c471,#d68910); }
|
|
|
|
|
|
|
| 523 |
|
| 524 |
+
/* Fallback if gradient not supported */
|
| 525 |
+
@supports not (background:linear-gradient(red,blue)) {
|
| 526 |
+
.bar-fill.blue { background:#2e86c1; }
|
| 527 |
+
.bar-fill.green { background:#239b56; }
|
| 528 |
+
.bar-fill.red { background:#cb4335; }
|
| 529 |
+
.bar-fill.yellow { background:#d68910; }
|
| 530 |
}
|
|
|
|
| 531 |
|
| 532 |
+
/* Enhanced results section */
|
| 533 |
+
.result h2 {
|
| 534 |
+
color: #2C3E50;
|
| 535 |
+
text-align: center;
|
| 536 |
+
margin-bottom: 24px;
|
| 537 |
+
font-size: 2rem;
|
| 538 |
+
}
|
| 539 |
|
| 540 |
+
.result-sub {
|
| 541 |
+
text-align: center;
|
| 542 |
+
margin-bottom: 20px;
|
| 543 |
+
font-size: 1.1rem;
|
| 544 |
+
color: #666;
|
| 545 |
}
|
| 546 |
|
| 547 |
+
.pill {
|
| 548 |
+
display: inline-flex;
|
|
|
|
| 549 |
align-items: center;
|
| 550 |
+
gap: 6px;
|
| 551 |
+
padding: 8px 16px;
|
| 552 |
+
border-radius: 20px;
|
| 553 |
+
font-weight: 600;
|
| 554 |
+
text-transform: capitalize;
|
| 555 |
}
|
| 556 |
|
| 557 |
+
.pill-blue { background: rgba(52, 152, 219, 0.15); color: #2980b9; }
|
| 558 |
+
.pill-green { background: rgba(46, 204, 113, 0.15); color: #27ae60; }
|
| 559 |
+
.pill-red { background: rgba(231, 76, 60, 0.15); color: #c0392b; }
|
| 560 |
+
.pill-yellow { background: rgba(241, 196, 15, 0.15); color: #d35400; }
|
| 561 |
|
| 562 |
+
.dom-desc {
|
| 563 |
+
text-align: center;
|
| 564 |
+
font-size: 1.1rem;
|
| 565 |
+
font-style: italic;
|
| 566 |
+
color: #7f8c8d;
|
| 567 |
+
margin-bottom: 30px;
|
| 568 |
+
padding: 16px;
|
| 569 |
+
background: rgba(243, 165, 76, 0.05);
|
| 570 |
+
border-radius: 12px;
|
| 571 |
+
border-left: 4px solid #f3a54c;
|
| 572 |
}
|
| 573 |
|
| 574 |
+
/* Loader overlay */
|
| 575 |
.loader-overlay {
|
| 576 |
position: fixed;
|
| 577 |
inset: 0;
|
| 578 |
+
background: rgba(12, 18, 28, 0.7);
|
| 579 |
display: grid;
|
| 580 |
place-items: center;
|
| 581 |
+
z-index: 9999;
|
| 582 |
+
backdrop-filter: blur(5px);
|
| 583 |
}
|
| 584 |
|
| 585 |
+
.loader-container { text-align:center; }
|
| 586 |
+
.loader-gif { width: 50%; height: 50%; }
|
| 587 |
+
/* Keep spinner styles in case referenced elsewhere */
|
| 588 |
+
.loader { display:none; }
|
| 589 |
+
.spinner { display:none; }
|
| 590 |
|
| 591 |
+
/* Form elements in panel */
|
| 592 |
+
.panel {
|
| 593 |
+
text-align: center;
|
|
|
|
|
|
|
| 594 |
}
|
| 595 |
|
| 596 |
+
.panel-title {
|
| 597 |
+
color: #2C3E50;
|
| 598 |
+
margin-bottom: 16px;
|
| 599 |
+
font-size: 1.8rem;
|
| 600 |
}
|
| 601 |
|
| 602 |
+
.panel-hint {
|
| 603 |
+
color: #666;
|
| 604 |
+
margin-bottom: 28px;
|
| 605 |
+
line-height: 1.5;
|
| 606 |
}
|
| 607 |
|
| 608 |
+
.row {
|
| 609 |
display: flex;
|
| 610 |
align-items: center;
|
| 611 |
+
justify-content: space-between;
|
| 612 |
+
margin-bottom: 18px;
|
| 613 |
+
text-align: left;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
}
|
| 615 |
|
| 616 |
+
.row label {
|
| 617 |
+
font-weight: 600;
|
| 618 |
+
color: #2C3E50;
|
| 619 |
+
min-width: 140px;
|
|
|
|
|
|
|
| 620 |
}
|
| 621 |
|
| 622 |
+
.row input, .row select {
|
| 623 |
+
flex: 1;
|
| 624 |
+
padding: 12px 16px;
|
| 625 |
+
border: 2px solid #e2e7ee;
|
| 626 |
+
border-radius: 10px;
|
| 627 |
+
font-size: 1rem;
|
| 628 |
+
transition: border-color 0.3s ease;
|
| 629 |
+
background: white;
|
| 630 |
}
|
| 631 |
|
| 632 |
+
.row input:focus, .row select:focus {
|
| 633 |
+
outline: none;
|
| 634 |
+
border-color: #f3a54c;
|
| 635 |
+
box-shadow: 0 0 0 3px rgba(243, 165, 76, 0.2);
|
| 636 |
}
|
| 637 |
|
| 638 |
+
/* Watermark */
|
| 639 |
+
/*:host::after {
|
| 640 |
+
content: "NeuroMatch";
|
| 641 |
+
position: fixed;
|
| 642 |
+
right: 20px;
|
| 643 |
+
bottom: 20px;
|
| 644 |
+
z-index: 2;
|
| 645 |
+
pointer-events: none;
|
| 646 |
+
user-select: none;
|
| 647 |
+
font-weight: 700;
|
| 648 |
+
font-size: 14px;
|
| 649 |
+
letter-spacing: 1px;
|
| 650 |
+
color: rgba(255, 255, 255, 0.7);
|
| 651 |
+
background: rgba(30, 42, 60, 0.7);
|
| 652 |
+
backdrop-filter: blur(4px);
|
| 653 |
+
padding: 8px 14px;
|
| 654 |
+
border-radius: 10px;
|
| 655 |
+
border: 1px solid rgba(243, 165, 76, 0.3);
|
| 656 |
+
}*/
|
| 657 |
+
|
| 658 |
+
/* Responsive design */
|
| 659 |
+
@media (max-width: 900px) {
|
| 660 |
+
.quiz-hero {
|
| 661 |
+
padding: 40px 28px;
|
| 662 |
+
margin-top: 110px;
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
.hero-icon {
|
| 666 |
+
width: 80px;
|
| 667 |
+
height: 80px;
|
| 668 |
+
font-size: 38px;
|
| 669 |
+
}
|
| 670 |
+
|
| 671 |
+
.quiz {
|
| 672 |
+
margin: 0 16px 30px;
|
| 673 |
+
padding: 24px;
|
| 674 |
+
}
|
| 675 |
+
|
| 676 |
+
.options {
|
| 677 |
+
grid-template-columns: 1fr;
|
| 678 |
+
}
|
| 679 |
}
|
| 680 |
|
| 681 |
+
@media (max-width: 600px) {
|
| 682 |
+
.quiz-topbar {
|
| 683 |
+
padding: 12px 20px;
|
| 684 |
+
}
|
| 685 |
+
|
| 686 |
+
.brand__logo-img {
|
| 687 |
+
width: 44px;
|
| 688 |
+
height: 44px;
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
.brand__name {
|
| 692 |
+
font-size: 22px;
|
| 693 |
+
}
|
| 694 |
+
|
| 695 |
+
.quiz-hero {
|
| 696 |
+
padding: 30px 20px;
|
| 697 |
+
margin-top: 90px;
|
| 698 |
+
}
|
| 699 |
+
|
| 700 |
+
.hero-title {
|
| 701 |
+
font-size: 28px;
|
| 702 |
+
}
|
| 703 |
+
|
| 704 |
+
.q-text {
|
| 705 |
+
font-size: 1.4rem;
|
| 706 |
+
}
|
| 707 |
+
|
| 708 |
+
.bar {
|
| 709 |
+
grid-template-columns: 1fr;
|
| 710 |
+
gap: 8px;
|
| 711 |
+
text-align: center;
|
| 712 |
+
}
|
| 713 |
+
|
| 714 |
+
.bar-val {
|
| 715 |
+
text-align: center;
|
| 716 |
+
}
|
| 717 |
+
|
| 718 |
+
.row {
|
| 719 |
+
flex-direction: column;
|
| 720 |
+
align-items: flex-start;
|
| 721 |
+
gap: 8px;
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
.row input, .row select {
|
| 725 |
+
width: 100%;
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
.actions {
|
| 729 |
+
flex-direction: column;
|
| 730 |
+
}
|
| 731 |
}
|
| 732 |
+
/*[file content end]*/
|
src/app/llm-quiz/llm-quiz.component.html
CHANGED
|
@@ -1,142 +1,138 @@
|
|
| 1 |
-
<!--
|
| 2 |
-
<
|
| 3 |
-
<div class="row">
|
| 4 |
-
<label>User ID</label>
|
| 5 |
-
<input type="text" [(ngModel)]="userId" placeholder="e.g. 1" />
|
| 6 |
-
</div>
|
| 7 |
-
|
| 8 |
-
<div class="row">
|
| 9 |
-
<label>Role</label>
|
| 10 |
-
<select [(ngModel)]="role">
|
| 11 |
-
<option value="interview">interview</option>
|
| 12 |
-
<option value="marriage">marriage</option>
|
| 13 |
-
<option value="partnership">partnership</option>
|
| 14 |
-
<option value="team">team</option>
|
| 15 |
-
<option value="general">general</option>
|
| 16 |
-
<option value="assistant">assistant</option>
|
| 17 |
-
<option value="ceo">ceo</option>
|
| 18 |
-
</select>
|
| 19 |
-
</div>
|
| 20 |
-
|
| 21 |
-
<div class="row">
|
| 22 |
-
<label>No. of questions</label>
|
| 23 |
-
<input type="number" [(ngModel)]="nQuestions" min="1" max="50" />
|
| 24 |
-
</div>
|
| 25 |
-
|
| 26 |
-
<div class="row">
|
| 27 |
-
<label>Batch size</label>
|
| 28 |
-
<input type="number" [(ngModel)]="batchSize" min="1" max="20" />
|
| 29 |
-
</div>
|
| 30 |
-
|
| 31 |
-
<div class="actions">
|
| 32 |
-
<button type="button" (click)="start()" [disabled]="loading">Start</button>
|
| 33 |
-
<button type="button" (click)="restart()" [disabled]="loading">Reset</button>
|
| 34 |
-
</div>
|
| 35 |
-
|
| 36 |
-
<p class="error" *ngIf="errorMsg">{{ errorMsg }}</p>
|
| 37 |
-
</section>-->
|
| 38 |
|
| 39 |
-
<!-- Loader -->
|
| 40 |
<div class="loader-overlay" *ngIf="loading">
|
| 41 |
-
<div class="loader">
|
|
|
|
|
|
|
| 42 |
</div>
|
| 43 |
|
| 44 |
<!-- Quiz area -->
|
| 45 |
<section class="quiz" *ngIf="!loading">
|
| 46 |
-
|
| 47 |
-
<!-- When there is an active question -->
|
| 48 |
<div class="question-card" *ngIf="!isDone && question">
|
| 49 |
<div class="q-meta">
|
| 50 |
-
<div class="q-count">
|
| 51 |
-
<div class="q-role">
|
|
|
|
|
|
|
| 52 |
</div>
|
| 53 |
|
| 54 |
-
<h2 class="q-text">{{ question }}</h2>
|
| 55 |
|
| 56 |
<form (ngSubmit)="submitAnswer()">
|
| 57 |
<div class="options">
|
| 58 |
<label class="option" *ngFor="let opt of options; let i = index">
|
| 59 |
-
<input type="radio"
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
<span class="opt-text">{{ opt.text }}</span>
|
| 65 |
-
<!--<small class="opt-color">({{ opt.color }})</small>-->
|
| 66 |
</label>
|
| 67 |
</div>
|
| 68 |
-
|
| 69 |
<div class="actions">
|
| 70 |
-
<button type="submit" [disabled]="!selectedColor || loading">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
</div>
|
| 72 |
</form>
|
| 73 |
|
| 74 |
-
<
|
| 75 |
-
|
| 76 |
-
<
|
| 77 |
-
|
| 78 |
-
<span>Blue</span>
|
| 79 |
-
<div class="bar-track"><div class="bar-fill" [style.width.%]="getInProgressMixValue('blue')"></div></div>
|
| 80 |
-
<div class="bar-val">{{ asPercent(getInProgressMixValue('blue')) }}%</div>
|
| 81 |
-
</div>
|
| 82 |
-
|
| 83 |
-
<div class="bar">
|
| 84 |
-
<span>Green</span>
|
| 85 |
-
<div class="bar-track"><div class="bar-fill" [style.width.%]="getInProgressMixValue('green')"></div></div>
|
| 86 |
-
<div class="bar-val">{{ asPercent(getInProgressMixValue('green')) }}%</div>
|
| 87 |
-
</div>
|
| 88 |
-
|
| 89 |
-
<div class="bar">
|
| 90 |
-
<span>Red</span>
|
| 91 |
-
<div class="bar-track"><div class="bar-fill" [style.width.%]="getInProgressMixValue('red')"></div></div>
|
| 92 |
-
<div class="bar-val">{{ asPercent(getInProgressMixValue('red')) }}%</div>
|
| 93 |
-
</div>
|
| 94 |
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
</div>
|
| 100 |
-
</div>
|
| 101 |
</div>
|
| 102 |
|
| 103 |
<!-- Final result -->
|
| 104 |
-
<div>
|
| 105 |
-
|
| 106 |
-
<
|
| 107 |
-
|
| 108 |
-
<
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
|
| 114 |
-
<div class="
|
| 115 |
-
<
|
| 116 |
-
|
| 117 |
-
<
|
| 118 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
<!--</div>
|
| 125 |
|
| 126 |
-
<div class="
|
| 127 |
-
<
|
| 128 |
-
<
|
| 129 |
-
|
| 130 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
|
| 132 |
<div class="actions">
|
| 133 |
-
<button type="button" (click)="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
</div>
|
| 135 |
-
</div>-->
|
| 136 |
|
| 137 |
-
|
| 138 |
-
<div class="empty" *ngIf="!isDone && !question && !errorMsg">
|
| 139 |
-
<p>Press <strong>Start</strong> to begin the quiz.</p>
|
| 140 |
</div>
|
| 141 |
</section>
|
| 142 |
-
|
|
|
|
| 1 |
+
<!-- Removed local brand header (global top navigation present) -->
|
| 2 |
+
<div class="brain-bg" #brainBg></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
+
<!-- Loader (updated to match quiz component) -->
|
| 5 |
<div class="loader-overlay" *ngIf="loading">
|
| 6 |
+
<div class="loader-container">
|
| 7 |
+
<img src="assets/Loader.gif" alt="Loading..." class="loader-gif" />
|
| 8 |
+
</div>
|
| 9 |
</div>
|
| 10 |
|
| 11 |
<!-- Quiz area -->
|
| 12 |
<section class="quiz" *ngIf="!loading">
|
| 13 |
+
<!-- Active question area -->
|
|
|
|
| 14 |
<div class="question-card" *ngIf="!isDone && question">
|
| 15 |
<div class="q-meta">
|
| 16 |
+
<div class="q-count">Neural Query {{ index }} / {{ total }}</div>
|
| 17 |
+
<div class="q-role">
|
| 18 |
+
<span class="badge"><i [class]="quizIcon"></i> {{ role | titlecase }} Profile</span>
|
| 19 |
+
</div>
|
| 20 |
</div>
|
| 21 |
|
| 22 |
+
<h2 class="q-text fade-in"><i class="fa-solid fa-brain q-brain"></i> {{ question }}</h2>
|
| 23 |
|
| 24 |
<form (ngSubmit)="submitAnswer()">
|
| 25 |
<div class="options">
|
| 26 |
<label class="option" *ngFor="let opt of options; let i = index">
|
| 27 |
+
<input type="radio" name="answer" [value]="opt.color" [(ngModel)]="selectedColor" required />
|
| 28 |
+
<span class="opt-text">
|
| 29 |
+
<span class="color-chip {{opt.color}}"></span>
|
| 30 |
+
{{ opt.text }}
|
| 31 |
+
</span>
|
|
|
|
|
|
|
| 32 |
</label>
|
| 33 |
</div>
|
|
|
|
| 34 |
<div class="actions">
|
| 35 |
+
<button type="submit" [disabled]="!selectedColor || loading" class="submit-btn">
|
| 36 |
+
<i class="fa-solid fa-brain"></i> Process Response
|
| 37 |
+
</button>
|
| 38 |
+
<button type="button" (click)="restart()" [disabled]="loading">
|
| 39 |
+
<i class="fa-solid fa-rotate-left"></i> Reset Assessment
|
| 40 |
+
</button>
|
| 41 |
</div>
|
| 42 |
</form>
|
| 43 |
|
| 44 |
+
<div class="inline-progress" *ngIf="total">
|
| 45 |
+
<div class="ip-track"><div class="ip-fill" [style.width.%]="progressPercent"></div></div>
|
| 46 |
+
<small class="ip-label">Neural Mapping: {{ progressPercent }}% Complete</small>
|
| 47 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
+
<div class="progress" *ngIf="inProgressMix">
|
| 50 |
+
<h3><i class="fa-solid fa-network-wired"></i> Current Neural Signature</h3>
|
| 51 |
+
<div class="bar" *ngFor="let color of ['blue', 'green', 'red', 'yellow']">
|
| 52 |
+
<span class="bar-label" [ngClass]="'dim-' + color">
|
| 53 |
+
<span class="dot" [ngClass]="color"></span>
|
| 54 |
+
{{ getDimensionName(color) }}
|
| 55 |
+
</span>
|
| 56 |
+
<div class="bar-track">
|
| 57 |
+
<div class="bar-fill {{color}}" [style.width.%]="getInProgressMixValue(color)"></div>
|
| 58 |
+
</div>
|
| 59 |
+
<div class="bar-val">{{ asPercent(getInProgressMixValue(color)) }}%</div>
|
| 60 |
</div>
|
| 61 |
+
</div>
|
| 62 |
</div>
|
| 63 |
|
| 64 |
<!-- Final result -->
|
| 65 |
+
<div class="result" *ngIf="isDone && finalMix">
|
| 66 |
+
<h2 class="fade-in"><i class="fa-solid fa-brain"></i> Neural Profile Complete</h2>
|
| 67 |
+
<p class="result-sub" *ngIf="dominantColor">
|
| 68 |
+
Primary Cognitive Pattern: <span class="pill pill-{{dominantColor}}"><i [class]="quizIcon"></i> {{ getDimensionName(dominantColor) }}</span>
|
| 69 |
+
</p>
|
| 70 |
+
<p class="dom-desc" *ngIf="dominantDescription">{{ dominantDescription }}</p>
|
| 71 |
+
|
| 72 |
+
<div class="bar" *ngFor="let color of ['blue', 'green', 'red', 'yellow']">
|
| 73 |
+
<span class="bar-label" [ngClass]="'dim-' + color">
|
| 74 |
+
<span class="dot" [ngClass]="color"></span>
|
| 75 |
+
{{ getDimensionName(color) }}
|
| 76 |
+
</span>
|
| 77 |
+
<div class="bar-track">
|
| 78 |
+
<div class="bar-fill {{color}}" [style.width.%]="getFinalMixValue(color)"></div>
|
| 79 |
+
</div>
|
| 80 |
+
<div class="bar-val">{{ asPercent(getFinalMixValue(color)) }}%</div>
|
| 81 |
+
</div>
|
| 82 |
|
| 83 |
+
<div class="actions">
|
| 84 |
+
<button type="button" (click)="restart()" class="primary">
|
| 85 |
+
<i class="fa-solid fa-arrow-rotate-right"></i> New Assessment
|
| 86 |
+
</button>
|
| 87 |
+
<button type="button" (click)="goHome()">
|
| 88 |
+
<i class="fa-solid fa-house"></i> Return Home
|
| 89 |
+
</button>
|
| 90 |
+
<a [routerLink]="['/matchinglist', userId]"
|
| 91 |
+
class="btn btn-secondary">
|
| 92 |
+
Matching
|
| 93 |
+
</a>
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
|
| 97 |
+
<!-- Configuration panel -->
|
| 98 |
+
<div class="panel" *ngIf="!isDone && !question && !errorMsg">
|
| 99 |
+
<h2 class="panel-title"><i class="fa-solid fa-brain"></i> Configure Neural Assessment</h2>
|
| 100 |
+
<p class="panel-hint">Set up your cognitive profile assessment. Your existing profile will personalize the questions and results.</p>
|
|
|
|
| 101 |
|
| 102 |
+
<div class="row">
|
| 103 |
+
<label for="userId">ID</label>
|
| 104 |
+
<input id="userId" type="text" [(ngModel)]="userId" placeholder="Enter your unique identifier" />
|
| 105 |
+
</div>
|
| 106 |
+
<div class="row">
|
| 107 |
+
<label for="role">Assessment Context</label>
|
| 108 |
+
<select id="role" [(ngModel)]="role">
|
| 109 |
+
<option value="interview">Career Interview</option>
|
| 110 |
+
<option value="marriage">Relationship Compatibility</option>
|
| 111 |
+
<option value="partnership">Business Partnership</option>
|
| 112 |
+
<option value="team">Team Dynamics</option>
|
| 113 |
+
<option value="general">General Personality</option>
|
| 114 |
+
<option value="assistant">AI Assistant Matching</option>
|
| 115 |
+
<option value="ceo">Leadership Profile</option>
|
| 116 |
+
</select>
|
| 117 |
+
</div>
|
| 118 |
+
<div class="row">
|
| 119 |
+
<label for="nQuestions">Neural Queries</label>
|
| 120 |
+
<input id="nQuestions" type="number" [(ngModel)]="nQuestions" min="5" max="50" />
|
| 121 |
+
</div>
|
| 122 |
+
<div class="row">
|
| 123 |
+
<label for="batchSize">Processing Batch</label>
|
| 124 |
+
<input id="batchSize" type="number" [(ngModel)]="batchSize" min="1" max="20" />
|
| 125 |
+
</div>
|
| 126 |
|
| 127 |
<div class="actions">
|
| 128 |
+
<button type="button" (click)="start()" [disabled]="loading" class="primary">
|
| 129 |
+
<i class="fa-solid fa-play"></i> Begin Neural Mapping
|
| 130 |
+
</button>
|
| 131 |
+
<button type="button" (click)="restart()" [disabled]="loading">
|
| 132 |
+
<i class="fa-solid fa-rotate-left"></i> Reset Configuration
|
| 133 |
+
</button>
|
| 134 |
</div>
|
|
|
|
| 135 |
|
| 136 |
+
<p class="error" *ngIf="errorMsg">{{ errorMsg }}</p>
|
|
|
|
|
|
|
| 137 |
</div>
|
| 138 |
</section>
|
|
|
src/app/llm-quiz/llm-quiz.component.ts
CHANGED
|
@@ -1,244 +1,207 @@
|
|
| 1 |
-
import { Component, OnInit } from '@angular/core';
|
| 2 |
import { FormsModule } from '@angular/forms';
|
| 3 |
import { CommonModule } from '@angular/common';
|
| 4 |
-
import { LlmQaService, Role, ColorKey, StartResponse, NextResponse } from '../services/llm-qa.service';
|
| 5 |
import { ActivatedRoute, Router } from '@angular/router';
|
| 6 |
-
|
| 7 |
type Mix = Record<ColorKey, number>;
|
| 8 |
|
| 9 |
@Component({
|
| 10 |
selector: 'app-llm-quiz',
|
| 11 |
standalone: true,
|
| 12 |
-
imports: [CommonModule, FormsModule],
|
| 13 |
templateUrl: './llm-quiz.component.html',
|
| 14 |
styleUrls: ['./llm-quiz.component.css'],
|
| 15 |
})
|
| 16 |
-
export class LlmQuizComponent implements OnInit {
|
|
|
|
|
|
|
| 17 |
// --- Config bound to the UI ---
|
| 18 |
role: Role = 'marriage';
|
| 19 |
-
nQuestions =
|
| 20 |
-
batchSize =
|
| 21 |
-
|
| 22 |
-
/** Provide user id. Will auto-read from localStorage('user_id') if available. */
|
| 23 |
userId = '';
|
| 24 |
|
| 25 |
// --- Runtime state ---
|
| 26 |
sessionId = '';
|
| 27 |
-
index = 0;
|
| 28 |
-
total = 0;
|
| 29 |
-
|
| 30 |
question = '';
|
| 31 |
-
options:
|
| 32 |
selectedColor: ColorKey | '' = '';
|
| 33 |
|
| 34 |
loading = false;
|
| 35 |
errorMsg = '';
|
| 36 |
isDone = false;
|
| 37 |
|
| 38 |
-
inProgressMix: Mix | null = null; // live progress
|
| 39 |
-
finalMix: Mix | null = null;
|
| 40 |
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
-
|
| 45 |
-
private api: LlmQaService,
|
| 46 |
-
private route: ActivatedRoute,
|
| 47 |
-
private router: Router
|
| 48 |
-
) { }
|
| 49 |
|
| 50 |
-
ngOnInit(): void {
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
|
| 56 |
-
|
| 57 |
-
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
}
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
// ---------- Helpers ----------
|
| 66 |
-
asPercent(v: number | undefined | null): number {
|
| 67 |
-
const n = Number(v);
|
| 68 |
-
return Number.isFinite(n) ? Math.round(n) : 0;
|
| 69 |
}
|
| 70 |
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
}
|
| 77 |
|
| 78 |
-
/
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
}
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
case '
|
| 88 |
-
case '
|
| 89 |
-
case '
|
| 90 |
-
case '
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
}
|
| 95 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
-
// ----------
|
| 98 |
-
//start(): void {
|
| 99 |
-
// this.errorMsg = '';
|
| 100 |
-
// this.isDone = false;
|
| 101 |
-
// this.finalMix = null;
|
| 102 |
-
// this.inProgressMix = null;
|
| 103 |
-
// this.selectedColor = '';
|
| 104 |
-
// this.sessionId = '';
|
| 105 |
-
// this.index = 0;
|
| 106 |
-
// this.total = 0;
|
| 107 |
-
// this.question = '';
|
| 108 |
-
// this.options = [];
|
| 109 |
-
|
| 110 |
-
// if (!this.userId?.trim()) {
|
| 111 |
-
// this.errorMsg = 'User ID is required.';
|
| 112 |
-
// return;
|
| 113 |
-
// }
|
| 114 |
-
|
| 115 |
-
// this.loading = true;
|
| 116 |
-
// this.api .start({
|
| 117 |
-
|
| 118 |
-
// user_id: this.userId.trim(),
|
| 119 |
-
// role: this.role,
|
| 120 |
-
// n_questions: this.nQuestions,
|
| 121 |
-
// batch_size: this.batchSize,
|
| 122 |
-
// })
|
| 123 |
-
// .subscribe({
|
| 124 |
-
// next: (res: StartResponse) => {
|
| 125 |
-
// this.loading = false;
|
| 126 |
-
// this.sessionId = res.session_id;
|
| 127 |
-
// this.index = res.index;
|
| 128 |
-
// this.total = res.total;
|
| 129 |
-
// this.question = res.question;
|
| 130 |
-
// this.options = (res.options || []) as any;
|
| 131 |
-
// // Optional: inspect res.profile_used
|
| 132 |
-
// if (res.profile_used === false) {
|
| 133 |
-
// this.errorMsg =
|
| 134 |
-
// 'No profile found for this user and role. Please complete the profile first.';
|
| 135 |
-
// }
|
| 136 |
-
// },
|
| 137 |
-
// error: (err) => {
|
| 138 |
-
// this.loading = false;
|
| 139 |
-
// this.errorMsg = err?.error?.error || 'Failed to start LLM session.';
|
| 140 |
-
// },
|
| 141 |
-
// });
|
| 142 |
-
//}
|
| 143 |
start(): void {
|
|
|
|
|
|
|
| 144 |
this.errorMsg = '';
|
| 145 |
this.isDone = false;
|
| 146 |
this.finalMix = null;
|
| 147 |
this.inProgressMix = null;
|
| 148 |
this.selectedColor = '';
|
| 149 |
-
this.sessionId = '';
|
| 150 |
-
this.index = 0;
|
| 151 |
-
this.total = 0;
|
| 152 |
-
this.question = '';
|
| 153 |
-
this.options = [];
|
| 154 |
|
| 155 |
-
|
| 156 |
-
this.
|
| 157 |
-
return;
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
-
this.loading = true;
|
| 161 |
-
|
| 162 |
-
// Log the payload before the API request
|
| 163 |
-
const payload = {
|
| 164 |
-
user_id: this.userId.trim(),
|
| 165 |
role: this.role,
|
| 166 |
n_questions: this.nQuestions,
|
| 167 |
-
batch_size: this.batchSize
|
| 168 |
-
}
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
this.
|
| 175 |
-
this.sessionId = res.session_id;
|
| 176 |
-
this.index = res.index;
|
| 177 |
-
this.total = res.total;
|
| 178 |
-
this.question = res.question;
|
| 179 |
-
this.options = (res.options || []) as any;
|
| 180 |
-
console.log("res", res);
|
| 181 |
-
// Optional: inspect res.profile_used
|
| 182 |
-
if (res.profile_used === false) {
|
| 183 |
-
this.errorMsg =
|
| 184 |
-
'No profile found for this user and role. Please complete the profile first.';
|
| 185 |
-
}
|
| 186 |
},
|
| 187 |
error: (err) => {
|
|
|
|
| 188 |
this.loading = false;
|
| 189 |
-
this.errorMsg = err?.error?.error || 'Failed to start LLM session.';
|
| 190 |
},
|
|
|
|
| 191 |
});
|
| 192 |
}
|
| 193 |
|
| 194 |
submitAnswer(): void {
|
| 195 |
-
if (!this.sessionId || !this.selectedColor) return;
|
| 196 |
this.loading = true;
|
| 197 |
this.errorMsg = '';
|
| 198 |
|
| 199 |
-
this.api
|
| 200 |
-
.next({ session_id: this.sessionId, selected_color: this.selectedColor })
|
| 201 |
.subscribe({
|
| 202 |
-
next: (
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
if (
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
// Continue
|
| 218 |
-
this.index = res.index ?? this.index + 1;
|
| 219 |
-
this.total = res.total ?? this.total;
|
| 220 |
-
this.question = res.question || '';
|
| 221 |
-
this.options = (res.options || []) as any;
|
| 222 |
-
this.inProgressMix = (res.progress || null) as Mix;
|
| 223 |
-
this.selectedColor = ''; // reset selection
|
| 224 |
},
|
| 225 |
error: (err) => {
|
|
|
|
| 226 |
this.loading = false;
|
| 227 |
-
this.errorMsg = err?.error?.error || 'Failed to fetch next question.';
|
| 228 |
},
|
|
|
|
| 229 |
});
|
| 230 |
}
|
| 231 |
|
| 232 |
restart(): void {
|
| 233 |
-
this.isDone = false;
|
| 234 |
-
this.finalMix = null;
|
| 235 |
-
this.inProgressMix = null;
|
| 236 |
-
this.selectedColor = '';
|
| 237 |
this.sessionId = '';
|
| 238 |
this.index = 0;
|
| 239 |
this.total = 0;
|
| 240 |
this.question = '';
|
| 241 |
this.options = [];
|
|
|
|
|
|
|
| 242 |
this.errorMsg = '';
|
|
|
|
|
|
|
|
|
|
| 243 |
}
|
| 244 |
}
|
|
|
|
| 1 |
+
import { Component, OnInit, AfterViewInit, ElementRef, ViewChild } from '@angular/core';
|
| 2 |
import { FormsModule } from '@angular/forms';
|
| 3 |
import { CommonModule } from '@angular/common';
|
| 4 |
+
import { LlmQaService, Role, ColorKey, StartResponse, NextResponse, LlmOption } from '../services/llm-qa.service';
|
| 5 |
import { ActivatedRoute, Router } from '@angular/router';
|
| 6 |
+
import { RouterLink } from '@angular/router';
|
| 7 |
type Mix = Record<ColorKey, number>;
|
| 8 |
|
| 9 |
@Component({
|
| 10 |
selector: 'app-llm-quiz',
|
| 11 |
standalone: true,
|
| 12 |
+
imports: [CommonModule, FormsModule, RouterLink],
|
| 13 |
templateUrl: './llm-quiz.component.html',
|
| 14 |
styleUrls: ['./llm-quiz.component.css'],
|
| 15 |
})
|
| 16 |
+
export class LlmQuizComponent implements OnInit, AfterViewInit {
|
| 17 |
+
@ViewChild('brainBg') brainBg!: ElementRef;
|
| 18 |
+
|
| 19 |
// --- Config bound to the UI ---
|
| 20 |
role: Role = 'marriage';
|
| 21 |
+
nQuestions = 20;
|
| 22 |
+
batchSize = 10;
|
|
|
|
|
|
|
| 23 |
userId = '';
|
| 24 |
|
| 25 |
// --- Runtime state ---
|
| 26 |
sessionId = '';
|
| 27 |
+
index = 0; // current question index (1‑based from backend)
|
| 28 |
+
total = 0; // total questions in session
|
|
|
|
| 29 |
question = '';
|
| 30 |
+
options: LlmOption[] = [];
|
| 31 |
selectedColor: ColorKey | '' = '';
|
| 32 |
|
| 33 |
loading = false;
|
| 34 |
errorMsg = '';
|
| 35 |
isDone = false;
|
| 36 |
|
| 37 |
+
inProgressMix: Mix | null = null; // live progress (NextResponse.progress)
|
| 38 |
+
finalMix: Mix | null = null; // final mix (NextResponse.mix when done)
|
| 39 |
|
| 40 |
+
constructor(
|
| 41 |
+
private api: LlmQaService,
|
| 42 |
+
private route: ActivatedRoute,
|
| 43 |
+
private router: Router
|
| 44 |
+
) { }
|
| 45 |
|
| 46 |
+
goHome(): void { this.router.navigate(['/']); }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
+
ngOnInit(): void {
|
| 49 |
+
const qp = this.route.snapshot.queryParamMap;
|
| 50 |
+
const uid = (qp.get('user_id') || localStorage.getItem('user_id') || '').trim();
|
| 51 |
+
const role = (qp.get('role') || localStorage.getItem('role') || '').toLowerCase();
|
| 52 |
+
const autostart = qp.get('autostart') === '1';
|
| 53 |
|
| 54 |
+
if (uid) this.userId = uid;
|
| 55 |
+
if (role) this.role = role as any;
|
| 56 |
|
| 57 |
+
if (autostart && this.userId && this.role) {
|
| 58 |
+
this.start();
|
| 59 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
}
|
| 61 |
|
| 62 |
+
ngAfterViewInit(): void { this.createNeuralNetwork(); }
|
| 63 |
+
|
| 64 |
+
// Decorative background (unchanged)
|
| 65 |
+
createNeuralNetwork(): void {
|
| 66 |
+
if (!this.brainBg) return;
|
| 67 |
+
const container = this.brainBg.nativeElement;
|
| 68 |
+
const neuronCount = 25;
|
| 69 |
+
const connectionCount = 40;
|
| 70 |
+
for (let i = 0; i < neuronCount; i++) {
|
| 71 |
+
const neuron = document.createElement('div');
|
| 72 |
+
neuron.className = 'neuron';
|
| 73 |
+
neuron.style.left = Math.random() * 100 + '%';
|
| 74 |
+
neuron.style.top = Math.random() * 100 + '%';
|
| 75 |
+
neuron.style.animationDelay = (Math.random() * 3) + 's';
|
| 76 |
+
container.appendChild(neuron);
|
| 77 |
+
}
|
| 78 |
+
for (let i = 0; i < connectionCount; i++) {
|
| 79 |
+
const connection = document.createElement('div');
|
| 80 |
+
connection.className = 'neuron-connection';
|
| 81 |
+
connection.style.left = Math.random() * 100 + '%';
|
| 82 |
+
connection.style.top = Math.random() * 100 + '%';
|
| 83 |
+
connection.style.width = (50 + Math.random() * 150) + 'px';
|
| 84 |
+
connection.style.transform = `rotate(${Math.random() * 360}deg)`;
|
| 85 |
+
connection.style.animationDelay = (Math.random() * 4) + 's';
|
| 86 |
+
container.appendChild(connection);
|
| 87 |
+
}
|
| 88 |
}
|
| 89 |
|
| 90 |
+
// ---------- Helpers ----------
|
| 91 |
+
asPercent(v: number | undefined | null): number { const n = Number(v); return Number.isFinite(n) ? Math.round(n) : 0; }
|
| 92 |
+
getDimensionName(color: string): string {
|
| 93 |
+
switch (color) {
|
| 94 |
+
case 'blue': return 'Analytical';
|
| 95 |
+
case 'green': return 'Empathetic';
|
| 96 |
+
case 'red': return 'Assertive';
|
| 97 |
+
case 'yellow': return 'Creative';
|
| 98 |
+
default: return color;
|
| 99 |
+
}
|
| 100 |
}
|
| 101 |
+
get quizIcon(): string {
|
| 102 |
+
switch (this.role) {
|
| 103 |
+
case 'interview': return 'fa-solid fa-briefcase';
|
| 104 |
+
case 'marriage': return 'fa-solid fa-heart';
|
| 105 |
+
case 'partnership': return 'fa-solid fa-handshake';
|
| 106 |
+
case 'team': return 'fa-solid fa-users';
|
| 107 |
+
case 'general': return 'fa-solid fa-user';
|
| 108 |
+
case 'assistant': return 'fa-solid fa-robot';
|
| 109 |
+
case 'ceo': return 'fa-solid fa-crown';
|
| 110 |
+
default: return 'fa-solid fa-brain';
|
| 111 |
}
|
| 112 |
}
|
| 113 |
+
get progressLabel(): string { return `${this.index} / ${this.total}`; }
|
| 114 |
+
get progressPercent(): number { return this.total ? Math.round((this.index / this.total) * 100) : 0; }
|
| 115 |
+
getInProgressMixValue(color: string): number { return this.inProgressMix?.[color as ColorKey] || 0; }
|
| 116 |
+
getFinalMixValue(color: string): number { return this.finalMix?.[color as ColorKey] || 0; }
|
| 117 |
+
get dominantColor(): ColorKey | null {
|
| 118 |
+
if (!this.finalMix) return null;
|
| 119 |
+
return (Object.entries(this.finalMix) as [ColorKey, number][])
|
| 120 |
+
.sort((a, b) => b[1] - a[1])[0]?.[0] || null;
|
| 121 |
+
}
|
| 122 |
+
get dominantDescription(): string {
|
| 123 |
+
const color = this.dominantColor; if (!color) return '';
|
| 124 |
+
const descriptions: Record<ColorKey, string> = {
|
| 125 |
+
blue: 'Your thinking is primarily analytical and logical, focusing on data-driven decisions and systematic problem-solving.',
|
| 126 |
+
green: 'You lead with empathy and emotional intelligence, valuing relationships and harmony in your interactions.',
|
| 127 |
+
red: 'You approach situations with assertiveness and directness, prioritizing action and tangible results.',
|
| 128 |
+
yellow: 'Your cognitive style is creative and innovative, embracing new ideas and unconventional approaches.'
|
| 129 |
+
};
|
| 130 |
+
return descriptions[color];
|
| 131 |
+
}
|
| 132 |
|
| 133 |
+
// ---------- API calls (Observable based) ----------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
start(): void {
|
| 135 |
+
if (!this.userId) { this.errorMsg = 'User ID required'; return; }
|
| 136 |
+
this.loading = true;
|
| 137 |
this.errorMsg = '';
|
| 138 |
this.isDone = false;
|
| 139 |
this.finalMix = null;
|
| 140 |
this.inProgressMix = null;
|
| 141 |
this.selectedColor = '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
+
this.api.start({
|
| 144 |
+
user_id: this.userId,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
role: this.role,
|
| 146 |
n_questions: this.nQuestions,
|
| 147 |
+
batch_size: this.batchSize
|
| 148 |
+
}).subscribe({
|
| 149 |
+
next: (resp: StartResponse) => {
|
| 150 |
+
this.sessionId = resp.session_id;
|
| 151 |
+
this.index = resp.index ?? 0;
|
| 152 |
+
this.total = resp.total ?? 0;
|
| 153 |
+
this.question = resp.question || '';
|
| 154 |
+
this.options = resp.options || [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
},
|
| 156 |
error: (err) => {
|
| 157 |
+
this.errorMsg = err?.error?.error || err.message || 'Failed to start quiz';
|
| 158 |
this.loading = false;
|
|
|
|
| 159 |
},
|
| 160 |
+
complete: () => { this.loading = false; }
|
| 161 |
});
|
| 162 |
}
|
| 163 |
|
| 164 |
submitAnswer(): void {
|
| 165 |
+
if (!this.sessionId || !this.selectedColor || this.loading) return;
|
| 166 |
this.loading = true;
|
| 167 |
this.errorMsg = '';
|
| 168 |
|
| 169 |
+
this.api.next({ session_id: this.sessionId, selected_color: this.selectedColor as ColorKey })
|
|
|
|
| 170 |
.subscribe({
|
| 171 |
+
next: (resp: NextResponse) => {
|
| 172 |
+
// Update progress
|
| 173 |
+
if (resp.index !== undefined) this.index = resp.index;
|
| 174 |
+
if (resp.total !== undefined) this.total = resp.total;
|
| 175 |
+
if (resp.progress) this.inProgressMix = resp.progress as Mix;
|
| 176 |
+
|
| 177 |
+
if (resp.done) {
|
| 178 |
+
this.isDone = true;
|
| 179 |
+
this.finalMix = (resp.mix || null) as Mix;
|
| 180 |
+
} else {
|
| 181 |
+
this.question = resp.question || '';
|
| 182 |
+
this.options = resp.options || [];
|
| 183 |
+
this.selectedColor = '';
|
| 184 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
},
|
| 186 |
error: (err) => {
|
| 187 |
+
this.errorMsg = err?.error?.error || err.message || 'Failed to submit answer';
|
| 188 |
this.loading = false;
|
|
|
|
| 189 |
},
|
| 190 |
+
complete: () => { this.loading = false; }
|
| 191 |
});
|
| 192 |
}
|
| 193 |
|
| 194 |
restart(): void {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
this.sessionId = '';
|
| 196 |
this.index = 0;
|
| 197 |
this.total = 0;
|
| 198 |
this.question = '';
|
| 199 |
this.options = [];
|
| 200 |
+
this.selectedColor = '';
|
| 201 |
+
this.loading = false;
|
| 202 |
this.errorMsg = '';
|
| 203 |
+
this.isDone = false;
|
| 204 |
+
this.inProgressMix = null;
|
| 205 |
+
this.finalMix = null;
|
| 206 |
}
|
| 207 |
}
|
src/app/matching-list/matching-list.component.css
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:host {
|
| 2 |
+
display: block;
|
| 3 |
+
height: 100vh;
|
| 4 |
+
background: #f5f7f9;
|
| 5 |
+
font-family: "Poppins", sans-serif;
|
| 6 |
+
margin: 0;
|
| 7 |
+
color: #333;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
/* Loader overlay */
|
| 11 |
+
.loader-overlay {
|
| 12 |
+
position: fixed;
|
| 13 |
+
top: 0;
|
| 14 |
+
left: 0;
|
| 15 |
+
width: 100%;
|
| 16 |
+
height: 100%;
|
| 17 |
+
background: rgba(15, 19, 30, 0.95);
|
| 18 |
+
display: flex;
|
| 19 |
+
justify-content: center;
|
| 20 |
+
align-items: center;
|
| 21 |
+
z-index: 2000;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
.loader-container {
|
| 25 |
+
text-align: center;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.loader-gif {
|
| 29 |
+
width: 50%;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
/* HEADER */
|
| 33 |
+
.header-bar {
|
| 34 |
+
display: flex;
|
| 35 |
+
align-items: center;
|
| 36 |
+
justify-content: space-between;
|
| 37 |
+
background: #fff;
|
| 38 |
+
padding: 15px 25px;
|
| 39 |
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
| 40 |
+
position: sticky;
|
| 41 |
+
top: 7vw;
|
| 42 |
+
z-index: 50;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.logo {
|
| 46 |
+
height: 40px;
|
| 47 |
+
margin-right: 10px;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.left-section {
|
| 51 |
+
display: flex;
|
| 52 |
+
align-items: center;
|
| 53 |
+
gap: 10px;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
.search-section {
|
| 57 |
+
flex: 1;
|
| 58 |
+
text-align: center;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.search-section input,
|
| 62 |
+
.search-section select {
|
| 63 |
+
padding: 8px 12px;
|
| 64 |
+
border-radius: 6px;
|
| 65 |
+
border: 1px solid #ccc;
|
| 66 |
+
margin-right: 6px;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
.filter-btn {
|
| 70 |
+
padding: 8px 14px;
|
| 71 |
+
border: none;
|
| 72 |
+
background: #00bfa6;
|
| 73 |
+
color: #fff;
|
| 74 |
+
border-radius: 6px;
|
| 75 |
+
cursor: pointer;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.like-section {
|
| 79 |
+
position: relative;
|
| 80 |
+
cursor: pointer;
|
| 81 |
+
padding: 8px 12px;
|
| 82 |
+
border-radius: 6px;
|
| 83 |
+
background: #f5f7f9;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.like-count {
|
| 87 |
+
margin-left: 5px;
|
| 88 |
+
font-weight: bold;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
.liked-dropdown {
|
| 92 |
+
display: none;
|
| 93 |
+
position: absolute;
|
| 94 |
+
top: 100%;
|
| 95 |
+
right: 0;
|
| 96 |
+
background: white;
|
| 97 |
+
border: 1px solid #ccc;
|
| 98 |
+
border-radius: 6px;
|
| 99 |
+
padding: 10px;
|
| 100 |
+
min-width: 150px;
|
| 101 |
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
| 102 |
+
z-index: 100;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
.liked-dropdown.show {
|
| 106 |
+
display: block;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.liked-dropdown ul {
|
| 110 |
+
list-style: none;
|
| 111 |
+
padding: 0;
|
| 112 |
+
margin: 0;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.liked-dropdown li {
|
| 116 |
+
padding: 5px 0;
|
| 117 |
+
border-bottom: 1px solid #eee;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
.liked-dropdown li:last-child {
|
| 121 |
+
border-bottom: none;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
/* MAIN LAYOUT */
|
| 125 |
+
.main-layout {
|
| 126 |
+
display: flex;
|
| 127 |
+
flex-direction: row;
|
| 128 |
+
height: calc(100vh - 70px);
|
| 129 |
+
position: relative;
|
| 130 |
+
top: 8vw;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
.user-panel {
|
| 134 |
+
width: 260px;
|
| 135 |
+
background: #fff;
|
| 136 |
+
height: calc(100vh - 70px);
|
| 137 |
+
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
|
| 138 |
+
padding: 20px;
|
| 139 |
+
display: flex;
|
| 140 |
+
flex-direction: column;
|
| 141 |
+
align-items: center;
|
| 142 |
+
position: sticky;
|
| 143 |
+
top: 70px;
|
| 144 |
+
overflow-y: auto;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
.user-photo {
|
| 148 |
+
width: 120px;
|
| 149 |
+
height: 120px;
|
| 150 |
+
border-radius: 50%;
|
| 151 |
+
object-fit: cover;
|
| 152 |
+
margin-bottom: 15px;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.user-name {
|
| 156 |
+
font-weight: bold;
|
| 157 |
+
font-size: 18px;
|
| 158 |
+
margin-bottom: 5px;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
.user-city {
|
| 162 |
+
color: #666;
|
| 163 |
+
margin-bottom: 20px;
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
.user-stats {
|
| 167 |
+
width: 100%;
|
| 168 |
+
margin-bottom: 20px;
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
.user-stats div {
|
| 172 |
+
display: flex;
|
| 173 |
+
justify-content: space-between;
|
| 174 |
+
margin-bottom: 10px;
|
| 175 |
+
padding: 5px 0;
|
| 176 |
+
border-bottom: 1px solid #eee;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
.user-stats .value {
|
| 180 |
+
font-weight: bold;
|
| 181 |
+
color: #00bfa6;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.edit-btn {
|
| 185 |
+
padding: 10px 20px;
|
| 186 |
+
border: none;
|
| 187 |
+
background: #f5f7f9;
|
| 188 |
+
border-radius: 6px;
|
| 189 |
+
cursor: pointer;
|
| 190 |
+
margin-bottom: 15px;
|
| 191 |
+
width: 100%;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
.progress-bar-user {
|
| 195 |
+
width: 100%;
|
| 196 |
+
height: 8px;
|
| 197 |
+
border-radius: 6px;
|
| 198 |
+
background: #e5e5e5;
|
| 199 |
+
overflow: hidden;
|
| 200 |
+
margin-top: 10px;
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
.progress-fill {
|
| 204 |
+
height: 100%;
|
| 205 |
+
background: linear-gradient(90deg, #00bfa6, #2ecc71);
|
| 206 |
+
transition: width 0.3s ease;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
.matching-container {
|
| 210 |
+
flex: 1;
|
| 211 |
+
padding: 20px;
|
| 212 |
+
overflow-y: auto;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
.grid-view {
|
| 216 |
+
display: grid;
|
| 217 |
+
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
| 218 |
+
gap: 20px;
|
| 219 |
+
margin-top: 10px;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
/* PROFILE CARD */
|
| 223 |
+
.profile-card {
|
| 224 |
+
background: #fff;
|
| 225 |
+
border-radius: 12px;
|
| 226 |
+
overflow: hidden;
|
| 227 |
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
| 228 |
+
transition: 0.3s;
|
| 229 |
+
cursor: pointer;
|
| 230 |
+
position: relative;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
.profile-card:hover {
|
| 234 |
+
transform: translateY(-5px);
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
.image-wrapper {
|
| 238 |
+
position: relative;
|
| 239 |
+
height: 200px;
|
| 240 |
+
overflow: hidden;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
.image-wrapper img {
|
| 244 |
+
width: 100%;
|
| 245 |
+
height: 100%;
|
| 246 |
+
object-fit: cover;
|
| 247 |
+
transition: 0.4s;
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
.image-wrapper img:hover {
|
| 251 |
+
transform: scale(1.05);
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
.online-dot {
|
| 255 |
+
position: absolute;
|
| 256 |
+
top: 10px;
|
| 257 |
+
left: 10px;
|
| 258 |
+
width: 10px;
|
| 259 |
+
height: 10px;
|
| 260 |
+
border-radius: 50%;
|
| 261 |
+
background: #0f0;
|
| 262 |
+
box-shadow: 0 0 6px #0f0;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
.image-wrapper .badge {
|
| 266 |
+
position: absolute;
|
| 267 |
+
top: 10px;
|
| 268 |
+
right: 10px;
|
| 269 |
+
background: #00bfa6;
|
| 270 |
+
color: white;
|
| 271 |
+
padding: 4px 8px;
|
| 272 |
+
border-radius: 12px;
|
| 273 |
+
font-size: 12px;
|
| 274 |
+
font-weight: bold;
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
.profile-info {
|
| 278 |
+
padding: 15px;
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
.profile-info h3 {
|
| 282 |
+
margin: 0 0 5px 0;
|
| 283 |
+
font-size: 16px;
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
.profile-info p {
|
| 287 |
+
margin: 2px 0;
|
| 288 |
+
color: #666;
|
| 289 |
+
font-size: 14px;
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
.profession {
|
| 293 |
+
font-weight: 500;
|
| 294 |
+
color: #333 !important;
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
.progress-bar {
|
| 298 |
+
height: 6px;
|
| 299 |
+
background: #e5e5e5;
|
| 300 |
+
border-radius: 3px;
|
| 301 |
+
overflow: hidden;
|
| 302 |
+
margin: 10px 0;
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
.progress-bar-inner {
|
| 306 |
+
height: 100%;
|
| 307 |
+
background: linear-gradient(90deg, #00bfa6, #2ecc71);
|
| 308 |
+
transition: width 0.3s ease;
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
.actions {
|
| 312 |
+
display: flex;
|
| 313 |
+
justify-content: space-between;
|
| 314 |
+
margin-top: 10px;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
.actions button {
|
| 318 |
+
border: none;
|
| 319 |
+
background: #f3f3f3;
|
| 320 |
+
border-radius: 6px;
|
| 321 |
+
padding: 8px 12px;
|
| 322 |
+
cursor: pointer;
|
| 323 |
+
transition: 0.2s;
|
| 324 |
+
flex: 1;
|
| 325 |
+
margin: 0 2px;
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
.actions button:hover,
|
| 329 |
+
.actions button.liked {
|
| 330 |
+
background: #00bfa6;
|
| 331 |
+
color: #fff;
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
/* MODAL */
|
| 335 |
+
.modal {
|
| 336 |
+
position: fixed;
|
| 337 |
+
top: 0;
|
| 338 |
+
left: 0;
|
| 339 |
+
width: 100%;
|
| 340 |
+
height: 100%;
|
| 341 |
+
background: rgba(0, 0, 0, 0.5);
|
| 342 |
+
display: none;
|
| 343 |
+
justify-content: center;
|
| 344 |
+
align-items: center;
|
| 345 |
+
z-index: 40;
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
.modal.show {
|
| 349 |
+
display: flex;
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
.modal-content {
|
| 353 |
+
background: #fff;
|
| 354 |
+
padding: 25px;
|
| 355 |
+
border-radius: 10px;
|
| 356 |
+
width: 450px;
|
| 357 |
+
max-height: 90vh;
|
| 358 |
+
overflow-y: auto;
|
| 359 |
+
text-align: center;
|
| 360 |
+
position: relative;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
.ai-summary {
|
| 364 |
+
margin-top: 10px;
|
| 365 |
+
font-size: 14px;
|
| 366 |
+
color: #444;
|
| 367 |
+
text-align: left;
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
.close {
|
| 371 |
+
position: absolute;
|
| 372 |
+
right: 30px;
|
| 373 |
+
top: 20px;
|
| 374 |
+
font-size: 28px;
|
| 375 |
+
cursor: pointer;
|
| 376 |
+
color: #555;
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
/* Compatibility Report */
|
| 380 |
+
.compatibility-report {
|
| 381 |
+
background: #f8f9fa;
|
| 382 |
+
padding: 16px 20px;
|
| 383 |
+
border-radius: 10px;
|
| 384 |
+
font-size: 14px;
|
| 385 |
+
line-height: 1.6;
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
.quadratic-section h4 {
|
| 389 |
+
color: #007bff;
|
| 390 |
+
margin-bottom: 6px;
|
| 391 |
+
margin-top: 12px;
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
.quadratic-section p {
|
| 395 |
+
margin: 0 0 10px 0;
|
| 396 |
+
color: #333;
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
.quadratic-section ul {
|
| 400 |
+
padding-left: 20px;
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
.quadratic-section li {
|
| 404 |
+
margin-bottom: 4px;
|
| 405 |
+
line-height: 1.4;
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
/* CHAT WINDOW */
|
| 409 |
+
.chat-window {
|
| 410 |
+
position: fixed;
|
| 411 |
+
bottom: 20px;
|
| 412 |
+
right: 20px;
|
| 413 |
+
width: 350px;
|
| 414 |
+
height: 400px;
|
| 415 |
+
background: white;
|
| 416 |
+
border-radius: 10px;
|
| 417 |
+
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.2);
|
| 418 |
+
display: none;
|
| 419 |
+
flex-direction: column;
|
| 420 |
+
z-index: 100;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
.chat-window.show {
|
| 424 |
+
display: flex;
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
.chat-header {
|
| 428 |
+
padding: 15px;
|
| 429 |
+
background: #00bfa6;
|
| 430 |
+
color: white;
|
| 431 |
+
border-radius: 10px 10px 0 0;
|
| 432 |
+
display: flex;
|
| 433 |
+
justify-content: space-between;
|
| 434 |
+
align-items: center;
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
.chat-body {
|
| 438 |
+
flex: 1;
|
| 439 |
+
padding: 15px;
|
| 440 |
+
overflow-y: auto;
|
| 441 |
+
display: flex;
|
| 442 |
+
flex-direction: column;
|
| 443 |
+
gap: 10px;
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
.chat-msg {
|
| 447 |
+
padding: 10px 15px;
|
| 448 |
+
border-radius: 18px;
|
| 449 |
+
max-width: 80%;
|
| 450 |
+
font-size: 14px;
|
| 451 |
+
position: relative;
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
.user-msg {
|
| 455 |
+
align-self: flex-end;
|
| 456 |
+
background: #00bfa6;
|
| 457 |
+
color: white;
|
| 458 |
+
border-bottom-right-radius: 4px;
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
.other-msg {
|
| 462 |
+
align-self: flex-start;
|
| 463 |
+
background: #f1f1f1;
|
| 464 |
+
color: #333;
|
| 465 |
+
border-bottom-left-radius: 4px;
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
.timestamp {
|
| 469 |
+
font-size: 10px;
|
| 470 |
+
opacity: 0.7;
|
| 471 |
+
margin-top: 4px;
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
+
.typing {
|
| 475 |
+
font-style: italic;
|
| 476 |
+
color: #666;
|
| 477 |
+
font-size: 12px;
|
| 478 |
+
align-self: flex-start;
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
.chat-input {
|
| 482 |
+
display: flex;
|
| 483 |
+
padding: 15px;
|
| 484 |
+
border-top: 1px solid #eee;
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
.chat-input input {
|
| 488 |
+
flex: 1;
|
| 489 |
+
padding: 10px;
|
| 490 |
+
border: 1px solid #ddd;
|
| 491 |
+
border-radius: 20px;
|
| 492 |
+
margin-right: 10px;
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
.chat-input button {
|
| 496 |
+
padding: 10px 20px;
|
| 497 |
+
border: none;
|
| 498 |
+
background: #00bfa6;
|
| 499 |
+
color: white;
|
| 500 |
+
border-radius: 20px;
|
| 501 |
+
cursor: pointer;
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
/* Skeleton Loader */
|
| 505 |
+
.skeleton {
|
| 506 |
+
background: #e0e0e0;
|
| 507 |
+
border-radius: 12px;
|
| 508 |
+
height: 300px;
|
| 509 |
+
animation: pulse 1.5s ease-in-out infinite;
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
@keyframes pulse {
|
| 513 |
+
0% {
|
| 514 |
+
opacity: 1;
|
| 515 |
+
}
|
| 516 |
+
|
| 517 |
+
50% {
|
| 518 |
+
opacity: 0.5;
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
100% {
|
| 522 |
+
opacity: 1;
|
| 523 |
+
}
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
/* Responsive Design */
|
| 527 |
+
@media (max-width: 768px) {
|
| 528 |
+
.main-layout {
|
| 529 |
+
flex-direction: column;
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
.user-panel {
|
| 533 |
+
width: 100%;
|
| 534 |
+
height: auto;
|
| 535 |
+
position: static;
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
.grid-view {
|
| 539 |
+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
| 540 |
+
}
|
| 541 |
+
|
| 542 |
+
.header-bar {
|
| 543 |
+
flex-direction: column;
|
| 544 |
+
gap: 10px;
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
.search-section {
|
| 548 |
+
width: 100%;
|
| 549 |
+
display: flex;
|
| 550 |
+
flex-wrap: wrap;
|
| 551 |
+
gap: 5px;
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
.search-section input,
|
| 555 |
+
.search-section select {
|
| 556 |
+
flex: 1;
|
| 557 |
+
min-width: 120px;
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
.modal-content {
|
| 561 |
+
width: 90%;
|
| 562 |
+
margin: 20px;
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
.chat-window {
|
| 566 |
+
width: 300px;
|
| 567 |
+
height: 350px;
|
| 568 |
+
}
|
| 569 |
+
}
|
| 570 |
+
|
| 571 |
+
@media (max-width: 480px) {
|
| 572 |
+
.grid-view {
|
| 573 |
+
grid-template-columns: 1fr;
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
.search-section input,
|
| 577 |
+
.search-section select {
|
| 578 |
+
min-width: 100px;
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
.chat-window {
|
| 582 |
+
width: 280px;
|
| 583 |
+
right: 10px;
|
| 584 |
+
bottom: 10px;
|
| 585 |
+
}
|
| 586 |
+
}
|
src/app/matching-list/matching-list.component.html
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- Loader Overlay -->
|
| 2 |
+
<div class="loader-overlay" *ngIf="loading">
|
| 3 |
+
<div class="loader-container">
|
| 4 |
+
<img src="assets/Loader.gif" alt="Loading..." class="loader-gif" />
|
| 5 |
+
</div>
|
| 6 |
+
</div>
|
| 7 |
+
|
| 8 |
+
<!-- Main Content -->
|
| 9 |
+
<div *ngIf="!loading" class="main-container">
|
| 10 |
+
<!-- HEADER -->
|
| 11 |
+
<div class="header-bar">
|
| 12 |
+
<div class="left-section">
|
| 13 |
+
<img src="https://cdn-icons-png.flaticon.com/512/168/168724.png" class="logo" alt="logo">
|
| 14 |
+
<h2>Py-Match</h2>
|
| 15 |
+
</div>
|
| 16 |
+
<div class="search-section">
|
| 17 |
+
<input type="text" id="searchBox" placeholder="Search name or city"
|
| 18 |
+
[(ngModel)]="searchTerm" (input)="onSearchChange()">
|
| 19 |
+
<select id="filterLocation" (change)="onLocationFilterChange($event)">
|
| 20 |
+
<option value="">All Locations</option>
|
| 21 |
+
<option *ngFor="let location of availableLocations" [value]="location">{{ location }}</option>
|
| 22 |
+
</select>
|
| 23 |
+
<select id="filterAge" (change)="onAgeFilterChange($event)">
|
| 24 |
+
<option value="">All Ages</option>
|
| 25 |
+
<option *ngFor="let ageRange of availableAgeRanges" [value]="ageRange">{{ ageRange }}</option>
|
| 26 |
+
</select>
|
| 27 |
+
<select id="filterProfession" (change)="onProfessionFilterChange($event)">
|
| 28 |
+
<option value="">All Professions</option>
|
| 29 |
+
<option *ngFor="let profession of availableProfessions" [value]="profession">{{ profession }}</option>
|
| 30 |
+
</select>
|
| 31 |
+
<select id="scoreFilter" (change)="onScoreFilterChange($event)">
|
| 32 |
+
<option value="0">All Scores</option>
|
| 33 |
+
<option value="80">80%+</option>
|
| 34 |
+
<option value="90">90%+</option>
|
| 35 |
+
</select>
|
| 36 |
+
<select id="sortBy" (change)="onSortChange($event)">
|
| 37 |
+
<option value="compatibility">Top Compatibility</option>
|
| 38 |
+
<option value="newest">Newest</option>
|
| 39 |
+
<option value="location">Closest Location</option>
|
| 40 |
+
</select>
|
| 41 |
+
<button class="filter-btn" (click)="applyFilters()">Apply</button>
|
| 42 |
+
</div>
|
| 43 |
+
<div class="like-section" (click)="toggleLikedDropdown()">
|
| 44 |
+
❤️
|
| 45 |
+
<span class="like-count">{{ likedProfiles.length }}</span>
|
| 46 |
+
<div class="liked-dropdown" [class.show]="showLikedDropdown">
|
| 47 |
+
<ul>
|
| 48 |
+
<li *ngFor="let profile of likedProfiles">{{ profile.name }}</li>
|
| 49 |
+
<li *ngIf="likedProfiles.length === 0">No liked profiles</li>
|
| 50 |
+
</ul>
|
| 51 |
+
</div>
|
| 52 |
+
</div>
|
| 53 |
+
</div>
|
| 54 |
+
|
| 55 |
+
<!-- MAIN LAYOUT -->
|
| 56 |
+
<div class="main-layout">
|
| 57 |
+
<!-- LEFT USER PANEL -->
|
| 58 |
+
<div class="user-panel">
|
| 59 |
+
<img [src]="userPhoto" class="user-photo" alt="User Photo">
|
| 60 |
+
<div class="user-name">{{ userName }}</div>
|
| 61 |
+
<div class="user-city">{{ userCity }}</div>
|
| 62 |
+
<div class="user-stats">
|
| 63 |
+
<div><span>Matches</span><span class="value">{{ totalMatches }}</span></div>
|
| 64 |
+
<div><span>Likes</span><span class="value">{{ likedProfiles.length }}</span></div>
|
| 65 |
+
<div><span>Compatibility</span><span class="value">87%</span></div>
|
| 66 |
+
</div>
|
| 67 |
+
<button class="edit-btn" (click)="openEditModal()">⚙️ Edit Profile</button>
|
| 68 |
+
<div class="progress-bar-user">
|
| 69 |
+
<div class="progress-fill" [style.width]="userProfileCompletion + '%'"></div>
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
| 72 |
+
|
| 73 |
+
<!-- RIGHT MATCHING CONTAINER -->
|
| 74 |
+
<div class="matching-container">
|
| 75 |
+
<div class="grid-view" id="profilesContainer">
|
| 76 |
+
<!-- Skeleton Loaders -->
|
| 77 |
+
<div class="skeleton" *ngIf="displayedMatches.length === 0 && !loading"></div>
|
| 78 |
+
<div class="skeleton" *ngIf="displayedMatches.length === 0 && !loading"></div>
|
| 79 |
+
<div class="skeleton" *ngIf="displayedMatches.length === 0 && !loading"></div>
|
| 80 |
+
|
| 81 |
+
<!-- Profile Cards -->
|
| 82 |
+
<div *ngFor="let profile of displayedMatches"
|
| 83 |
+
class="profile-card"
|
| 84 |
+
(click)="openProfileModal(profile)">
|
| 85 |
+
<div class="image-wrapper">
|
| 86 |
+
<div class="online-dot" *ngIf="profile.isOnline"></div>
|
| 87 |
+
<img [src]="profile.photo_url" [alt]="profile.name"
|
| 88 |
+
(error)="handleImageError($event, profile)">
|
| 89 |
+
<span class="badge">{{ profile.match_score || profile.score }}%</span>
|
| 90 |
+
</div>
|
| 91 |
+
<div class="profile-info">
|
| 92 |
+
<h3>{{ profile.name }}</h3>
|
| 93 |
+
<p>{{ profile.age }} • {{ profile.city }}</p>
|
| 94 |
+
<p class="profession">{{ profile.job }}</p>
|
| 95 |
+
<div class="progress-bar">
|
| 96 |
+
<div class="progress-bar-inner" [style.width]="(profile.match_score || profile.score) + '%'"></div>
|
| 97 |
+
</div>
|
| 98 |
+
<div class="actions">
|
| 99 |
+
<button [class.liked]="isLiked(profile)"
|
| 100 |
+
(click)="$event.stopPropagation(); toggleLike(profile)">
|
| 101 |
+
❤️
|
| 102 |
+
</button>
|
| 103 |
+
<button (click)="$event.stopPropagation(); openChat(profile)">
|
| 104 |
+
💬
|
| 105 |
+
</button>
|
| 106 |
+
<button (click)="$event.stopPropagation(); openModal(profile)">
|
| 107 |
+
👁️
|
| 108 |
+
</button>
|
| 109 |
+
</div>
|
| 110 |
+
</div>
|
| 111 |
+
</div>
|
| 112 |
+
</div>
|
| 113 |
+
</div>
|
| 114 |
+
</div>
|
| 115 |
+
|
| 116 |
+
<!-- PROFILE MODAL -->
|
| 117 |
+
<div class="modal" [class.show]="showProfileModal">
|
| 118 |
+
<div class="modal-content" *ngIf="selectedProfile">
|
| 119 |
+
<span class="close" (click)="closeModal()">×</span>
|
| 120 |
+
<img [src]="selectedProfile.photo_url" [alt]="selectedProfile.name"
|
| 121 |
+
(error)="handleImageError($event, selectedProfile)">
|
| 122 |
+
<h3>{{ selectedProfile.name }} ({{ selectedProfile.gender || 'Male' }})</h3>
|
| 123 |
+
<p>{{ selectedProfile.age }} • {{ selectedProfile.city }}</p>
|
| 124 |
+
<p>{{ selectedProfile.job }}</p>
|
| 125 |
+
<p>Overall Match: {{ selectedProfile.match_score || selectedProfile.score }}%</p>
|
| 126 |
+
|
| 127 |
+
<div class="ai-summary" [innerHTML]="getCompatibilityReport(selectedProfile)"></div>
|
| 128 |
+
|
| 129 |
+
<canvas id="aiChart" style="display: none;"></canvas>
|
| 130 |
+
</div>
|
| 131 |
+
</div>
|
| 132 |
+
|
| 133 |
+
<!-- EDIT PROFILE MODAL -->
|
| 134 |
+
<div class="modal" [class.show]="showEditModal">
|
| 135 |
+
<div class="modal-content">
|
| 136 |
+
<span class="close" (click)="closeEditModal()">×</span>
|
| 137 |
+
<h3>Edit Profile</h3>
|
| 138 |
+
<input [(ngModel)]="editName" placeholder="Name" style="margin:6px;padding:6px;width:90%">
|
| 139 |
+
<input [(ngModel)]="editCity" placeholder="City" style="margin:6px;padding:6px;width:90%">
|
| 140 |
+
<button class="filter-btn" (click)="saveProfile()">Save</button>
|
| 141 |
+
</div>
|
| 142 |
+
</div>
|
| 143 |
+
|
| 144 |
+
<!-- CHAT WINDOW -->
|
| 145 |
+
<div class="chat-window" [class.show]="showChatWindow">
|
| 146 |
+
<div class="chat-header">
|
| 147 |
+
<span>Chat with {{ currentChatUser?.name }}</span>
|
| 148 |
+
<span style="cursor:pointer" (click)="closeChat()">✖</span>
|
| 149 |
+
</div>
|
| 150 |
+
<div class="chat-body" #chatBody>
|
| 151 |
+
<div *ngFor="let message of chatMessages"
|
| 152 |
+
class="chat-msg"
|
| 153 |
+
[class.user-msg]="message.sender === 'me'"
|
| 154 |
+
[class.other-msg]="message.sender === 'them'">
|
| 155 |
+
{{ message.text }}
|
| 156 |
+
<div class="timestamp">{{ message.time }}</div>
|
| 157 |
+
</div>
|
| 158 |
+
<div *ngIf="showTyping" class="typing" id="typingMsg">Typing...</div>
|
| 159 |
+
</div>
|
| 160 |
+
<div class="chat-input">
|
| 161 |
+
<input type="text"
|
| 162 |
+
[(ngModel)]="newMessage"
|
| 163 |
+
placeholder="Type a message..."
|
| 164 |
+
(keyup.enter)="sendMessage()"
|
| 165 |
+
(keypress)="typingIndicator()">
|
| 166 |
+
<button (click)="sendMessage()">Send</button>
|
| 167 |
+
</div>
|
| 168 |
+
</div>
|
| 169 |
+
</div>
|
src/app/{quiz/quiz.component.spec.ts → matching-list/matching-list.component.spec.ts}
RENAMED
|
@@ -1,16 +1,16 @@
|
|
| 1 |
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
| 2 |
|
| 3 |
-
import {
|
| 4 |
|
| 5 |
-
describe('
|
| 6 |
-
let component:
|
| 7 |
-
let fixture: ComponentFixture<
|
| 8 |
|
| 9 |
beforeEach(() => {
|
| 10 |
TestBed.configureTestingModule({
|
| 11 |
-
|
| 12 |
});
|
| 13 |
-
fixture = TestBed.createComponent(
|
| 14 |
component = fixture.componentInstance;
|
| 15 |
fixture.detectChanges();
|
| 16 |
});
|
|
|
|
| 1 |
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
| 2 |
|
| 3 |
+
import { MatchingListComponent } from './matching-list.component';
|
| 4 |
|
| 5 |
+
describe('MatchingListComponent', () => {
|
| 6 |
+
let component: MatchingListComponent;
|
| 7 |
+
let fixture: ComponentFixture<MatchingListComponent>;
|
| 8 |
|
| 9 |
beforeEach(() => {
|
| 10 |
TestBed.configureTestingModule({
|
| 11 |
+
imports: [MatchingListComponent]
|
| 12 |
});
|
| 13 |
+
fixture = TestBed.createComponent(MatchingListComponent);
|
| 14 |
component = fixture.componentInstance;
|
| 15 |
fixture.detectChanges();
|
| 16 |
});
|
src/app/matching-list/matching-list.component.ts
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component, OnInit, ElementRef, ViewChild, HostListener } from '@angular/core';
|
| 2 |
+
import { Router } from '@angular/router';
|
| 3 |
+
import { FormsModule } from '@angular/forms';
|
| 4 |
+
import { MatchingService, ApiMatchItem, ApiMatchResponse } from '../matching.service';
|
| 5 |
+
import { CommonModule } from '@angular/common';
|
| 6 |
+
|
| 7 |
+
interface Profile extends ApiMatchItem {
|
| 8 |
+
id?: number;
|
| 9 |
+
name?: string;
|
| 10 |
+
age?: number | null;
|
| 11 |
+
city?: string | null;
|
| 12 |
+
job?: string | null;
|
| 13 |
+
isOnline?: boolean;
|
| 14 |
+
match_score?: number;
|
| 15 |
+
photo_url?: string;
|
| 16 |
+
blue: number;
|
| 17 |
+
green: number;
|
| 18 |
+
yellow: number;
|
| 19 |
+
red: number;
|
| 20 |
+
score: number;
|
| 21 |
+
user_id: number;
|
| 22 |
+
gender?: string;
|
| 23 |
+
profession?: string;
|
| 24 |
+
meetsExpectations?: boolean;
|
| 25 |
+
expectationMatchScore?: number;
|
| 26 |
+
aiExplanation?: string;
|
| 27 |
+
hobbies?: string[];
|
| 28 |
+
education?: string;
|
| 29 |
+
location?: string;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
interface ChatMessage {
|
| 33 |
+
text: string;
|
| 34 |
+
sender: 'me' | 'them';
|
| 35 |
+
time: string;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
@Component({
|
| 39 |
+
selector: 'app-matching-list',
|
| 40 |
+
templateUrl: './matching-list.component.html',
|
| 41 |
+
styleUrls: ['./matching-list.component.css'],
|
| 42 |
+
standalone: true,
|
| 43 |
+
imports: [FormsModule, CommonModule]
|
| 44 |
+
})
|
| 45 |
+
export class MatchingListComponent implements OnInit {
|
| 46 |
+
@ViewChild('chatBody') chatBody!: ElementRef;
|
| 47 |
+
|
| 48 |
+
loading = false;
|
| 49 |
+
error: string | null = null;
|
| 50 |
+
|
| 51 |
+
// Modal states
|
| 52 |
+
showProfileModal = false;
|
| 53 |
+
showEditModal = false;
|
| 54 |
+
showChatWindow = false;
|
| 55 |
+
showLikedDropdown = false;
|
| 56 |
+
showTyping = false;
|
| 57 |
+
|
| 58 |
+
// User data
|
| 59 |
+
userId: number = 1;
|
| 60 |
+
userPhoto = 'https://randomuser.me/api/portraits/men/15.jpg';
|
| 61 |
+
userName = 'Ravi Kumar';
|
| 62 |
+
userCity = 'Chennai, India';
|
| 63 |
+
userProfileCompletion = 80;
|
| 64 |
+
|
| 65 |
+
// Edit profile
|
| 66 |
+
editName = '';
|
| 67 |
+
editCity = '';
|
| 68 |
+
|
| 69 |
+
// Filter state
|
| 70 |
+
searchTerm = '';
|
| 71 |
+
locationFilter = '';
|
| 72 |
+
ageFilter = '';
|
| 73 |
+
professionFilter = '';
|
| 74 |
+
scoreFilter = 0;
|
| 75 |
+
sortBy = 'compatibility';
|
| 76 |
+
|
| 77 |
+
// Data
|
| 78 |
+
allMatches: Profile[] = [];
|
| 79 |
+
filteredMatches: Profile[] = [];
|
| 80 |
+
selectedProfile: Profile | null = null;
|
| 81 |
+
currentChatUser: Profile | null = null;
|
| 82 |
+
|
| 83 |
+
// Chat state
|
| 84 |
+
chatMessages: ChatMessage[] = [];
|
| 85 |
+
newMessage = '';
|
| 86 |
+
typingTimeout: any = null;
|
| 87 |
+
|
| 88 |
+
// Stats
|
| 89 |
+
totalMatches = 0;
|
| 90 |
+
|
| 91 |
+
// Available filters
|
| 92 |
+
availableLocations: string[] = ['Chennai', 'Bengaluru', 'Hyderabad', 'Mumbai', 'Delhi'];
|
| 93 |
+
availableAgeRanges: string[] = ['18-25', '26-30', '31-35', '36-40', '41+'];
|
| 94 |
+
availableProfessions: string[] = ['Software Engineer', 'Doctor', 'Teacher', 'Business', 'Student', 'Architect', 'Data Scientist', 'Marketing Analyst'];
|
| 95 |
+
|
| 96 |
+
// Like functionality
|
| 97 |
+
likedProfiles: Profile[] = [];
|
| 98 |
+
|
| 99 |
+
constructor(
|
| 100 |
+
private router: Router,
|
| 101 |
+
private matchingService: MatchingService
|
| 102 |
+
) { }
|
| 103 |
+
|
| 104 |
+
async ngOnInit() {
|
| 105 |
+
this.loadMatches();
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
get displayedMatches(): Profile[] {
|
| 109 |
+
return this.filteredMatches;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
async loadMatches() {
|
| 113 |
+
this.loading = true;
|
| 114 |
+
this.error = null;
|
| 115 |
+
|
| 116 |
+
try {
|
| 117 |
+
this.matchingService.getMatches(this.userId, {
|
| 118 |
+
role: 'marriage',
|
| 119 |
+
limit: 50,
|
| 120 |
+
excludeSelf: true
|
| 121 |
+
}).subscribe({
|
| 122 |
+
next: (response: ApiMatchResponse) => {
|
| 123 |
+
this.allMatches = response.matches.map((match: ApiMatchItem) => ({
|
| 124 |
+
...match,
|
| 125 |
+
id: match.user_id,
|
| 126 |
+
user_id: match.user_id,
|
| 127 |
+
name: match.name || `User ${match.user_id}`,
|
| 128 |
+
match_score: match.score,
|
| 129 |
+
age: match.age || Math.floor(Math.random() * 15) + 25,
|
| 130 |
+
city: match.city || this.availableLocations[Math.floor(Math.random() * this.availableLocations.length)],
|
| 131 |
+
job: match.role || this.availableProfessions[Math.floor(Math.random() * this.availableProfessions.length)],
|
| 132 |
+
profession: match.role || this.availableProfessions[Math.floor(Math.random() * this.availableProfessions.length)],
|
| 133 |
+
isOnline: Math.random() > 0.5,
|
| 134 |
+
photo_url: match.photo_url || `https://randomuser.me/api/portraits/${Math.random() > 0.5 ? 'men' : 'women'}/${Math.floor(Math.random() * 50)}.jpg`,
|
| 135 |
+
gender: Math.random() > 0.5 ? 'Male' : 'Female',
|
| 136 |
+
meetsExpectations: false,
|
| 137 |
+
expectationMatchScore: 0,
|
| 138 |
+
aiExplanation: this.generateAIExplanation(match),
|
| 139 |
+
hobbies: ['Reading', 'Travel', 'Music'].slice(0, Math.floor(Math.random() * 3) + 1),
|
| 140 |
+
education: ['Bachelor', 'Master', 'PhD'][Math.floor(Math.random() * 3)],
|
| 141 |
+
location: match.city || 'Unknown'
|
| 142 |
+
}));
|
| 143 |
+
|
| 144 |
+
this.applyFilters();
|
| 145 |
+
this.totalMatches = this.allMatches.length;
|
| 146 |
+
this.loading = false;
|
| 147 |
+
},
|
| 148 |
+
error: (err: any) => {
|
| 149 |
+
console.error('Error loading matches:', err);
|
| 150 |
+
this.error = 'Failed to load matches. Please try again.';
|
| 151 |
+
this.loading = false;
|
| 152 |
+
}
|
| 153 |
+
});
|
| 154 |
+
|
| 155 |
+
} catch (err) {
|
| 156 |
+
this.error = 'Failed to load matches. Please try again.';
|
| 157 |
+
this.loading = false;
|
| 158 |
+
}
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
generateAIExplanation(profile: ApiMatchItem): string {
|
| 162 |
+
const traits = [];
|
| 163 |
+
if (profile.blue > 60) traits.push('analytical thinking');
|
| 164 |
+
if (profile.green > 60) traits.push('empathetic nature');
|
| 165 |
+
if (profile.yellow > 60) traits.push('creative approach');
|
| 166 |
+
if (profile.red > 60) traits.push('assertive communication');
|
| 167 |
+
|
| 168 |
+
if (traits.length > 0) {
|
| 169 |
+
return `Strong compatibility in ${traits.join(' and ')} based on personality assessment.`;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
return 'Good overall compatibility with balanced personality traits.';
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
applyFilters() {
|
| 176 |
+
let filtered = [...this.allMatches];
|
| 177 |
+
|
| 178 |
+
// Apply search filter
|
| 179 |
+
if (this.searchTerm) {
|
| 180 |
+
const term = this.searchTerm.toLowerCase();
|
| 181 |
+
filtered = filtered.filter(profile =>
|
| 182 |
+
profile.name?.toLowerCase().includes(term) ||
|
| 183 |
+
profile.city?.toLowerCase().includes(term) ||
|
| 184 |
+
profile.job?.toLowerCase().includes(term) ||
|
| 185 |
+
profile.hobbies?.some(hobby => hobby.toLowerCase().includes(term))
|
| 186 |
+
);
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
// Apply location filter
|
| 190 |
+
if (this.locationFilter) {
|
| 191 |
+
filtered = filtered.filter(profile =>
|
| 192 |
+
profile.city?.toLowerCase().includes(this.locationFilter.toLowerCase())
|
| 193 |
+
);
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
// Apply age filter
|
| 197 |
+
if (this.ageFilter) {
|
| 198 |
+
filtered = filtered.filter(profile => {
|
| 199 |
+
if (!profile.age) return false;
|
| 200 |
+
return this.doesAgeMatch(profile.age, this.ageFilter);
|
| 201 |
+
});
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
// Apply profession filter
|
| 205 |
+
if (this.professionFilter) {
|
| 206 |
+
filtered = filtered.filter(profile =>
|
| 207 |
+
profile.profession?.toLowerCase().includes(this.professionFilter.toLowerCase())
|
| 208 |
+
);
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
// Apply score filter
|
| 212 |
+
if (this.scoreFilter > 0) {
|
| 213 |
+
filtered = filtered.filter(profile =>
|
| 214 |
+
(profile.match_score || profile.score) >= this.scoreFilter
|
| 215 |
+
);
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
// Apply sorting
|
| 219 |
+
filtered.sort((a, b) => {
|
| 220 |
+
switch (this.sortBy) {
|
| 221 |
+
case 'compatibility':
|
| 222 |
+
return (b.match_score || b.score) - (a.match_score || a.score);
|
| 223 |
+
case 'newest':
|
| 224 |
+
return Math.random() - 0.5; // Random for demo
|
| 225 |
+
case 'location':
|
| 226 |
+
return a.city?.localeCompare(b.city || '') || 0;
|
| 227 |
+
default:
|
| 228 |
+
return 0;
|
| 229 |
+
}
|
| 230 |
+
});
|
| 231 |
+
|
| 232 |
+
this.filteredMatches = filtered;
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
doesAgeMatch(age: number, ageRange: string): boolean {
|
| 236 |
+
const ranges: { [key: string]: [number, number] } = {
|
| 237 |
+
'18-25': [18, 25],
|
| 238 |
+
'26-30': [26, 30],
|
| 239 |
+
'31-35': [31, 35],
|
| 240 |
+
'36-40': [36, 40],
|
| 241 |
+
'41+': [41, 100]
|
| 242 |
+
};
|
| 243 |
+
|
| 244 |
+
const range = ranges[ageRange];
|
| 245 |
+
return range ? age >= range[0] && age <= range[1] : false;
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
// Filter handlers
|
| 249 |
+
onSearchChange() {
|
| 250 |
+
this.applyFilters();
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
onLocationFilterChange(event: any) {
|
| 254 |
+
this.locationFilter = event.target.value;
|
| 255 |
+
this.applyFilters();
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
onAgeFilterChange(event: any) {
|
| 259 |
+
this.ageFilter = event.target.value;
|
| 260 |
+
this.applyFilters();
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
onProfessionFilterChange(event: any) {
|
| 264 |
+
this.professionFilter = event.target.value;
|
| 265 |
+
this.applyFilters();
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
onScoreFilterChange(event: any) {
|
| 269 |
+
this.scoreFilter = parseInt(event.target.value);
|
| 270 |
+
this.applyFilters();
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
onSortChange(event: any) {
|
| 274 |
+
this.sortBy = event.target.value;
|
| 275 |
+
this.applyFilters();
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
// Profile actions
|
| 279 |
+
openModal(profile: Profile) {
|
| 280 |
+
this.selectedProfile = profile;
|
| 281 |
+
this.showProfileModal = true;
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
closeModal() {
|
| 285 |
+
this.showProfileModal = false;
|
| 286 |
+
this.selectedProfile = null;
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
openProfileModal(profile: Profile) {
|
| 290 |
+
this.selectedProfile = profile;
|
| 291 |
+
this.showProfileModal = true;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
toggleLike(profile: Profile) {
|
| 295 |
+
const index = this.likedProfiles.findIndex(p => p.user_id === profile.user_id);
|
| 296 |
+
|
| 297 |
+
if (index > -1) {
|
| 298 |
+
this.likedProfiles.splice(index, 1);
|
| 299 |
+
} else {
|
| 300 |
+
this.likedProfiles.push(profile);
|
| 301 |
+
this.triggerConfetti();
|
| 302 |
+
}
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
isLiked(profile: Profile): boolean {
|
| 306 |
+
return this.likedProfiles.some(p => p.user_id === profile.user_id);
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
triggerConfetti() {
|
| 310 |
+
// Simple confetti effect - you can enhance this with a proper confetti library
|
| 311 |
+
console.log('Confetti effect triggered!');
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
toggleLikedDropdown() {
|
| 315 |
+
this.showLikedDropdown = !this.showLikedDropdown;
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
// Edit profile
|
| 319 |
+
openEditModal() {
|
| 320 |
+
this.editName = this.userName;
|
| 321 |
+
this.editCity = this.userCity.split(', ')[0]; // Remove country for edit
|
| 322 |
+
this.showEditModal = true;
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
closeEditModal() {
|
| 326 |
+
this.showEditModal = false;
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
saveProfile() {
|
| 330 |
+
this.userName = this.editName;
|
| 331 |
+
this.userCity = this.editCity + ', India';
|
| 332 |
+
this.userProfileCompletion = 100;
|
| 333 |
+
this.showEditModal = false;
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
// Chat functionality
|
| 337 |
+
openChat(profile: Profile) {
|
| 338 |
+
this.currentChatUser = profile;
|
| 339 |
+
this.showChatWindow = true;
|
| 340 |
+
this.loadChat(profile);
|
| 341 |
+
setTimeout(() => {
|
| 342 |
+
this.scrollChatToBottom();
|
| 343 |
+
}, 100);
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
closeChat() {
|
| 347 |
+
this.showChatWindow = false;
|
| 348 |
+
this.currentChatUser = null;
|
| 349 |
+
this.chatMessages = [];
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
typingIndicator() {
|
| 353 |
+
this.showTyping = true;
|
| 354 |
+
if (this.typingTimeout) {
|
| 355 |
+
clearTimeout(this.typingTimeout);
|
| 356 |
+
}
|
| 357 |
+
this.typingTimeout = setTimeout(() => {
|
| 358 |
+
this.showTyping = false;
|
| 359 |
+
}, 1500);
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
sendMessage() {
|
| 363 |
+
const text = this.newMessage.trim();
|
| 364 |
+
if (!text || !this.currentChatUser) return;
|
| 365 |
+
|
| 366 |
+
const message: ChatMessage = {
|
| 367 |
+
text,
|
| 368 |
+
sender: 'me',
|
| 369 |
+
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
| 370 |
+
};
|
| 371 |
+
|
| 372 |
+
this.chatMessages.push(message);
|
| 373 |
+
this.saveMessage(this.currentChatUser, message);
|
| 374 |
+
this.newMessage = '';
|
| 375 |
+
this.scrollChatToBottom();
|
| 376 |
+
|
| 377 |
+
// Simulate reply after 1 second
|
| 378 |
+
setTimeout(() => {
|
| 379 |
+
const reply: ChatMessage = {
|
| 380 |
+
text: `${this.currentChatUser?.name}: Nice to hear from you!`,
|
| 381 |
+
sender: 'them',
|
| 382 |
+
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
| 383 |
+
};
|
| 384 |
+
this.chatMessages.push(reply);
|
| 385 |
+
this.saveMessage(this.currentChatUser!, reply);
|
| 386 |
+
this.scrollChatToBottom();
|
| 387 |
+
}, 1000);
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
saveMessage(user: Profile, message: ChatMessage) {
|
| 391 |
+
const key = `chat_${user.user_id}`;
|
| 392 |
+
let arr = JSON.parse(localStorage.getItem(key) || '[]');
|
| 393 |
+
arr.push(message);
|
| 394 |
+
localStorage.setItem(key, JSON.stringify(arr));
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
loadChat(user: Profile) {
|
| 398 |
+
const key = `chat_${user.user_id}`;
|
| 399 |
+
const arr = JSON.parse(localStorage.getItem(key) || '[]');
|
| 400 |
+
this.chatMessages = arr;
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
scrollChatToBottom() {
|
| 404 |
+
if (this.chatBody) {
|
| 405 |
+
this.chatBody.nativeElement.scrollTop = this.chatBody.nativeElement.scrollHeight;
|
| 406 |
+
}
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
// Compatibility report
|
| 410 |
+
getCompatibilityReport(profile: Profile): string {
|
| 411 |
+
const colorSet = {
|
| 412 |
+
B: Math.floor(Math.random() * 40),
|
| 413 |
+
G: Math.floor(Math.random() * 40),
|
| 414 |
+
Y: Math.floor(Math.random() * 40),
|
| 415 |
+
R: Math.floor(Math.random() * 40),
|
| 416 |
+
};
|
| 417 |
+
const colorMatch = (Math.random() * (0.9 - 0.6) + 0.6).toFixed(2);
|
| 418 |
+
const expectationMatch = (Math.random() * (0.9 - 0.6) + 0.6).toFixed(2);
|
| 419 |
+
|
| 420 |
+
const differences = [
|
| 421 |
+
"Their religion or faith expectations differ (same faith vs no preference).",
|
| 422 |
+
"Both share similar preferences in smoking preference (Occasionally).",
|
| 423 |
+
"Their alcohol preference expectations differ (no preference vs never).",
|
| 424 |
+
"Their dietary habits expectations differ (vegetarian vs no preference).",
|
| 425 |
+
"Both share similar preferences in family type (Either).",
|
| 426 |
+
"Their fitness lifestyle expectations differ (high vs moderate).",
|
| 427 |
+
"Their preferred location expectations differ (pune vs kolkata).",
|
| 428 |
+
"Their preferred country expectations differ (other vs australia).",
|
| 429 |
+
];
|
| 430 |
+
|
| 431 |
+
return `
|
| 432 |
+
<div class="compatibility-report">
|
| 433 |
+
<p><strong>${profile.name}</strong> (${profile.gender || 'Male'})</p>
|
| 434 |
+
<p><strong>Overall Match:</strong> ${profile.match_score || profile.score}%</p>
|
| 435 |
+
<p><strong>Colors:</strong> B${colorSet.B} • G${colorSet.G} • Y${colorSet.Y} • R${colorSet.R}</p>
|
| 436 |
+
<p><strong>Color Match:</strong> ${colorMatch} • <strong>Expectation Match:</strong> ${expectationMatch}</p>
|
| 437 |
+
<br>
|
| 438 |
+
<div class="quadratic-section">
|
| 439 |
+
<h4>1️⃣ How this profile matches to you</h4>
|
| 440 |
+
<p>${profile.name} shows high compatibility in emotional stability, career orientation, and communication approach. You both value similar ethics, decision-making styles, and emotional awareness.</p>
|
| 441 |
+
|
| 442 |
+
<h4>2️⃣ Why the remaining percentage does not match</h4>
|
| 443 |
+
<p>The mismatch comes mainly from lifestyle and belief areas. Factors like faith flexibility, preferred diet, and regional lifestyle contribute to the gap.</p>
|
| 444 |
+
|
| 445 |
+
<h4>3️⃣ What you may need to sacrifice</h4>
|
| 446 |
+
<p>For smoother compatibility, consider being more adaptive toward dietary and cultural choices, and show patience toward different viewpoints in lifestyle preferences.</p>
|
| 447 |
+
|
| 448 |
+
<h4>4️⃣ Expectation-wise Comparison</h4>
|
| 449 |
+
<ul>
|
| 450 |
+
${differences
|
| 451 |
+
.map(
|
| 452 |
+
(d) =>
|
| 453 |
+
`<li style="color:${d.includes("similar") ? "green" : "red"
|
| 454 |
+
}">${d}</li>`
|
| 455 |
+
)
|
| 456 |
+
.join("")}
|
| 457 |
+
</ul>
|
| 458 |
+
</div>
|
| 459 |
+
</div>`;
|
| 460 |
+
}
|
| 461 |
+
|
| 462 |
+
// Utility functions
|
| 463 |
+
handleImageError(event: any, profile: Profile) {
|
| 464 |
+
event.target.src = 'https://via.placeholder.com/300?text=' + (profile.name || 'User');
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
@HostListener('document:click', ['$event'])
|
| 468 |
+
handleDocumentClick(event: MouseEvent) {
|
| 469 |
+
const target = event.target as HTMLElement;
|
| 470 |
+
if (!target.closest('.like-section')) {
|
| 471 |
+
this.showLikedDropdown = false;
|
| 472 |
+
}
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
@HostListener('document:keydown.escape')
|
| 476 |
+
handleEscape() {
|
| 477 |
+
this.closeModal();
|
| 478 |
+
this.closeEditModal();
|
| 479 |
+
this.closeChat();
|
| 480 |
+
}
|
| 481 |
+
}
|
src/app/matching.service.spec.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@angular/core';
|
| 2 |
+
import { HttpClient, HttpParams } from '@angular/common/http';
|
| 3 |
+
import { Observable } from 'rxjs';
|
| 4 |
+
|
| 5 |
+
export interface ApiMatchItem {
|
| 6 |
+
user_id: number;
|
| 7 |
+
role?: string | null;
|
| 8 |
+
name?: string;
|
| 9 |
+
gender?: string;
|
| 10 |
+
blue: number;
|
| 11 |
+
green: number;
|
| 12 |
+
yellow: number;
|
| 13 |
+
red: number;
|
| 14 |
+
score: number;
|
| 15 |
+
created_at?: string | null;
|
| 16 |
+
explanations?: string[];
|
| 17 |
+
// Optional display fields that may not come from API but used in templates
|
| 18 |
+
city?: string | null;
|
| 19 |
+
age?: number | null;
|
| 20 |
+
job?: string | null;
|
| 21 |
+
isOnline?: boolean; // optional presence indicator for UI
|
| 22 |
+
photo_url?: string; // optional photo URL for profile image
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
export interface ApiMatchResponse {
|
| 26 |
+
input_user: {
|
| 27 |
+
user_id: number;
|
| 28 |
+
role?: string | null;
|
| 29 |
+
blue: number;
|
| 30 |
+
green: number;
|
| 31 |
+
yellow: number;
|
| 32 |
+
red: number;
|
| 33 |
+
created_at?: string | null;
|
| 34 |
+
};
|
| 35 |
+
matches: ApiMatchItem[];
|
| 36 |
+
count: number;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
@Injectable({ providedIn: 'root' })
|
| 40 |
+
export class MatchingService {
|
| 41 |
+
private readonly base = 'http://127.0.0.1:5000'; // Flask backend
|
| 42 |
+
|
| 43 |
+
constructor(private http: HttpClient) { }
|
| 44 |
+
|
| 45 |
+
getMatches(
|
| 46 |
+
userId: number,
|
| 47 |
+
opts?: { role?: string; limit?: number; excludeSelf?: boolean }
|
| 48 |
+
): Observable<ApiMatchResponse> {
|
| 49 |
+
let params = new HttpParams()
|
| 50 |
+
.set('user_id', String(userId))
|
| 51 |
+
.set('limit', String(opts?.limit ?? 10))
|
| 52 |
+
.set('exclude_self', (opts?.excludeSelf ?? true) ? 'yes' : 'no');
|
| 53 |
+
|
| 54 |
+
// 👇 Default to "marriage" if role is not explicitly passed
|
| 55 |
+
params = params.set('role', opts?.role ?? 'marriage');
|
| 56 |
+
|
| 57 |
+
return this.http.get<ApiMatchResponse>(`${this.base}/match`, { params });
|
| 58 |
+
}
|
| 59 |
+
}
|
src/app/matching.service.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@angular/core';
|
| 2 |
+
import { HttpClient, HttpParams } from '@angular/common/http';
|
| 3 |
+
import { Observable } from 'rxjs';
|
| 4 |
+
|
| 5 |
+
export interface ApiMatchItem {
|
| 6 |
+
user_id: number;
|
| 7 |
+
role?: string | null;
|
| 8 |
+
name?: string;
|
| 9 |
+
gender?: string;
|
| 10 |
+
blue: number;
|
| 11 |
+
green: number;
|
| 12 |
+
yellow: number;
|
| 13 |
+
red: number;
|
| 14 |
+
score: number;
|
| 15 |
+
created_at?: string | null;
|
| 16 |
+
explanations?: string[];
|
| 17 |
+
// Optional display fields that may not come from API but used in templates
|
| 18 |
+
city?: string | null;
|
| 19 |
+
age?: number | null;
|
| 20 |
+
job?: string | null;
|
| 21 |
+
isOnline?: boolean; // optional presence indicator for UI
|
| 22 |
+
photo_url?: string; // optional photo URL for profile image
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
export interface ApiMatchResponse {
|
| 26 |
+
input_user: {
|
| 27 |
+
user_id: number;
|
| 28 |
+
role?: string | null;
|
| 29 |
+
blue: number;
|
| 30 |
+
green: number;
|
| 31 |
+
yellow: number;
|
| 32 |
+
red: number;
|
| 33 |
+
created_at?: string | null;
|
| 34 |
+
};
|
| 35 |
+
matches: ApiMatchItem[];
|
| 36 |
+
count: number;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
@Injectable({ providedIn: 'root' })
|
| 40 |
+
export class MatchingService {
|
| 41 |
+
|
| 42 |
+
private baseUrl: string;
|
| 43 |
+
|
| 44 |
+
constructor(private http: HttpClient) {
|
| 45 |
+
// Initialize baseUrl based on the current hostname
|
| 46 |
+
this.baseUrl = typeof location !== 'undefined' && location.hostname.endsWith('hf.space')
|
| 47 |
+
? 'https://pykara-py-match-backend.hf.space/api'
|
| 48 |
+
: 'http://localhost:5000/api';
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
getMatches(
|
| 52 |
+
userId: number,
|
| 53 |
+
opts?: { role?: string; limit?: number; excludeSelf?: boolean }
|
| 54 |
+
): Observable<ApiMatchResponse> {
|
| 55 |
+
let params = new HttpParams()
|
| 56 |
+
.set('user_id', String(userId))
|
| 57 |
+
.set('limit', String(opts?.limit ?? 10))
|
| 58 |
+
.set('exclude_self', (opts?.excludeSelf ?? true) ? 'yes' : 'no');
|
| 59 |
+
|
| 60 |
+
// 👇 Default to "marriage" if role is not explicitly passed
|
| 61 |
+
params = params.set('role', opts?.role ?? 'marriage');
|
| 62 |
+
|
| 63 |
+
return this.http.get<ApiMatchResponse>(`${this.baseUrl}/match`, { params });
|
| 64 |
+
}
|
| 65 |
+
}
|
src/app/question-answer/question-answer-service.service.ts
CHANGED
|
@@ -2,35 +2,41 @@ import { Injectable } from '@angular/core';
|
|
| 2 |
import { HttpClient } from '@angular/common/http';
|
| 3 |
import { environment } from '../../environments/environment';
|
| 4 |
import { Observable } from 'rxjs';
|
|
|
|
| 5 |
export interface QAItem {
|
| 6 |
label: string;
|
| 7 |
-
input_type: 'text' | 'date' | 'radio' | '
|
| 8 |
options?: string[];
|
| 9 |
column_key: string;
|
|
|
|
|
|
|
|
|
|
| 10 |
}
|
|
|
|
| 11 |
@Injectable({ providedIn: 'root' })
|
| 12 |
export class QuestionAnswerService {
|
| 13 |
-
|
| 14 |
-
private baseUrl = location.hostname.endsWith('hf.space')
|
| 15 |
-
? 'https://pykara-py-match-backend.hf.space/api/questions'
|
| 16 |
-
: 'http://localhost:5000/api/questions';
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
// Assign role to user
|
| 23 |
-
assignRole(payload: { user_id: number; role_name: string; assigned_at: string }): Observable<any> {
|
| 24 |
return this.http.post<any>(`${this.baseUrl}/select-role`, payload);
|
| 25 |
}
|
| 26 |
|
| 27 |
-
|
| 28 |
getQuestions(role: string): Observable<QAItem[]> {
|
| 29 |
return this.http.get<QAItem[]>(`${this.baseUrl}/${role}`);
|
| 30 |
}
|
| 31 |
|
| 32 |
-
// fields = { full_name: 'John', created_at: '...' }
|
| 33 |
submitAnswers(role: string, userId: number, fields: Record<string, any>): Observable<any> {
|
| 34 |
-
|
|
|
|
|
|
|
| 35 |
}
|
| 36 |
}
|
|
|
|
| 2 |
import { HttpClient } from '@angular/common/http';
|
| 3 |
import { environment } from '../../environments/environment';
|
| 4 |
import { Observable } from 'rxjs';
|
| 5 |
+
|
| 6 |
export interface QAItem {
|
| 7 |
label: string;
|
| 8 |
+
input_type: 'text' | 'date' | 'select' | 'radio' | 'multiselect';
|
| 9 |
options?: string[];
|
| 10 |
column_key: string;
|
| 11 |
+
category: string;
|
| 12 |
+
required?: boolean;
|
| 13 |
+
placeholder?: string;
|
| 14 |
}
|
| 15 |
+
|
| 16 |
@Injectable({ providedIn: 'root' })
|
| 17 |
export class QuestionAnswerService {
|
| 18 |
+
private baseUrl: string;
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
+
constructor(private http: HttpClient) {
|
| 21 |
+
// Initialize baseUrl based on the current hostname
|
| 22 |
+
this.baseUrl = typeof location !== 'undefined' && location.hostname.endsWith('hf.space')
|
| 23 |
+
? 'https://pykara-py-match-backend.hf.space/api/questions'
|
| 24 |
+
: 'http://127.0.0.1:5000/api/questions';
|
| 25 |
+
}
|
| 26 |
|
| 27 |
// Assign role to user
|
| 28 |
+
assignRole(payload: { user_id: number; role_name: string; assigned_at?: string }): Observable<any> {
|
| 29 |
return this.http.post<any>(`${this.baseUrl}/select-role`, payload);
|
| 30 |
}
|
| 31 |
|
| 32 |
+
// Fetch questions by role (returns label/options/input_type/column_key/category)
|
| 33 |
getQuestions(role: string): Observable<QAItem[]> {
|
| 34 |
return this.http.get<QAItem[]>(`${this.baseUrl}/${role}`);
|
| 35 |
}
|
| 36 |
|
|
|
|
| 37 |
submitAnswers(role: string, userId: number, fields: Record<string, any>): Observable<any> {
|
| 38 |
+
const payload = { user_id: userId, ...fields };
|
| 39 |
+
console.log(payload);
|
| 40 |
+
return this.http.post(`${this.baseUrl}/submit-answers/${role}`, payload);
|
| 41 |
}
|
| 42 |
}
|
src/app/question-answer/question-answer.component.css
CHANGED
|
@@ -1,317 +1,693 @@
|
|
| 1 |
-
/*
|
| 2 |
-
.
|
| 3 |
-
height:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
position: relative;
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
overflow: hidden;
|
| 10 |
}
|
| 11 |
|
| 12 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
position: relative;
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
align-items: center;
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
font-weight: 600;
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
}
|
| 34 |
|
| 35 |
-
.
|
| 36 |
-
|
| 37 |
-
|
| 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 |
-
position: absolute;
|
| 66 |
-
inset: 0;
|
| 67 |
-
z-index: 0;
|
| 68 |
-
pointer-events: none;
|
| 69 |
-
background: #e9cfad57;
|
| 70 |
-
opacity: 0.8;
|
| 71 |
-
top: 50%;
|
| 72 |
-
left: 50%;
|
| 73 |
-
transform: translate(-50%, -50%);
|
| 74 |
-
height: 28vw;
|
| 75 |
-
margin: 0 auto;
|
| 76 |
-
border-radius: 8vw;
|
| 77 |
-
border: 2px solid #e9cfad;
|
| 78 |
}
|
| 79 |
|
| 80 |
-
.
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
transform: translate(-50%, -50%);
|
| 86 |
}
|
| 87 |
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
display: grid;
|
| 95 |
-
place-items: center;
|
| 96 |
-
border: 10px solid rgba(255,255,255,0.9); /* white rim to mimic gaps */
|
| 97 |
-
box-shadow: 0 28px 60px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.6);
|
| 98 |
-
position: absolute;
|
| 99 |
-
cursor: pointer;
|
| 100 |
-
text-align: center;
|
| 101 |
-
text-transform: uppercase;
|
| 102 |
-
font-weight: 800;
|
| 103 |
-
letter-spacing: .04em;
|
| 104 |
-
transition: transform .18s ease, box-shadow .18s ease, filter .18s ease;
|
| 105 |
}
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
}
|
| 112 |
-
|
| 113 |
-
.
|
| 114 |
-
|
| 115 |
-
color: #2b2316;
|
| 116 |
}
|
| 117 |
-
|
| 118 |
-
.
|
| 119 |
-
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
}
|
| 122 |
-
/* soft tan */
|
| 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 |
-
padding: 0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
}
|
| 166 |
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
position: fixed;
|
| 171 |
top: 0;
|
| 172 |
left: 0;
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
background: rgba(0, 0, 0, 0.
|
| 176 |
display: flex;
|
| 177 |
justify-content: center;
|
| 178 |
align-items: center;
|
| 179 |
z-index: 1000;
|
| 180 |
-
|
| 181 |
}
|
| 182 |
|
| 183 |
-
/* Modal Content */
|
| 184 |
.modal {
|
| 185 |
-
|
| 186 |
border-radius: 20px;
|
| 187 |
-
|
| 188 |
-
max-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
|
|
|
| 192 |
}
|
| 193 |
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
}
|
| 205 |
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
font-weight: 700;
|
| 209 |
-
color: #fff;
|
| 210 |
-
position: absolute;
|
| 211 |
-
top: 20px;
|
| 212 |
-
left: 20px;
|
| 213 |
-
z-index: 2;
|
| 214 |
-
background-color: rgba(0, 0, 0, 0.5); /* Background for readability */
|
| 215 |
-
padding: 5px 10px;
|
| 216 |
-
border-radius: 5px;
|
| 217 |
}
|
| 218 |
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
width: 50%;
|
| 222 |
-
padding: 20px;
|
| 223 |
-
color: #eb4814;
|
| 224 |
-
background-color: #fbeccf;
|
| 225 |
-
overflow-y: auto;
|
| 226 |
}
|
|
|
|
| 227 |
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
}
|
| 236 |
|
| 237 |
-
.
|
| 238 |
-
|
| 239 |
}
|
| 240 |
|
| 241 |
-
.
|
| 242 |
-
|
|
|
|
| 243 |
}
|
| 244 |
|
| 245 |
-
.
|
| 246 |
-
|
| 247 |
-
border-radius: 5px;
|
| 248 |
-
border: 1px solid #ccc;
|
| 249 |
width: 100%;
|
| 250 |
-
margin-top: 10px;
|
| 251 |
}
|
| 252 |
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
top: 10px;
|
| 257 |
-
right: 25px;
|
| 258 |
-
background: transparent;
|
| 259 |
-
font-size: 24px;
|
| 260 |
-
color: #aaa;
|
| 261 |
-
cursor: pointer;
|
| 262 |
-
transition: color 0.3s;
|
| 263 |
-
width: 2vw;
|
| 264 |
-
border: 3px solid #eb48149c;
|
| 265 |
-
padding: 1px;
|
| 266 |
-
}
|
| 267 |
|
| 268 |
-
.
|
| 269 |
-
|
|
|
|
|
|
|
| 270 |
}
|
| 271 |
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
font-size: 16px;
|
| 280 |
-
cursor: pointer;
|
| 281 |
-
transition: background-color 0.3s ease, transform 0.2s ease;
|
| 282 |
-
width: 100%;
|
| 283 |
-
text-align: center;
|
| 284 |
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
| 285 |
}
|
| 286 |
|
| 287 |
-
|
| 288 |
-
|
|
|
|
| 289 |
}
|
| 290 |
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
outline-offset: 3px;
|
| 294 |
}
|
| 295 |
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
from {
|
| 299 |
-
opacity: 0;
|
| 300 |
}
|
| 301 |
|
| 302 |
-
|
| 303 |
-
|
| 304 |
}
|
| 305 |
-
}
|
| 306 |
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
transform: translateY(20px);
|
| 310 |
-
opacity: 0;
|
| 311 |
}
|
| 312 |
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
opacity: 1;
|
| 316 |
}
|
| 317 |
}
|
|
|
|
|
|
| 1 |
+
/* Professional Styles for Question Answer Component - Updated to match intro theme */
|
| 2 |
+
.professional-page {
|
| 3 |
+
min-height: 100vh;
|
| 4 |
+
background: #f8fafc;
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
/* Loader */
|
| 8 |
+
.loader-overlay {
|
| 9 |
+
position: fixed;
|
| 10 |
+
inset: 0;
|
| 11 |
+
background: rgba(12, 18, 28, 0.7);
|
| 12 |
+
display: grid;
|
| 13 |
+
place-items: center;
|
| 14 |
+
z-index: 9999;
|
| 15 |
+
backdrop-filter: blur(5px);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
.loader-container {
|
| 19 |
+
text-align: center;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.loader-gif {width: 50%; height: 50%;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
/* Hero Section */
|
| 26 |
+
.hero {
|
| 27 |
position: relative;
|
| 28 |
+
width: 100%;
|
| 29 |
+
min-height: 70vh;
|
| 30 |
+
display: flex;
|
| 31 |
+
align-items: center;
|
|
|
|
| 32 |
}
|
| 33 |
|
| 34 |
+
.hero .content {
|
| 35 |
+
width: 100%;
|
| 36 |
+
padding: 80px 5%;
|
| 37 |
+
color: white;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.info-card {
|
| 41 |
+
display: flex;
|
| 42 |
+
gap: 2rem;
|
| 43 |
+
margin: 0 auto;
|
| 44 |
+
background: rgb(255 255 255 / 52%);
|
| 45 |
+
border-radius: 24px;
|
| 46 |
+
padding: 3rem;
|
| 47 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 48 |
+
align-items: center;
|
| 49 |
+
width: fit-content;
|
| 50 |
position: relative;
|
| 51 |
+
top: 50%;
|
| 52 |
+
transform: translatey(-50%);
|
| 53 |
+
right: 24vw;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
.info-card__icon {
|
| 57 |
+
flex: 0 0 100px;
|
| 58 |
+
width: 100px;
|
| 59 |
+
height: 100px;
|
| 60 |
+
border-radius: 50%;
|
| 61 |
+
background: linear-gradient(135deg, #f3a54c, #ffcb7a);
|
| 62 |
+
display: flex;
|
| 63 |
align-items: center;
|
| 64 |
+
justify-content: center;
|
| 65 |
+
color: #111;
|
| 66 |
+
font-size: 2.5rem;
|
| 67 |
+
box-shadow: 0 10px 30px rgba(243, 165, 76, 0.3);
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.info-card__content {
|
| 71 |
+
flex: 1;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
.info-card__title {
|
| 75 |
+
margin: 0 0 1rem 0;
|
| 76 |
+
font-size: 2.5rem;
|
| 77 |
+
font-weight: 700;
|
| 78 |
+
color: #1a202c;
|
| 79 |
+
display: flex;
|
| 80 |
+
align-items: center;
|
| 81 |
+
gap: 0.75rem;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.info-card__title i {
|
| 85 |
+
color: #f3a54c;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
.info-card__text {
|
| 89 |
+
margin: 0 0 2rem 0;
|
| 90 |
+
font-size: 1.1rem;
|
| 91 |
+
line-height: 1.6;
|
| 92 |
+
color: black;
|
| 93 |
+
max-width: 600px;
|
| 94 |
+
font-weight: 500;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.info-card__actions {
|
| 98 |
+
display: flex;
|
| 99 |
+
gap: 1rem;
|
| 100 |
+
flex-wrap: wrap;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
/* Buttons */
|
| 104 |
+
.btn {
|
| 105 |
+
display: inline-flex;
|
| 106 |
+
align-items: center;
|
| 107 |
+
gap: 0.5rem;
|
| 108 |
+
padding: 0.875rem 1.75rem;
|
| 109 |
+
border: none;
|
| 110 |
+
border-radius: 12px;
|
| 111 |
+
font-weight: 600;
|
| 112 |
+
font-size: 0.95rem;
|
| 113 |
+
text-decoration: none;
|
| 114 |
cursor: pointer;
|
| 115 |
+
transition: all 0.3s ease;
|
| 116 |
+
font-family: inherit;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
.btn-primary {
|
| 120 |
+
background: linear-gradient(135deg, #f3a54c, #ffcb7a);
|
| 121 |
+
color: #111;
|
| 122 |
+
box-shadow: 0 4px 15px rgba(243, 165, 76, 0.3);
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
.btn-primary:hover {
|
| 126 |
+
transform: translateY(-2px);
|
| 127 |
+
box-shadow: 0 8px 25px rgba(243, 165, 76, 0.4);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
.btn-secondary {
|
| 131 |
+
background: white;
|
| 132 |
+
color: #f3a54c;
|
| 133 |
+
border: 2px solid #f3a54c;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
.btn-secondary:hover {
|
| 137 |
+
background: #f3a54c;
|
| 138 |
+
color: white;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
/* Questions Container */
|
| 142 |
+
.questions-container {
|
| 143 |
+
background: white;
|
| 144 |
+
padding: 5rem 0 1vw 0;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
.container {
|
| 148 |
+
width: 80%;
|
| 149 |
+
margin: 0 auto;
|
| 150 |
+
/* padding: 0 2rem;*/
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
/* Category Navigation */
|
| 154 |
+
.category-nav-wrapper {
|
| 155 |
+
margin-bottom: 3rem;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
.category-nav {
|
| 159 |
+
display: flex;
|
| 160 |
+
gap: 1rem;
|
| 161 |
+
flex-wrap: wrap;
|
| 162 |
+
justify-content: center;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.nav-chip {
|
| 166 |
+
display: inline-flex;
|
| 167 |
+
align-items: center;
|
| 168 |
+
gap: 0.75rem;
|
| 169 |
+
background: white;
|
| 170 |
+
padding: 1rem 1.5rem;
|
| 171 |
+
border-radius: 12px;
|
| 172 |
font-weight: 600;
|
| 173 |
+
color: #4a5568;
|
| 174 |
+
text-decoration: none;
|
| 175 |
+
border: 2px solid #e2e8f0;
|
| 176 |
+
cursor: pointer;
|
| 177 |
+
transition: all 0.3s ease;
|
| 178 |
+
font-family: inherit;
|
| 179 |
}
|
| 180 |
|
| 181 |
+
.nav-chip:hover {
|
| 182 |
+
border-color: #f3a54c;
|
| 183 |
+
transform: translateY(-2px);
|
| 184 |
+
box-shadow: 0 4px 12px rgba(243, 165, 76, 0.15);
|
| 185 |
}
|
| 186 |
|
| 187 |
+
.nav-chip.active {
|
| 188 |
+
background: #f3a54c;
|
| 189 |
+
color: white;
|
| 190 |
+
border-color: #f3a54c;
|
| 191 |
}
|
| 192 |
|
| 193 |
+
.nav-chip.completed {
|
| 194 |
+
background: #00d68f;
|
| 195 |
+
color: #fff;
|
| 196 |
+
border-color: #00d68f;
|
| 197 |
}
|
| 198 |
|
| 199 |
+
.nav-chip.completed i {
|
| 200 |
+
color: #fff;
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
.nav-chip i {
|
| 204 |
+
font-size: 1.1rem;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
.chip-progress {
|
| 208 |
+
background: rgba(255, 255, 255, 0.2);
|
| 209 |
+
padding: 0.25rem 0.75rem;
|
| 210 |
+
border-radius: 20px;
|
| 211 |
+
font-size: 0.85rem;
|
| 212 |
+
font-weight: 600;
|
| 213 |
}
|
| 214 |
|
| 215 |
+
/* Category Content */
|
| 216 |
+
.category-content {
|
| 217 |
+
background: white;
|
| 218 |
+
border-radius: 20px;
|
| 219 |
+
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
|
| 220 |
+
overflow: hidden;
|
| 221 |
+
border: 1px solid #e3e5e8;
|
| 222 |
}
|
| 223 |
|
| 224 |
+
.category-section {
|
| 225 |
+
padding: 2vw 2vw 0.5vw 2vw;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
}
|
| 227 |
|
| 228 |
+
.category-header {
|
| 229 |
+
display: flex;
|
| 230 |
+
gap: 3rem;
|
| 231 |
+
margin-bottom: 3rem;
|
| 232 |
+
align-items: center;
|
|
|
|
| 233 |
}
|
| 234 |
|
| 235 |
+
.category-image {
|
| 236 |
+
flex: 0 0 300px;
|
| 237 |
+
border-radius: 16px;
|
| 238 |
+
overflow: hidden;
|
| 239 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
| 240 |
+
position: relative;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
}
|
| 242 |
|
| 243 |
+
.category-image::before {
|
| 244 |
+
content: "";
|
| 245 |
+
position: absolute;
|
| 246 |
+
inset: 0;
|
| 247 |
+
background: linear-gradient(135deg, rgba(243, 165, 76, 0.1), rgba(0, 255, 136, 0.1));
|
| 248 |
+
z-index: 1;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
.category-image img {
|
| 252 |
+
width: 100%;
|
| 253 |
+
height: 200px;
|
| 254 |
+
object-fit: cover;
|
| 255 |
+
position: relative;
|
| 256 |
+
z-index: 0;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
.category-info {
|
| 260 |
+
flex: 1;
|
| 261 |
}
|
| 262 |
+
|
| 263 |
+
.title-section {
|
| 264 |
+
margin-bottom: 2rem;
|
|
|
|
| 265 |
}
|
| 266 |
+
|
| 267 |
+
.title-section h2 {
|
| 268 |
+
margin: 0 0 1rem 0;
|
| 269 |
+
font-size: 2rem;
|
| 270 |
+
font-weight: 700;
|
| 271 |
+
color: #1a202c;
|
| 272 |
+
display: flex;
|
| 273 |
+
align-items: center;
|
| 274 |
+
gap: 0.75rem;
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
.title-section h2 i {
|
| 278 |
+
color: #f3a54c;
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
.progress-indicator {
|
| 282 |
+
display: flex;
|
| 283 |
+
align-items: center;
|
| 284 |
+
gap: 1rem;
|
| 285 |
}
|
|
|
|
| 286 |
|
| 287 |
+
.progress-bar {
|
| 288 |
+
flex: 1;
|
| 289 |
+
height: 8px;
|
| 290 |
+
background: #e2e8f0;
|
| 291 |
+
border-radius: 4px;
|
| 292 |
+
overflow: hidden;
|
| 293 |
}
|
| 294 |
|
| 295 |
+
.progress-fill {
|
| 296 |
+
height: 100%;
|
| 297 |
+
background: linear-gradient(135deg, #f3a54c, #ffcb7a);
|
| 298 |
+
border-radius: 4px;
|
| 299 |
+
transition: width 0.4s ease;
|
| 300 |
}
|
| 301 |
|
| 302 |
+
.progress-indicator small {
|
| 303 |
+
font-size: 0.9rem;
|
| 304 |
+
font-weight: 600;
|
| 305 |
+
color: #4a5568;
|
| 306 |
+
min-width: 80px;
|
| 307 |
}
|
| 308 |
|
| 309 |
+
/* Questions Grid - Compact 3-column layout */
|
| 310 |
+
.questions-grid {
|
| 311 |
+
display: grid;
|
| 312 |
+
grid-template-columns: repeat(4, 1fr);
|
| 313 |
+
gap: 1.5rem;
|
| 314 |
+
}
|
| 315 |
+
.checkbox-options{
|
| 316 |
+
overflow-y:scroll;
|
| 317 |
+
height:3vh;
|
| 318 |
}
|
| 319 |
+
.question-card {
|
| 320 |
+
background: white;
|
| 321 |
+
border: 2px solid #f7fafc;
|
| 322 |
+
border-radius: 16px;
|
| 323 |
+
padding: 0.2vw;
|
| 324 |
+
transition: all 0.3s ease;
|
| 325 |
+
position: relative;
|
| 326 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
| 327 |
+
width: 100%;
|
| 328 |
+
min-width: 0;
|
| 329 |
+
box-sizing: border-box;
|
| 330 |
+
height: auto;
|
| 331 |
+
color: #1a202c;
|
| 332 |
+
display: flex;
|
| 333 |
+
flex-direction: column;
|
| 334 |
+
justify-content: stretch;
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
.question-card:hover {
|
| 338 |
+
border-color: #f3a54c;
|
| 339 |
+
transform: translateY(-2px);
|
| 340 |
+
box-shadow: 0 8px 25px rgba(243, 165, 76, 0.15);
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
.question-card.answered {
|
| 344 |
+
border-color: #48bb78;
|
| 345 |
+
background: #f0fff4;
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
.question-card.error {
|
| 349 |
+
border-color: #f56565;
|
| 350 |
+
animation: shake 0.5s ease-in-out;
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
.question-card.required::after {
|
| 354 |
+
content: 'Required';
|
| 355 |
+
position: absolute;
|
| 356 |
+
top: 1rem;
|
| 357 |
+
right: 1rem;
|
| 358 |
+
background: #fed7d7;
|
| 359 |
+
color: #c53030;
|
| 360 |
+
padding: 0.25rem 0.75rem;
|
| 361 |
+
border-radius: 20px;
|
| 362 |
+
font-size: 0.75rem;
|
| 363 |
+
font-weight: 700;
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
.question-card label {
|
| 367 |
+
display: block;
|
| 368 |
+
margin-bottom: 0.75rem;
|
| 369 |
+
font-weight: 600;
|
| 370 |
+
color: #2d3748;
|
| 371 |
+
font-size: 0.95rem;
|
| 372 |
+
line-height: 1.4;
|
| 373 |
+
}
|
| 374 |
|
| 375 |
+
.required-indicator {
|
| 376 |
+
color: #f56565;
|
| 377 |
}
|
| 378 |
|
| 379 |
+
.question-card select,
|
| 380 |
+
.question-card input[type="text"],
|
| 381 |
+
.question-card input[type="date"] {
|
| 382 |
+
width: 100%;
|
| 383 |
+
padding: 0.875rem 1rem;
|
| 384 |
+
border: 2px solid #e2e8f0;
|
| 385 |
+
border-radius: 8px;
|
| 386 |
+
font-size: 0.95rem;
|
| 387 |
+
transition: all 0.3s ease;
|
| 388 |
+
background: white;
|
| 389 |
+
font-family: inherit;
|
| 390 |
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
| 391 |
}
|
| 392 |
|
| 393 |
+
.question-card select:focus,
|
| 394 |
+
.question-card input[type="text"]:focus,
|
| 395 |
+
.question-card input[type="date"]:focus {
|
| 396 |
+
outline: none;
|
| 397 |
+
border-color: #f3a54c;
|
| 398 |
+
box-shadow: 0 0 0 3px rgba(243, 165, 76, 0.1);
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
.radio-options {
|
| 402 |
+
display: flex;/*
|
| 403 |
+
flex-direction: column;*/
|
| 404 |
+
gap: 0.75rem;
|
| 405 |
}
|
| 406 |
|
| 407 |
+
.radio-option {
|
| 408 |
+
display: flex;
|
| 409 |
+
align-items: center;
|
| 410 |
+
gap: 0.75rem;
|
| 411 |
+
padding: 0.75rem;
|
| 412 |
+
background: #f7fafc;
|
| 413 |
+
border-radius: 8px;
|
| 414 |
+
cursor: pointer;
|
| 415 |
+
transition: background 0.3s ease;
|
| 416 |
}
|
| 417 |
|
| 418 |
+
.radio-option:hover {
|
| 419 |
+
background: #edf2f7;
|
| 420 |
+
}
|
| 421 |
|
| 422 |
+
.radio-option input {
|
| 423 |
+
margin: 0;
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
.hint {
|
| 427 |
+
font-size: 0.85rem;
|
| 428 |
+
color: #718096;
|
| 429 |
+
margin-top: 0.5rem;
|
| 430 |
+
line-height: 1.4;
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
.error-message {
|
| 434 |
+
font-size: 0.85rem;
|
| 435 |
+
color: #e53e3e;
|
| 436 |
+
font-weight: 600;
|
| 437 |
+
margin-top: 0.5rem;
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
/* Submit Section */
|
| 441 |
+
.submit-section {
|
| 442 |
+
text-align: center;
|
| 443 |
+
padding: 1vw;
|
| 444 |
+
border-top: 1px solid #e2e8f0;
|
| 445 |
+
/* margin-top: 2rem;*/
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
.submit-btn {
|
| 449 |
+
padding: 1rem 2.5rem;
|
| 450 |
+
font-size: 1.1rem;
|
| 451 |
+
background: linear-gradient(135deg, #f3a54c, #ffcb7a);
|
| 452 |
+
color: #111;
|
| 453 |
+
border: none;
|
| 454 |
+
border-radius: 12px;
|
| 455 |
+
font-weight: 600;
|
| 456 |
+
cursor: pointer;
|
| 457 |
+
transition: all 0.3s ease;
|
| 458 |
+
box-shadow: 0 4px 15px rgba(243, 165, 76, 0.3);
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
.submit-btn:hover {
|
| 462 |
+
transform: translateY(-2px);
|
| 463 |
+
box-shadow: 0 8px 25px rgba(243, 165, 76, 0.4);
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
.submit-btn:disabled {
|
| 467 |
+
opacity: 0.6;
|
| 468 |
+
cursor: not-allowed;
|
| 469 |
+
transform: none !important;
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
.validation-message {
|
| 473 |
+
margin-top: 1rem;
|
| 474 |
+
color: #e53e3e;
|
| 475 |
+
font-weight: 600;
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
/* Empty State */
|
| 479 |
+
.empty-state {
|
| 480 |
+
padding: 4rem 2rem;
|
| 481 |
+
text-align: center;
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
.empty-state-text {
|
| 485 |
+
font-size: 1.1rem;
|
| 486 |
+
color: #718096;
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
/* Modal */
|
| 490 |
+
.modal-backdrop {
|
| 491 |
position: fixed;
|
| 492 |
top: 0;
|
| 493 |
left: 0;
|
| 494 |
+
width: 100%;
|
| 495 |
+
height: 100%;
|
| 496 |
+
background: rgba(0, 0, 0, 0.6);
|
| 497 |
display: flex;
|
| 498 |
justify-content: center;
|
| 499 |
align-items: center;
|
| 500 |
z-index: 1000;
|
| 501 |
+
padding: 2rem;
|
| 502 |
}
|
| 503 |
|
|
|
|
| 504 |
.modal {
|
| 505 |
+
background: white;
|
| 506 |
border-radius: 20px;
|
| 507 |
+
padding: 3rem;
|
| 508 |
+
max-width: 500px;
|
| 509 |
+
width: 100%;
|
| 510 |
+
text-align: center;
|
| 511 |
+
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.2);
|
| 512 |
+
border: 1px solid #e3e5e8;
|
| 513 |
}
|
| 514 |
|
| 515 |
+
.success-title {
|
| 516 |
+
color: #48bb78;
|
| 517 |
+
font-size: 1.5rem;
|
| 518 |
+
margin-bottom: 1rem;
|
| 519 |
+
display: flex;
|
| 520 |
+
align-items: center;
|
| 521 |
+
justify-content: center;
|
| 522 |
+
gap: 0.5rem;
|
| 523 |
+
}
|
| 524 |
+
|
| 525 |
+
/* Tip Card */
|
| 526 |
+
.tip-card {
|
| 527 |
+
background: linear-gradient(135deg, #fed7aa, #fdba74);
|
| 528 |
+
border-radius: 16px;
|
| 529 |
+
padding: 1.5rem;
|
| 530 |
+
margin: 2rem 0;
|
| 531 |
+
text-align: left;
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
.tip-content {
|
| 535 |
+
display: flex;
|
| 536 |
+
align-items: center;
|
| 537 |
+
gap: 1rem;
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
.tip-icon {
|
| 541 |
+
width: 50px;
|
| 542 |
+
height: 50px;
|
| 543 |
+
flex-shrink: 0;
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
.tip-text {
|
| 547 |
+
flex: 1;
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
.tip-message {
|
| 551 |
+
margin: 0;
|
| 552 |
+
color: #7c2d12;
|
| 553 |
+
font-weight: 500;
|
| 554 |
+
line-height: 1.5;
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
/* Modal Actions */
|
| 558 |
+
.modal-actions {
|
| 559 |
+
display: flex;
|
| 560 |
+
gap: 1rem;
|
| 561 |
+
justify-content: center;
|
| 562 |
+
margin-bottom: 2rem;
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
/* Countdown Timer */
|
| 566 |
+
.countdown-timer {
|
| 567 |
+
margin-top: 2rem;
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
.timer-bar {
|
| 571 |
+
width: 100%;
|
| 572 |
+
height: 6px;
|
| 573 |
+
background: #e2e8f0;
|
| 574 |
+
border-radius: 3px;
|
| 575 |
+
overflow: hidden;
|
| 576 |
+
margin-bottom: 0.5rem;
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
.timer-fill {
|
| 580 |
+
height: 100%;
|
| 581 |
+
background: linear-gradient(135deg, #48bb78, #38a169);
|
| 582 |
+
border-radius: 3px;
|
| 583 |
+
transition: width 0.1s linear;
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
.countdown-timer small {
|
| 587 |
+
color: #718096;
|
| 588 |
+
font-size: 0.85rem;
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
/* Animations */
|
| 592 |
+
@keyframes shake {
|
| 593 |
+
0%, 100% {
|
| 594 |
+
transform: translateX(0);
|
| 595 |
}
|
| 596 |
|
| 597 |
+
25% {
|
| 598 |
+
transform: translateX(-5px);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 599 |
}
|
| 600 |
|
| 601 |
+
75% {
|
| 602 |
+
transform: translateX(5px);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 603 |
}
|
| 604 |
+
}
|
| 605 |
|
| 606 |
+
/* Responsive Design */
|
| 607 |
+
@media (max-width: 1024px) {
|
| 608 |
+
.questions-grid {
|
| 609 |
+
grid-template-columns: repeat(2, 1fr);
|
| 610 |
+
}
|
| 611 |
+
}
|
| 612 |
+
|
| 613 |
+
@media (max-width: 768px) {
|
| 614 |
+
.hero .content {
|
| 615 |
+
padding: 60px 2rem;
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
.info-card {
|
| 619 |
+
flex-direction: column;
|
| 620 |
+
text-align: center;
|
| 621 |
+
padding: 2rem;
|
| 622 |
+
}
|
| 623 |
+
|
| 624 |
+
.info-card__icon {
|
| 625 |
+
width: 80px;
|
| 626 |
+
height: 80px;
|
| 627 |
+
font-size: 2rem;
|
| 628 |
+
}
|
| 629 |
+
|
| 630 |
+
.info-card__title {
|
| 631 |
+
font-size: 2rem;
|
| 632 |
+
justify-content: center;
|
| 633 |
}
|
| 634 |
|
| 635 |
+
.info-card__actions {
|
| 636 |
+
justify-content: center;
|
| 637 |
}
|
| 638 |
|
| 639 |
+
.category-header {
|
| 640 |
+
flex-direction: column;
|
| 641 |
+
gap: 2rem;
|
| 642 |
}
|
| 643 |
|
| 644 |
+
.category-image {
|
| 645 |
+
flex: none;
|
|
|
|
|
|
|
| 646 |
width: 100%;
|
|
|
|
| 647 |
}
|
| 648 |
|
| 649 |
+
.questions-grid {
|
| 650 |
+
grid-template-columns: 1fr;
|
| 651 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 652 |
|
| 653 |
+
.category-nav {
|
| 654 |
+
justify-content: flex-start;
|
| 655 |
+
overflow-x: auto;
|
| 656 |
+
padding-bottom: 1rem;
|
| 657 |
}
|
| 658 |
|
| 659 |
+
.nav-chip {
|
| 660 |
+
flex-shrink: 0;
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
+
.modal-actions {
|
| 664 |
+
flex-direction: column;
|
| 665 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 666 |
}
|
| 667 |
|
| 668 |
+
@media (max-width: 480px) {
|
| 669 |
+
.container {
|
| 670 |
+
padding: 0 1rem;
|
| 671 |
}
|
| 672 |
|
| 673 |
+
.category-section {
|
| 674 |
+
padding: 2rem 1rem;
|
|
|
|
| 675 |
}
|
| 676 |
|
| 677 |
+
.modal {
|
| 678 |
+
padding: 2rem 1.5rem;
|
|
|
|
|
|
|
| 679 |
}
|
| 680 |
|
| 681 |
+
.info-card__title {
|
| 682 |
+
font-size: 1.75rem;
|
| 683 |
}
|
|
|
|
| 684 |
|
| 685 |
+
.questions-grid {
|
| 686 |
+
gap: 1rem;
|
|
|
|
|
|
|
| 687 |
}
|
| 688 |
|
| 689 |
+
.question-card {
|
| 690 |
+
padding: 1.25rem;
|
|
|
|
| 691 |
}
|
| 692 |
}
|
| 693 |
+
|
src/app/question-answer/question-answer.component.html
CHANGED
|
@@ -1,67 +1,175 @@
|
|
| 1 |
-
<div class="
|
| 2 |
-
<
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
<button class="orb orb-top" type="button" (click)="onSelect('marriage')" aria-label="Select MARRIAGE">
|
| 9 |
-
<span><i class="fa-solid fa-ring"></i> MARRIAGE</span>
|
| 10 |
-
</button>
|
| 11 |
-
<button class="orb orb-left" type="button" (click)="onSelect('interview')" aria-label="Select INTERVIEW">
|
| 12 |
-
<span><i class="fa-solid fa-user-tie"></i> INTERVIEW</span>
|
| 13 |
-
</button>
|
| 14 |
-
<button class="orb orb-right" type="button" (click)="onSelect('partnership')" aria-label="Select BUSINESS PARTNERS">
|
| 15 |
-
<span><i class="fa-solid fa-handshake"></i> BUSINESS PARTNERS</span>
|
| 16 |
-
</button>
|
| 17 |
-
</main>
|
| 18 |
-
|
| 19 |
-
<div *ngIf="isModalOpen" class="modal-overlay">
|
| 20 |
-
<div class="modal">
|
| 21 |
-
<button class="close-btn" (click)="closeModal()">×</button>
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
</div>
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
</div>
|
|
|
|
| 41 |
|
| 42 |
-
|
| 43 |
-
<div *
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
{{
|
| 51 |
</label>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
</div>
|
| 53 |
</div>
|
| 54 |
|
| 55 |
-
<
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
| 64 |
</div>
|
| 65 |
</div>
|
| 66 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
</div>
|
|
|
|
| 1 |
+
<div class="professional-page">
|
| 2 |
+
<!-- Loader Overlay (same as llm-quiz) -->
|
| 3 |
+
<div class="loader-overlay" *ngIf="isLoading">
|
| 4 |
+
<div class="loader-container">
|
| 5 |
+
<img src="assets/Loader.gif" alt="Loading..." class="loader-gif" />
|
| 6 |
+
</div>
|
| 7 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
+
<!-- Hero Section -->
|
| 10 |
+
<section class="hero">
|
| 11 |
+
<div class="content" [ngStyle]="heroContentStyle">
|
| 12 |
+
<div class="info-card">
|
| 13 |
+
<div class="info-card__icon">
|
| 14 |
+
<i [class]="headingIcon"></i>
|
| 15 |
+
</div>
|
| 16 |
+
<div class="info-card__content">
|
| 17 |
+
<h1 class="info-card__title">{{ selectedOption ? (selectedOption | titlecase) + ' Match' : 'Marriage Match' }}</h1>
|
| 18 |
+
<p class="info-card__text">
|
| 19 |
+
Discover meaningful connections beyond surface-level swipes.<br>
|
| 20 |
+
This helps to analyse compatibility deeply.<br>
|
| 21 |
+
Understand habits, values, and life goals.
|
| 22 |
+
Find harmony and balance for a lasting relationship.
|
| 23 |
+
</p>
|
| 24 |
+
<div class="info-card__actions">
|
| 25 |
+
<button class="btn btn-primary" (click)="scrollToQuestions()">
|
| 26 |
+
Fill Your Details
|
| 27 |
+
</button>
|
| 28 |
+
<button class="btn btn-secondary" (click)="goToPreferences()">
|
| 29 |
+
Set Preference
|
| 30 |
+
</button>
|
| 31 |
+
</div>
|
| 32 |
+
</div>
|
| 33 |
</div>
|
| 34 |
+
</div>
|
| 35 |
+
</section>
|
| 36 |
|
| 37 |
+
<!-- Questions container -->
|
| 38 |
+
<div id="questions-section" class="questions-container" *ngIf="selectedQuestions.length">
|
| 39 |
+
<div class="container">
|
| 40 |
+
<!-- Category Navigation -->
|
| 41 |
+
<div class="category-nav-wrapper">
|
| 42 |
+
<nav class="category-nav" aria-label="Question categories">
|
| 43 |
+
<button *ngFor="let cat of categoriesMeta" type="button"
|
| 44 |
+
(click)="selectCategory(cat.key)"
|
| 45 |
+
[class.active]="selectedCategory === cat.key"
|
| 46 |
+
[class.completed]="getCategoryAnsweredCount(cat.key) === getCategoryRequiredCount(cat.key) && getCategoryRequiredCount(cat.key) > 0"
|
| 47 |
+
[attr.aria-selected]="selectedCategory === cat.key"
|
| 48 |
+
class="nav-chip">
|
| 49 |
+
<i class="fa-solid" [ngClass]="cat.icon"></i>
|
| 50 |
+
<span>{{ cat.title }}</span>
|
| 51 |
+
<span class="chip-progress" *ngIf="getCategoryQuestions(cat.key).length">
|
| 52 |
+
{{ getCategoryAnsweredCount(cat.key) }}/{{ getCategoryRequiredCount(cat.key) }}
|
| 53 |
+
</span>
|
| 54 |
+
</button>
|
| 55 |
+
</nav>
|
| 56 |
+
</div>
|
| 57 |
|
| 58 |
+
<!-- Single Category Rendering -->
|
| 59 |
+
<div class="category-content">
|
| 60 |
+
<ng-container *ngFor="let cat of categoriesMeta; let last = last">
|
| 61 |
+
<section *ngIf="selectedCategory === cat.key" [attr.id]="'cat-' + cat.key" class="category-section">
|
| 62 |
+
<div class="category-header">
|
| 63 |
+
<div class="category-image">
|
| 64 |
+
<img [src]="getCategoryImage(cat.key)" [alt]="cat.title" loading="lazy" />
|
| 65 |
+
</div>
|
| 66 |
+
<div class="category-info">
|
| 67 |
+
<div class="title-section">
|
| 68 |
+
<h2><i class="fa-solid" [ngClass]="cat.icon"></i> {{ cat.title }}</h2>
|
| 69 |
+
<div class="progress-indicator">
|
| 70 |
+
<div class="progress-bar">
|
| 71 |
+
<div class="progress-fill" [style.width.%]="getCategoryPercent(cat.key)"></div>
|
| 72 |
+
</div>
|
| 73 |
+
<small>{{ getCategoryProgressLabel(cat.key) }}</small>
|
| 74 |
+
</div>
|
| 75 |
+
</div>
|
| 76 |
</div>
|
| 77 |
+
</div>
|
| 78 |
|
| 79 |
+
<div class="questions-grid">
|
| 80 |
+
<div *ngFor="let question of getCategoryQuestions(cat.key); trackBy: trackQuestion"
|
| 81 |
+
class="question-card"
|
| 82 |
+
[attr.id]="'q-' + question.column_key"
|
| 83 |
+
[class.required]="question.required"
|
| 84 |
+
[class.answered]="isAnswered(question)"
|
| 85 |
+
[class.error]="showError(question)">
|
| 86 |
+
<label [attr.for]="'answer' + question.column_key">
|
| 87 |
+
{{ question.label }} <span *ngIf="question.required" class="required-indicator">*</span>
|
| 88 |
</label>
|
| 89 |
+
<ng-container [ngSwitch]="question.input_type">
|
| 90 |
+
<!-- Custom multi-select for Languages Spoken -->
|
| 91 |
+
<div *ngIf="question.label === 'Languages Spoken'" class="checkbox-options">
|
| 92 |
+
<label class="checkbox-option" *ngFor="let opt of question.options">
|
| 93 |
+
<input type="checkbox"
|
| 94 |
+
[value]="opt"
|
| 95 |
+
[checked]="answers[question.column_key]?.includes(opt)"
|
| 96 |
+
(change)="onLanguagesCheckboxChange($event, question.column_key)" />
|
| 97 |
+
<span class="checkmark"></span>
|
| 98 |
+
{{ opt }}
|
| 99 |
+
</label>
|
| 100 |
+
</div>
|
| 101 |
+
|
| 102 |
+
<!-- Default rendering for other types, only if not Languages Spoken -->
|
| 103 |
+
<ng-container *ngIf="question.label !== 'Languages Spoken'">
|
| 104 |
+
<select *ngSwitchCase="'select'" [id]="'answer' + question.column_key"
|
| 105 |
+
[(ngModel)]="answers[question.column_key]"
|
| 106 |
+
[name]="'answer' + question.column_key"
|
| 107 |
+
(ngModelChange)="onAnswerChange(question.column_key, $event)"
|
| 108 |
+
[required]="question.required || false">
|
| 109 |
+
<option value="" disabled>{{ getPlaceholder(question) }}</option>
|
| 110 |
+
<option *ngFor="let opt of question.options" [value]="opt">{{ opt }}</option>
|
| 111 |
+
</select>
|
| 112 |
+
|
| 113 |
+
<select *ngSwitchCase="'multiselect'" multiple [id]="'answer' + question.column_key"
|
| 114 |
+
[(ngModel)]="answers[question.column_key]"
|
| 115 |
+
[name]="'answer' + question.column_key"
|
| 116 |
+
(ngModelChange)="onAnswerChange(question.column_key, $event)"
|
| 117 |
+
[required]="question.required || false">
|
| 118 |
+
<option *ngFor="let opt of question.options" [value]="opt">{{ opt }}</option>
|
| 119 |
+
</select>
|
| 120 |
+
</ng-container>
|
| 121 |
+
|
| 122 |
+
<input *ngSwitchCase="'date'" type="date" [id]="'answer' + question.column_key"
|
| 123 |
+
[(ngModel)]="answers[question.column_key]"
|
| 124 |
+
[name]="'answer' + question.column_key"
|
| 125 |
+
(ngModelChange)="onAnswerChange(question.column_key, $event)"
|
| 126 |
+
[required]="question.required || false"
|
| 127 |
+
[placeholder]="getPlaceholder(question)" />
|
| 128 |
+
|
| 129 |
+
<input *ngSwitchCase="'text'" type="text" [id]="'answer' + question.column_key"
|
| 130 |
+
[(ngModel)]="answers[question.column_key]"
|
| 131 |
+
[name]="'answer' + question.column_key"
|
| 132 |
+
(ngModelChange)="onAnswerChange(question.column_key, $event)"
|
| 133 |
+
[required]="question.required || false"
|
| 134 |
+
[placeholder]="getPlaceholder(question)" />
|
| 135 |
+
|
| 136 |
+
<div *ngSwitchCase="'radio'" class="radio-options">
|
| 137 |
+
<label *ngFor="let opt of question.options" class="radio-option">
|
| 138 |
+
<input type="radio" [name]="'answer' + question.column_key" [value]="opt"
|
| 139 |
+
[(ngModel)]="answers[question.column_key]"
|
| 140 |
+
(ngModelChange)="onAnswerChange(question.column_key, $event)" />
|
| 141 |
+
{{ opt }}
|
| 142 |
+
</label>
|
| 143 |
+
</div>
|
| 144 |
+
|
| 145 |
+
<input *ngSwitchDefault type="text" [id]="'answer' + question.column_key"
|
| 146 |
+
[(ngModel)]="answers[question.column_key]"
|
| 147 |
+
[name]="'answer' + question.column_key"
|
| 148 |
+
(ngModelChange)="onAnswerChange(question.column_key, $event)"
|
| 149 |
+
[required]="question.required || false"
|
| 150 |
+
[placeholder]="getPlaceholder(question)" />
|
| 151 |
+
</ng-container>
|
| 152 |
+
<div class="hint" *ngIf="question.placeholder && !showError(question)">{{ question.placeholder }}</div>
|
| 153 |
+
<div class="error-message" *ngIf="showError(question)">This field is required.</div>
|
| 154 |
</div>
|
| 155 |
</div>
|
| 156 |
|
| 157 |
+
<div class="submit-section" *ngIf="last">
|
| 158 |
+
<button class="btn btn-primary submit-btn" (click)="submitAnswers()" [disabled]="isLoading || !allFieldsAnswered">
|
| 159 |
+
<span *ngIf="!isLoading"><i class="fa-solid fa-paper-plane"></i> Submit Answers</span>
|
| 160 |
+
<span *ngIf="isLoading"><i class="fa-solid fa-circle-notch fa-spin"></i> Submitting...</span>
|
| 161 |
+
</button>
|
| 162 |
+
<div class="validation-message" *ngIf="attemptedSubmit && !allFieldsAnswered">
|
| 163 |
+
Please complete all fields ({{ unansweredCount }} remaining).
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
</section>
|
| 167 |
+
</ng-container>
|
| 168 |
</div>
|
| 169 |
</div>
|
| 170 |
</div>
|
| 171 |
+
|
| 172 |
+
<div class="empty-state" *ngIf="!selectedQuestions.length && !isLoading">
|
| 173 |
+
<p class="empty-state-text">Select a matching mode to begin answering questions.</p>
|
| 174 |
+
</div>
|
| 175 |
</div>
|
src/app/question-answer/question-answer.component.ts
CHANGED
|
@@ -1,157 +1,521 @@
|
|
| 1 |
-
import { Component, OnInit } from '@angular/core';
|
| 2 |
import { QuestionAnswerService, QAItem } from './question-answer-service.service';
|
| 3 |
-
import { Router,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
import { FormsModule } from '@angular/forms';
|
| 5 |
import { CommonModule } from '@angular/common';
|
| 6 |
-
import { HttpErrorResponse } from '@angular/common/http';
|
| 7 |
|
| 8 |
@Component({
|
| 9 |
selector: 'app-question-answer',
|
| 10 |
standalone: true,
|
| 11 |
-
imports: [FormsModule,
|
| 12 |
templateUrl: './question-answer.component.html',
|
| 13 |
styleUrls: ['./question-answer.component.css']
|
| 14 |
})
|
| 15 |
export class QuestionAnswerComponent implements OnInit {
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
selectedQuestions: QAItem[] = [];
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
|
|
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
constructor(
|
| 28 |
private qaService: QuestionAnswerService,
|
| 29 |
-
private router: Router
|
|
|
|
| 30 |
) { }
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
ngOnInit(): void {
|
| 33 |
-
// Optional: restore from localStorage if available
|
| 34 |
const uid = localStorage.getItem('user_id');
|
| 35 |
if (uid) {
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
fetchQuestions(role: string): void {
|
| 65 |
this.qaService.getQuestions(role).subscribe({
|
| 66 |
next: (data: QAItem[]) => {
|
|
|
|
|
|
|
| 67 |
this.selectedQuestions = data;
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
},
|
| 72 |
-
error: (err: HttpErrorResponse) =>
|
|
|
|
|
|
|
|
|
|
| 73 |
});
|
| 74 |
}
|
| 75 |
|
| 76 |
-
|
| 77 |
-
const
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
return;
|
| 83 |
}
|
| 84 |
|
| 85 |
-
|
| 86 |
-
//
|
|
|
|
|
|
|
|
|
|
| 87 |
const fields: Record<string, any> = {};
|
| 88 |
-
this.selectedQuestions.forEach((q
|
| 89 |
-
|
| 90 |
-
(q as any).column_key || // from RoleQuestions table
|
| 91 |
-
(q as any).key || // fallback if present
|
| 92 |
-
`q_${idx + 1}`; // final fallback
|
| 93 |
-
fields[key] = this.answers[idx] ?? '';
|
| 94 |
});
|
| 95 |
|
| 96 |
-
//
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
-
|
| 100 |
-
this.qaService.submitAnswers(role, userIdNum, fields).subscribe({
|
| 101 |
next: () => {
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
console.log('Navigating to LLM Quiz with queryParams:', fields);
|
| 110 |
-
// Navigate to LLM Quiz with autostart
|
| 111 |
-
// Prepare queryParams
|
| 112 |
-
const queryParams = {
|
| 113 |
-
user_id: String(userIdNum),
|
| 114 |
-
role,
|
| 115 |
-
fields, // Fields object (you might want to reconsider passing this depending on the size/complexity)
|
| 116 |
-
autostart: '1'
|
| 117 |
-
};
|
| 118 |
-
|
| 119 |
-
// Log the queryParams before navigating
|
| 120 |
-
this.router.navigate(['/llmquiz'], {
|
| 121 |
-
queryParams: { user_id: String(userIdNum), role, fields, autostart: '1' }
|
| 122 |
});
|
| 123 |
-
console.log('queryParams:', queryParams);
|
| 124 |
},
|
| 125 |
error: (err: HttpErrorResponse) => {
|
| 126 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
}
|
| 128 |
});
|
| 129 |
}
|
| 130 |
|
| 131 |
-
|
| 132 |
-
this.
|
| 133 |
}
|
| 134 |
|
| 135 |
closeModal(): void {
|
| 136 |
this.isModalOpen = false;
|
|
|
|
| 137 |
}
|
| 138 |
|
| 139 |
-
goHome(): void {
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
}
|
| 142 |
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
tagline: 'Discover your profile. Match with purpose.',
|
| 150 |
-
subtext: 'Py-Match runs a short, privacy-first assessment and builds a profile used for responsible matching. Color results are not shown to users.',
|
| 151 |
-
getStarted: 'Get started',
|
| 152 |
-
tos: 'Terms',
|
| 153 |
-
privacy: 'Privacy'
|
| 154 |
};
|
| 155 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
}
|
| 157 |
}
|
|
|
|
| 1 |
+
import { Component, OnInit, ViewChild } from '@angular/core';
|
| 2 |
import { QuestionAnswerService, QAItem } from './question-answer-service.service';
|
| 3 |
+
import { Router, ActivatedRoute } from '@angular/router';
|
| 4 |
+
import { HttpErrorResponse } from '@angular/common/http';
|
| 5 |
+
import { MatButtonModule } from '@angular/material/button';
|
| 6 |
+
import { MatIconModule } from '@angular/material/icon';
|
| 7 |
+
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
| 8 |
+
import { MatTabsModule } from '@angular/material/tabs';
|
| 9 |
+
import { MatTabGroup } from '@angular/material/tabs';
|
| 10 |
import { FormsModule } from '@angular/forms';
|
| 11 |
import { CommonModule } from '@angular/common';
|
|
|
|
| 12 |
|
| 13 |
@Component({
|
| 14 |
selector: 'app-question-answer',
|
| 15 |
standalone: true,
|
| 16 |
+
imports: [CommonModule, FormsModule, MatButtonModule, MatIconModule, MatProgressSpinnerModule, MatTabsModule],
|
| 17 |
templateUrl: './question-answer.component.html',
|
| 18 |
styleUrls: ['./question-answer.component.css']
|
| 19 |
})
|
| 20 |
export class QuestionAnswerComponent implements OnInit {
|
| 21 |
+
@ViewChild('tabGroup') tabGroup!: MatTabGroup;
|
| 22 |
+
|
| 23 |
+
selectedQuestions: QAItem[] = [];
|
| 24 |
+
filteredQuestions: QAItem[] = [];
|
| 25 |
+
selectedOption = '';
|
| 26 |
+
currentTabIndex = 0;
|
| 27 |
+
isModalOpen = false;
|
| 28 |
+
displayMode: 'modal' | 'page' = 'modal';
|
| 29 |
+
isLoading = false;
|
| 30 |
+
userId: any;
|
| 31 |
+
answers: Record<string, any> = {};
|
| 32 |
+
attemptedSubmit = false;
|
| 33 |
+
showProceedModal = false;
|
| 34 |
+
selectedCategory: string = '';
|
| 35 |
+
|
| 36 |
+
// NEW: Add submission status variables
|
| 37 |
+
showSubmissionPopup = false;
|
| 38 |
+
submissionMessage = '';
|
| 39 |
+
isSubmissionSuccess = false;
|
| 40 |
+
redirectTimer: any = null;
|
| 41 |
+
countdownProgress = 100;
|
| 42 |
+
countdownInterval: any = null;
|
| 43 |
+
|
| 44 |
+
categories = ['personal', 'locationfamily', 'lifestyle', 'educationcareer'];
|
| 45 |
+
categoriesMeta = [
|
| 46 |
+
{ key: 'personal', title: 'Personal Info', icon: 'fa-user', color: '#f3a54c' },
|
| 47 |
+
{ key: 'locationfamily', title: 'Family & Location', icon: 'fa-people-roof', color: '#ffcb7a' },
|
| 48 |
+
{ key: 'lifestyle', title: 'Lifestyle', icon: 'fa-seedling', color: '#f3a54c' },
|
| 49 |
+
{ key: 'educationcareer', title: 'Education & Career', icon: 'fa-briefcase', color: '#ffcb7a' }
|
| 50 |
+
];
|
| 51 |
+
|
| 52 |
+
private readonly allowedRoles = new Set(['marriage', 'interview', 'partnership']);
|
| 53 |
+
|
| 54 |
+
/** Columns that are BIT in SQL Server and require 1/0 values */
|
| 55 |
+
private readonly BIT_FIELDS = new Set<string>([
|
| 56 |
+
'dual_citizenship',
|
| 57 |
+
'live_with_parents',
|
| 58 |
+
'support_parents_financially',
|
| 59 |
+
'own_pets'
|
| 60 |
+
]);
|
| 61 |
+
|
| 62 |
+
// Added getter for icon next to heading
|
| 63 |
+
get headingIcon(): string {
|
| 64 |
+
switch (this.selectedOption) {
|
| 65 |
+
case 'marriage':
|
| 66 |
+
return 'fa-solid fa-ring';
|
| 67 |
+
case 'interview':
|
| 68 |
+
return 'fa-solid fa-user-tie';
|
| 69 |
+
case 'partnership':
|
| 70 |
+
return 'fa-solid fa-handshake';
|
| 71 |
+
default:
|
| 72 |
+
return 'fa-solid fa-layer-group';
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
// Dynamic hero background style used in template
|
| 77 |
+
get heroBackgroundStyle() {
|
| 78 |
+
const role = this.selectedOption || 'marriage';
|
| 79 |
+
const img = this.getBackgroundForRole(role);
|
| 80 |
+
return { 'background-image': `linear-gradient(rgba(0,0,0,0.35), rgba(0,0,0,0.35)), url(${img})` } as { [k: string]: string };
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
// Combined content style for hero content div
|
| 84 |
+
get heroContentStyle() {
|
| 85 |
+
return {
|
| 86 |
+
...this.heroBackgroundStyle,
|
| 87 |
+
'background-repeat': 'no-repeat',
|
| 88 |
+
'background-position': 'center center',
|
| 89 |
+
'background-attachment': 'fixed',
|
| 90 |
+
'background-size': 'cover',
|
| 91 |
+
'width': '100%',
|
| 92 |
+
'height': '100vh'
|
| 93 |
+
} as { [k: string]: string };
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
get totalRequired(): number {
|
| 97 |
+
return this.selectedQuestions.filter(q => q.required).length;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
get answeredRequired(): number {
|
| 101 |
+
return this.selectedQuestions.filter(q => q.required && this.isAnswered(q)).length;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
get progressPercent(): number {
|
| 105 |
+
return this.totalRequired ? Math.round((this.answeredRequired / this.totalRequired) * 100) : 0;
|
| 106 |
+
}
|
| 107 |
|
| 108 |
+
get progressLabel(): string {
|
| 109 |
+
return `${this.answeredRequired} / ${this.totalRequired || 0} Required`;
|
| 110 |
+
}
|
| 111 |
|
| 112 |
+
get allFieldsAnswered(): boolean {
|
| 113 |
+
return this.selectedQuestions.length > 0 && this.selectedQuestions.every(q => this.isAnswered(q));
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
get unansweredFields(): QAItem[] {
|
| 117 |
+
return this.selectedQuestions.filter(q => !this.isAnswered(q));
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
get unansweredCount(): number {
|
| 121 |
+
return this.unansweredFields.length;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
// --- Category progress helpers ---
|
| 125 |
+
getCategoryQuestions(key: string): QAItem[] {
|
| 126 |
+
const norm = key.replace(/\s+/g, '').toLowerCase();
|
| 127 |
+
return this.selectedQuestions.filter(q => (q.category || '').replace(/\s+/g, '').toLowerCase() === norm);
|
| 128 |
+
}
|
| 129 |
+
getCategoryAnsweredCount(key: string): number {
|
| 130 |
+
return this.getCategoryQuestions(key).filter(q => this.isAnswered(q)).length;
|
| 131 |
+
}
|
| 132 |
+
getCategoryRequiredCount(key: string): number {
|
| 133 |
+
const qs = this.getCategoryQuestions(key);
|
| 134 |
+
const requiredCnt = qs.filter(q => q.required).length;
|
| 135 |
+
return requiredCnt || qs.length;
|
| 136 |
+
}
|
| 137 |
+
getCategoryPercent(key: string): number {
|
| 138 |
+
const denom = this.getCategoryRequiredCount(key) || 1;
|
| 139 |
+
return (this.getCategoryAnsweredCount(key) / denom) * 100;
|
| 140 |
+
}
|
| 141 |
+
getCategoryProgressLabel(key: string): string {
|
| 142 |
+
return `${this.getCategoryAnsweredCount(key)} / ${this.getCategoryRequiredCount(key)}`;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
// NEW: Method to get the completed category key (only one at a time).
|
| 146 |
+
completedCategory(): string | null {
|
| 147 |
+
// Return the first category key that is fully completed
|
| 148 |
+
for (const cat of this.categoriesMeta) {
|
| 149 |
+
const total = this.getCategoryRequiredCount(cat.key);
|
| 150 |
+
const answered = this.getCategoryAnsweredCount(cat.key);
|
| 151 |
+
if (total > 0 && answered === total) {
|
| 152 |
+
return cat.key;
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
return null;
|
| 156 |
+
}
|
| 157 |
|
| 158 |
constructor(
|
| 159 |
private qaService: QuestionAnswerService,
|
| 160 |
+
private router: Router,
|
| 161 |
+
private route: ActivatedRoute
|
| 162 |
) { }
|
| 163 |
|
| 164 |
+
// NEW: Method to show submission popup and redirect
|
| 165 |
+
private showSubmissionSuccessPopup(): void {
|
| 166 |
+
console.log('Submission successful, preparing redirect to preferences');
|
| 167 |
+
console.log('User ID:', this.userId, 'Role:', this.selectedOption);
|
| 168 |
+
|
| 169 |
+
this.showSubmissionPopup = true;
|
| 170 |
+
this.isSubmissionSuccess = true;
|
| 171 |
+
// Updated message for next screen
|
| 172 |
+
this.submissionMessage = 'Profile submitted! Redirecting to preferences....';
|
| 173 |
+
|
| 174 |
+
// Reset countdown
|
| 175 |
+
this.countdownProgress = 100;
|
| 176 |
+
|
| 177 |
+
// Clear any existing timers
|
| 178 |
+
if (this.redirectTimer) {
|
| 179 |
+
clearTimeout(this.redirectTimer);
|
| 180 |
+
}
|
| 181 |
+
if (this.countdownInterval) {
|
| 182 |
+
clearInterval(this.countdownInterval);
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
// Start countdown animation
|
| 186 |
+
const countdownDuration = 5000; // 5 seconds
|
| 187 |
+
const intervalDuration = 50; // update every 50ms
|
| 188 |
+
const steps = countdownDuration / intervalDuration;
|
| 189 |
+
const progressDecrement = 100 / steps;
|
| 190 |
+
|
| 191 |
+
this.countdownInterval = setInterval(() => {
|
| 192 |
+
this.countdownProgress -= progressDecrement;
|
| 193 |
+
if (this.countdownProgress <= 0) {
|
| 194 |
+
this.countdownProgress = 0;
|
| 195 |
+
clearInterval(this.countdownInterval);
|
| 196 |
+
}
|
| 197 |
+
}, intervalDuration);
|
| 198 |
+
|
| 199 |
+
// Set redirect timer
|
| 200 |
+
this.redirectTimer = setTimeout(() => {
|
| 201 |
+
console.log('Auto-redirecting to preferences...');
|
| 202 |
+
this.redirectToPreferences();
|
| 203 |
+
}, countdownDuration);
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
private redirectToPreferences(): void {
|
| 207 |
+
this.showSubmissionPopup = false;
|
| 208 |
+
this.router.navigate(['/userPreference'], {
|
| 209 |
+
queryParams: {
|
| 210 |
+
user_id: this.userId,
|
| 211 |
+
role: this.selectedOption
|
| 212 |
+
}
|
| 213 |
+
});
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
// NEW: Navigate directly from hero to preferences
|
| 217 |
+
goToPreferences(): void {
|
| 218 |
+
if (!this.userId) {
|
| 219 |
+
const uid = localStorage.getItem('user_id');
|
| 220 |
+
if (uid) this.userId = Number(uid);
|
| 221 |
+
}
|
| 222 |
+
const role = this.selectedOption || 'marriage';
|
| 223 |
+
this.router.navigate(['/userPreference'], {
|
| 224 |
+
queryParams: {
|
| 225 |
+
user_id: this.userId,
|
| 226 |
+
role
|
| 227 |
+
}
|
| 228 |
+
});
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
onSelect(role: string, mode: 'modal' | 'page' = 'page'): void {
|
| 232 |
+
this.selectedOption = role;
|
| 233 |
+
this.displayMode = mode;
|
| 234 |
+
this.isModalOpen = mode === 'modal';
|
| 235 |
+
this.isLoading = true;
|
| 236 |
+
this.fetchQuestions(role);
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
ngOnInit(): void {
|
|
|
|
| 240 |
const uid = localStorage.getItem('user_id');
|
| 241 |
if (uid) {
|
| 242 |
+
this.userId = Number(uid);
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
this.selectedCategory = this.categoriesMeta[0]?.key || '';
|
| 246 |
+
|
| 247 |
+
// Restore answers from localStorage if available
|
| 248 |
+
const savedAnswers = localStorage.getItem('qa_answers');
|
| 249 |
+
if (savedAnswers) {
|
| 250 |
+
try {
|
| 251 |
+
this.answers = JSON.parse(savedAnswers);
|
| 252 |
+
} catch { }
|
| 253 |
}
|
| 254 |
|
| 255 |
+
// If navigated with role param -> show full page (not popup)
|
| 256 |
+
this.route.queryParamMap.subscribe(params => {
|
| 257 |
+
const role = (params.get('role') || '').toLowerCase();
|
| 258 |
+
if (this.allowedRoles.has(role)) {
|
| 259 |
+
this.onSelect(role, 'page');
|
| 260 |
+
}
|
| 261 |
+
// Scroll to questions-section if scrollTo param is present
|
| 262 |
+
const scrollTo = params.get('scrollTo');
|
| 263 |
+
if (scrollTo === 'questions-section') {
|
| 264 |
+
// Wait for questions to load, then scroll
|
| 265 |
+
const scrollAfterLoad = () => {
|
| 266 |
+
if (this.selectedQuestions.length) {
|
| 267 |
+
setTimeout(() => this.scrollToQuestions(), 200);
|
| 268 |
+
} else {
|
| 269 |
+
setTimeout(scrollAfterLoad, 100);
|
| 270 |
+
}
|
| 271 |
+
};
|
| 272 |
+
scrollAfterLoad();
|
| 273 |
+
}
|
| 274 |
+
});
|
| 275 |
}
|
| 276 |
|
| 277 |
+
selectCategory(key: string): void { this.selectedCategory = key; }
|
| 278 |
+
scrollToCategory(key: string): void { this.selectCategory(key); }
|
| 279 |
+
|
| 280 |
+
private getScrollOffset(): number {
|
| 281 |
+
const header = document.querySelector('.topbar') as HTMLElement | null;
|
| 282 |
+
const categoryNav = document.querySelector('.category-nav-wrapper') as HTMLElement | null;
|
| 283 |
+
let offset = 0;
|
| 284 |
+
if (header) offset += header.offsetHeight;
|
| 285 |
+
if (categoryNav) offset += categoryNav.offsetHeight;
|
| 286 |
+
return offset + 16; // Add some extra spacing
|
| 287 |
}
|
| 288 |
|
| 289 |
+
scrollToQuestions(): void {
|
| 290 |
+
setTimeout(() => {
|
| 291 |
+
const el = document.getElementById('questions-section');
|
| 292 |
+
if (el) {
|
| 293 |
+
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
| 294 |
+
}
|
| 295 |
+
}, 100); // Delay to ensure DOM is rendered
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
trackQuestion(index: number, item: QAItem): string { return item.column_key; }
|
| 299 |
|
| 300 |
+
private applyMarriageOptionMapping(questions: QAItem[]): void {
|
| 301 |
+
const map: Record<string, string[] | undefined> = {
|
| 302 |
+
'gender': ['Male', 'Female', 'Other'],
|
| 303 |
+
'marital status': ['Single', 'Divorced', 'Widowed'],
|
| 304 |
+
'education level': ['School', 'Diploma', 'Bachelor', 'Master', 'Doctorate'],
|
| 305 |
+
'employment status': ['Student', 'Employed', 'Self-employed', 'Homemaker', 'Not working'],
|
| 306 |
+
'family type': ['Nuclear', 'Joint', 'Extended'],
|
| 307 |
+
'hobbies/interests': ['Music', 'Reading', 'Sports', 'Travel', 'Cooking', 'Art'],
|
| 308 |
+
'conflict approach': ['Avoid', 'Discuss calmly', 'Decide fast', 'Problem-solving'],
|
| 309 |
+
'financial style': ['Budget-oriented', 'Balanced', 'Spend-oriented'],
|
| 310 |
+
'relocation willingness': ['Yes', 'No', 'Maybe']
|
| 311 |
+
};
|
| 312 |
+
questions.forEach(q => {
|
| 313 |
+
const key = q.label.toLowerCase().trim();
|
| 314 |
+
const opts = map[key];
|
| 315 |
+
if (opts) { q.input_type = 'select'; q.options = opts; }
|
| 316 |
});
|
| 317 |
}
|
| 318 |
|
| 319 |
fetchQuestions(role: string): void {
|
| 320 |
this.qaService.getQuestions(role).subscribe({
|
| 321 |
next: (data: QAItem[]) => {
|
| 322 |
+
// Process questions to ensure proper formatting
|
| 323 |
+
if (role === 'marriage') this.applyMarriageOptionMapping(data);
|
| 324 |
this.selectedQuestions = data;
|
| 325 |
+
this.answers = {};
|
| 326 |
+
data.forEach(q => { this.answers[q.column_key] = q.input_type === 'multiselect' ? [] : ''; });
|
| 327 |
+
this.isLoading = false;
|
| 328 |
+
this.attemptedSubmit = false;
|
| 329 |
+
if (role === 'marriage') {
|
| 330 |
+
this.displayMode = 'page';
|
| 331 |
+
this.isModalOpen = false;
|
| 332 |
+
} else if (this.displayMode === 'modal') {
|
| 333 |
+
this.isModalOpen = true;
|
| 334 |
+
} else {
|
| 335 |
+
this.isModalOpen = false;
|
| 336 |
+
}
|
| 337 |
+
this.selectedCategory = this.categoriesMeta[0]?.key || '';
|
| 338 |
+
this.filterQuestionsByCategory('personal');
|
| 339 |
},
|
| 340 |
+
error: (err: HttpErrorResponse) => {
|
| 341 |
+
console.error('Error fetching questions:', err);
|
| 342 |
+
this.isLoading = false;
|
| 343 |
+
}
|
| 344 |
});
|
| 345 |
}
|
| 346 |
|
| 347 |
+
filterQuestionsByCategory(category: string): void {
|
| 348 |
+
const norm = category.replace(/\s+/g, '').toLowerCase();
|
| 349 |
+
this.filteredQuestions = this.selectedQuestions.filter(q => (q.category || '').replace(/\s+/g, '').toLowerCase() === norm);
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
getQuestionsByCategory(category: string): QAItem[] {
|
| 353 |
+
const norm = category.replace(/\s+/g, '').toLowerCase();
|
| 354 |
+
return this.selectedQuestions.filter(q => (q.category || '').replace(/\s+/g, '').toLowerCase() === norm);
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
onAnswerChange(key: string, value: any): void {
|
| 358 |
+
this.answers[key] = value;
|
| 359 |
+
localStorage.setItem('qa_answers', JSON.stringify(this.answers));
|
| 360 |
+
// Auto-move to next category if current is completed and not last
|
| 361 |
+
const currentIndex = this.categoriesMeta.findIndex(cat => cat.key === this.selectedCategory);
|
| 362 |
+
const currentCat = this.categoriesMeta[currentIndex];
|
| 363 |
+
const total = this.getCategoryRequiredCount(currentCat.key);
|
| 364 |
+
const answered = this.getCategoryAnsweredCount(currentCat.key);
|
| 365 |
+
if (total > 0 && answered === total && currentIndex < this.categoriesMeta.length - 1) {
|
| 366 |
+
// Move to next category
|
| 367 |
+
this.selectedCategory = this.categoriesMeta[currentIndex + 1].key;
|
| 368 |
+
}
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
// Add handler for Languages Spoken checkbox group
|
| 372 |
+
onLanguagesCheckboxChange(event: Event, key: string): void {
|
| 373 |
+
const input = event.target as HTMLInputElement;
|
| 374 |
+
if (!Array.isArray(this.answers[key])) {
|
| 375 |
+
this.answers[key] = [];
|
| 376 |
+
}
|
| 377 |
+
if (input.checked) {
|
| 378 |
+
if (!this.answers[key].includes(input.value)) {
|
| 379 |
+
this.answers[key].push(input.value);
|
| 380 |
+
}
|
| 381 |
+
} else {
|
| 382 |
+
this.answers[key] = this.answers[key].filter((v: string) => v !== input.value);
|
| 383 |
+
}
|
| 384 |
+
localStorage.setItem('qa_answers', JSON.stringify(this.answers));
|
| 385 |
+
this.onAnswerChange(key, this.answers[key]);
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
isCurrentTabComplete(): boolean {
|
| 389 |
+
const cat = this.categories[this.currentTabIndex];
|
| 390 |
+
return this.getQuestionsByCategory(cat).every(q => !q.required || (this.answers[q.column_key] ?? '') !== '');
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
isAnswered(q: QAItem): boolean {
|
| 394 |
+
const v = this.answers[q.column_key];
|
| 395 |
+
if (q.input_type === 'multiselect') {
|
| 396 |
+
return Array.isArray(v) && v.length > 0;
|
| 397 |
+
}
|
| 398 |
+
return v !== undefined && v !== null && v !== '';
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
goToNextTab(group: MatTabGroup): void {
|
| 402 |
+
if (!group || group.selectedIndex == null) return;
|
| 403 |
+
const next = group.selectedIndex + 1;
|
| 404 |
+
if (next < (group as any)._tabs.length) { group.selectedIndex = next; this.currentTabIndex = next; }
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
private normalizeForBackend(obj: Record<string, any>): Record<string, any> {
|
| 408 |
+
const out: Record<string, any> = {};
|
| 409 |
+
for (const [k, v] of Object.entries(obj)) {
|
| 410 |
+
if (!this.BIT_FIELDS.has(k)) {
|
| 411 |
+
out[k] = v;
|
| 412 |
+
continue;
|
| 413 |
+
}
|
| 414 |
+
if (v === undefined || v === null || v === '') { out[k] = null; continue; }
|
| 415 |
+
if (typeof v === 'boolean') { out[k] = v ? 1 : 0; continue; }
|
| 416 |
+
if (typeof v === 'number') { out[k] = v !== 0 ? 1 : 0; continue; }
|
| 417 |
+
if (typeof v === 'string') {
|
| 418 |
+
const s = v.trim().toLowerCase();
|
| 419 |
+
if (['yes', 'true', 'y', '1'].includes(s)) { out[k] = 1; continue; }
|
| 420 |
+
if (['no', 'false', 'n', '0'].includes(s)) { out[k] = 0; continue; }
|
| 421 |
+
out[k] = v;
|
| 422 |
+
continue;
|
| 423 |
+
}
|
| 424 |
+
out[k] = v;
|
| 425 |
+
}
|
| 426 |
+
return out;
|
| 427 |
+
}
|
| 428 |
|
| 429 |
+
submitAnswers(): void {
|
| 430 |
+
this.attemptedSubmit = true;
|
| 431 |
+
// Validate required fields
|
| 432 |
+
const incompleteRequired = this.selectedQuestions.filter(q => q.required && !this.isAnswered(q));
|
| 433 |
+
if (incompleteRequired.length) {
|
| 434 |
+
const first = incompleteRequired[0];
|
| 435 |
+
const el = document.getElementById('q-' + first.column_key);
|
| 436 |
+
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
| 437 |
return;
|
| 438 |
}
|
| 439 |
|
| 440 |
+
this.isLoading = true;
|
| 441 |
+
// Normalize answers (only affects BIT fields)
|
| 442 |
+
const normalizedAnswers = this.normalizeForBackend(this.answers);
|
| 443 |
+
|
| 444 |
+
// Build fields by column_key
|
| 445 |
const fields: Record<string, any> = {};
|
| 446 |
+
this.selectedQuestions.forEach((q) => {
|
| 447 |
+
fields[q.column_key] = normalizedAnswers[q.column_key] ?? null;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
});
|
| 449 |
|
| 450 |
+
// Build payload for Marriage table
|
| 451 |
+
const payload = {
|
| 452 |
+
user_id: this.userId,
|
| 453 |
+
...fields,
|
| 454 |
+
created_at: new Date().toISOString()
|
| 455 |
+
};
|
| 456 |
|
| 457 |
+
this.qaService.submitAnswers(this.selectedOption, this.userId, payload).subscribe({
|
|
|
|
| 458 |
next: () => {
|
| 459 |
+
this.isLoading = false;
|
| 460 |
+
// Redirect to user preferences after successful submit
|
| 461 |
+
this.router.navigate(['/userPreference'], {
|
| 462 |
+
queryParams: {
|
| 463 |
+
user_id: this.userId,
|
| 464 |
+
role: this.selectedOption || 'marriage'
|
| 465 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 466 |
});
|
|
|
|
| 467 |
},
|
| 468 |
error: (err: HttpErrorResponse) => {
|
| 469 |
+
this.isLoading = false;
|
| 470 |
+
const reason = err.status === 0
|
| 471 |
+
? 'Network/CORS or server unreachable'
|
| 472 |
+
: (err.error?.error || err.message || 'Server error');
|
| 473 |
+
console.error('Submit failed:', err);
|
| 474 |
+
alert(`Submit failed: ${reason}`);
|
| 475 |
}
|
| 476 |
});
|
| 477 |
}
|
| 478 |
|
| 479 |
+
dismissProceedModal(): void {
|
| 480 |
+
this.showProceedModal = false;
|
| 481 |
}
|
| 482 |
|
| 483 |
closeModal(): void {
|
| 484 |
this.isModalOpen = false;
|
| 485 |
+
if (this.displayMode === 'modal') { this.selectedQuestions = []; this.answers = {}; this.selectedOption = ''; }
|
| 486 |
}
|
| 487 |
|
| 488 |
+
goHome(): void { this.router.navigate(['/']); }
|
| 489 |
+
|
| 490 |
+
getBackgroundForRole(role: string): string {
|
| 491 |
+
const backgrounds: Record<string, string> = {
|
| 492 |
+
marriage: 'assets/wedding.png',
|
| 493 |
+
interview: 'assets/interview.png',
|
| 494 |
+
partnership: 'assets/partnership.png'
|
| 495 |
+
};
|
| 496 |
+
return backgrounds[role] || 'assets/default-bg.png';
|
| 497 |
}
|
| 498 |
|
| 499 |
+
getCategoryImage(key: string): string {
|
| 500 |
+
const images: Record<string, string> = {
|
| 501 |
+
personal: 'assets/marriage-match/17.png',
|
| 502 |
+
locationfamily: 'assets/marriage-match/12.png',
|
| 503 |
+
lifestyle: 'assets/marriage-match/7.png',
|
| 504 |
+
educationcareer: 'assets/marriage-match/15.png'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 505 |
};
|
| 506 |
+
return images[key] || 'assets/marriage-match/9.png';
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
getPlaceholder(question: QAItem): string {
|
| 510 |
+
if (question.placeholder) return question.placeholder;
|
| 511 |
+
switch (question.input_type) {
|
| 512 |
+
case 'date': return 'Select date';
|
| 513 |
+
case 'select': return 'Select option';
|
| 514 |
+
default: return 'Enter your answer';
|
| 515 |
+
}
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
showError(q: QAItem): boolean {
|
| 519 |
+
return this.attemptedSubmit && !!q.required && !this.isAnswered(q);
|
| 520 |
}
|
| 521 |
}
|
src/app/quiz/quiz.component.css
DELETED
|
@@ -1,126 +0,0 @@
|
|
| 1 |
-
/* General styling for the quiz container */
|
| 2 |
-
.quiz-container {
|
| 3 |
-
display: flex;
|
| 4 |
-
justify-content: center;
|
| 5 |
-
align-items: center;
|
| 6 |
-
height: 100vh; /* Full viewport height */
|
| 7 |
-
background-image: url('src/assets/background.png');
|
| 8 |
-
background-size: cover; /* Ensures the image covers the entire area */
|
| 9 |
-
background-position: center; /* Center the image */
|
| 10 |
-
background-repeat: no-repeat; /* Prevents the image from repeating */
|
| 11 |
-
margin: 0; /* Remove default margin */
|
| 12 |
-
}
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
/* Overlay for dimming the background */
|
| 16 |
-
.quiz-overlay {
|
| 17 |
-
position: absolute;
|
| 18 |
-
top: 0;
|
| 19 |
-
left: 0;
|
| 20 |
-
width: 100%;
|
| 21 |
-
height: 100%;
|
| 22 |
-
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent black overlay */
|
| 23 |
-
display: flex;
|
| 24 |
-
justify-content: center;
|
| 25 |
-
align-items: center;
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
/* Card for the quiz with soft green background */
|
| 29 |
-
.quiz-card {
|
| 30 |
-
background-color: #eaf6e2; /* Soft light green background */
|
| 31 |
-
border-radius: 15px;
|
| 32 |
-
padding: 30px;
|
| 33 |
-
text-align: center;
|
| 34 |
-
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
| 35 |
-
max-width: 450px;
|
| 36 |
-
width: 100%;
|
| 37 |
-
position: relative;
|
| 38 |
-
}
|
| 39 |
-
|
| 40 |
-
/* Profile image */
|
| 41 |
-
.profile-picture {
|
| 42 |
-
width: 80px;
|
| 43 |
-
height: 80px;
|
| 44 |
-
border-radius: 50%;
|
| 45 |
-
object-fit: cover;
|
| 46 |
-
margin-bottom: 20px;
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
/* Title styling */
|
| 50 |
-
.quiz-title {
|
| 51 |
-
font-size: 22px;
|
| 52 |
-
font-weight: bold;
|
| 53 |
-
color: #4caf50; /* Green color */
|
| 54 |
-
margin-bottom: 20px;
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
/* Question text styling */
|
| 58 |
-
.question {
|
| 59 |
-
font-size: 18px;
|
| 60 |
-
color: #333;
|
| 61 |
-
margin-bottom: 30px;
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
/* Button styling for options */
|
| 65 |
-
.option-button {
|
| 66 |
-
background-color: #4caf50;
|
| 67 |
-
color: white;
|
| 68 |
-
padding: 12px 20px;
|
| 69 |
-
margin: 10px;
|
| 70 |
-
border: none;
|
| 71 |
-
border-radius: 10px;
|
| 72 |
-
width: 100%;
|
| 73 |
-
text-align: center;
|
| 74 |
-
cursor: pointer;
|
| 75 |
-
transition: background-color 0.3s ease;
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
.option-button:hover {
|
| 79 |
-
background-color: #45a049;
|
| 80 |
-
}
|
| 81 |
-
|
| 82 |
-
/* Button styling for "Next" and "Submit" */
|
| 83 |
-
.next-button, .submit-button {
|
| 84 |
-
background-color: #4caf50;
|
| 85 |
-
color: white;
|
| 86 |
-
padding: 12px 20px;
|
| 87 |
-
border: none;
|
| 88 |
-
border-radius: 10px;
|
| 89 |
-
margin-top: 20px;
|
| 90 |
-
width: 100%;
|
| 91 |
-
cursor: pointer;
|
| 92 |
-
transition: background-color 0.3s ease;
|
| 93 |
-
}
|
| 94 |
-
|
| 95 |
-
.next-button:hover, .submit-button:hover {
|
| 96 |
-
background-color: #45a049;
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
/* Success Popup */
|
| 100 |
-
.success-popup {
|
| 101 |
-
position: absolute;
|
| 102 |
-
top: 50%;
|
| 103 |
-
left: 50%;
|
| 104 |
-
transform: translate(-50%, -50%);
|
| 105 |
-
background: rgba(0, 0, 0, 0.8);
|
| 106 |
-
color: white;
|
| 107 |
-
padding: 20px;
|
| 108 |
-
border-radius: 10px;
|
| 109 |
-
text-align: center;
|
| 110 |
-
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
| 111 |
-
}
|
| 112 |
-
|
| 113 |
-
.success-popup button {
|
| 114 |
-
background-color: #4caf50;
|
| 115 |
-
color: white;
|
| 116 |
-
padding: 10px 20px;
|
| 117 |
-
border: none;
|
| 118 |
-
border-radius: 5px;
|
| 119 |
-
cursor: pointer;
|
| 120 |
-
margin-top: 15px;
|
| 121 |
-
transition: background-color 0.3s ease;
|
| 122 |
-
}
|
| 123 |
-
|
| 124 |
-
.success-popup button:hover {
|
| 125 |
-
background-color: #45a049;
|
| 126 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/app/quiz/quiz.component.html
DELETED
|
@@ -1,35 +0,0 @@
|
|
| 1 |
-
<div class="quiz-container">
|
| 2 |
-
<div class="quiz-overlay">
|
| 3 |
-
<div class="quiz-card">
|
| 4 |
-
<h1 class="quiz-title">QUIZ</h1>
|
| 5 |
-
<div class="question-container">
|
| 6 |
-
<p class="question">{{ questions[currentQuestionIndex].question }}</p>
|
| 7 |
-
<div class="options">
|
| 8 |
-
<button *ngFor="let option of questions[currentQuestionIndex].options"
|
| 9 |
-
class="option-button"
|
| 10 |
-
(click)="selectOption(option)">
|
| 11 |
-
{{ option }}
|
| 12 |
-
</button>
|
| 13 |
-
</div>
|
| 14 |
-
<button *ngIf="currentQuestionIndex < questions.length - 1"
|
| 15 |
-
class="next-button"
|
| 16 |
-
[disabled]="!isOptionSelected"
|
| 17 |
-
(click)="nextQuestion()">
|
| 18 |
-
Next
|
| 19 |
-
</button>
|
| 20 |
-
<button *ngIf="currentQuestionIndex === questions.length - 1"
|
| 21 |
-
class="submit-button"
|
| 22 |
-
[disabled]="!isOptionSelected"
|
| 23 |
-
(click)="submitQuiz()">
|
| 24 |
-
Submit
|
| 25 |
-
</button>
|
| 26 |
-
</div>
|
| 27 |
-
</div>
|
| 28 |
-
</div>
|
| 29 |
-
</div>
|
| 30 |
-
|
| 31 |
-
<!-- Success Popup -->
|
| 32 |
-
<div *ngIf="isSubmitted" class="success-popup">
|
| 33 |
-
<p>Your test is submitted!</p>
|
| 34 |
-
<button (click)="closePopup()">Close</button>
|
| 35 |
-
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/app/quiz/quiz.component.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
| 1 |
-
import { Component, OnInit } from '@angular/core';
|
| 2 |
-
import { Router } from '@angular/router';
|
| 3 |
-
import { FormsModule } from '@angular/forms';
|
| 4 |
-
import { CommonModule } from '@angular/common';
|
| 5 |
-
import { RouterLink } from '@angular/router';
|
| 6 |
-
interface QuizQuestion {
|
| 7 |
-
question: string;
|
| 8 |
-
options: string[];
|
| 9 |
-
// store the selected answer; start as null and set to a string later
|
| 10 |
-
answer: string | null;
|
| 11 |
-
}
|
| 12 |
-
|
| 13 |
-
@Component({
|
| 14 |
-
selector: 'app-quiz',
|
| 15 |
-
standalone: true,
|
| 16 |
-
imports: [FormsModule, CommonModule, RouterLink],
|
| 17 |
-
templateUrl: './quiz.component.html',
|
| 18 |
-
styleUrls: ['./quiz.component.css'],
|
| 19 |
-
})
|
| 20 |
-
export class QuizComponent implements OnInit {
|
| 21 |
-
|
| 22 |
-
constructor(public router: Router) { }
|
| 23 |
-
ngOnInit(): void { }
|
| 24 |
-
questions: QuizQuestion[] = [
|
| 25 |
-
{
|
| 26 |
-
question: 'You are leading a small team. A new member is quiet and avoids discussions. What will you do first?',
|
| 27 |
-
options: [
|
| 28 |
-
'Assign a simple task and review later',
|
| 29 |
-
'Speak privately to understand concerns',
|
| 30 |
-
'Ask them to present in the next meeting',
|
| 31 |
-
'Ignore it and focus on deadlines'
|
| 32 |
-
],
|
| 33 |
-
answer: null,
|
| 34 |
-
},
|
| 35 |
-
{
|
| 36 |
-
question: 'A stakeholder requests a big change late in the sprint. What is your approach?',
|
| 37 |
-
options: [
|
| 38 |
-
'Accept it immediately',
|
| 39 |
-
'Reject it immediately',
|
| 40 |
-
'Evaluate impact and negotiate scope/time',
|
| 41 |
-
'Ask the team to work overtime'
|
| 42 |
-
],
|
| 43 |
-
answer: null,
|
| 44 |
-
},
|
| 45 |
-
{
|
| 46 |
-
question: 'Two teammates disagree on a solution. How do you proceed?',
|
| 47 |
-
options: [
|
| 48 |
-
'Pick the faster option',
|
| 49 |
-
'Let them resolve it alone',
|
| 50 |
-
'Facilitate a short decision session with facts',
|
| 51 |
-
'Escalate to management'
|
| 52 |
-
],
|
| 53 |
-
answer: null,
|
| 54 |
-
},
|
| 55 |
-
{
|
| 56 |
-
question: 'You find a recurring production bug. What is your first step?',
|
| 57 |
-
options: [
|
| 58 |
-
'Hotfix every time',
|
| 59 |
-
'Add logs and create a root-cause ticket',
|
| 60 |
-
'Disable the feature',
|
| 61 |
-
'Rollback to an old release'
|
| 62 |
-
],
|
| 63 |
-
answer: null,
|
| 64 |
-
},
|
| 65 |
-
{
|
| 66 |
-
question: 'Your release is on time but test coverage is low. What will you do?',
|
| 67 |
-
options: [
|
| 68 |
-
'Release as-is',
|
| 69 |
-
'Block the release',
|
| 70 |
-
'Risk-assess, add critical tests, and plan coverage',
|
| 71 |
-
'Let QA handle it later'
|
| 72 |
-
],
|
| 73 |
-
answer: null,
|
| 74 |
-
},
|
| 75 |
-
];
|
| 76 |
-
|
| 77 |
-
currentQuestionIndex = 0;
|
| 78 |
-
isOptionSelected = false; // controls Next/Submit button enabled state
|
| 79 |
-
isSubmitted = false; // controls the success popup
|
| 80 |
-
|
| 81 |
-
selectOption(option: string): void {
|
| 82 |
-
this.questions[this.currentQuestionIndex].answer = option; // type-safe: string | null
|
| 83 |
-
this.isOptionSelected = true;
|
| 84 |
-
}
|
| 85 |
-
|
| 86 |
-
nextQuestion(): void {
|
| 87 |
-
if (!this.isOptionSelected) return;
|
| 88 |
-
this.currentQuestionIndex++;
|
| 89 |
-
|
| 90 |
-
// Reset the enable/disable state for the next question
|
| 91 |
-
const next = this.questions[this.currentQuestionIndex];
|
| 92 |
-
this.isOptionSelected = !!next?.answer; // true if already answered, else false
|
| 93 |
-
}
|
| 94 |
-
|
| 95 |
-
submitQuiz(): void {
|
| 96 |
-
if (!this.isOptionSelected) return;
|
| 97 |
-
this.isSubmitted = true; // shows the “Your test is submitted!” popup
|
| 98 |
-
}
|
| 99 |
-
|
| 100 |
-
closePopup(): void {
|
| 101 |
-
this.isSubmitted = false;
|
| 102 |
-
// Optionally navigate or reset after closing:
|
| 103 |
-
// this.currentQuestionIndex = 0;
|
| 104 |
-
// this.questions.forEach(q => (q.answer = null));
|
| 105 |
-
// this.isOptionSelected = false;
|
| 106 |
-
}
|
| 107 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/app/quiz/quiz.service.spec.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
| 1 |
-
import { TestBed } from '@angular/core/testing';
|
| 2 |
-
|
| 3 |
-
import { QuizService } from './quiz.service';
|
| 4 |
-
|
| 5 |
-
describe('QuizService', () => {
|
| 6 |
-
let service: QuizService;
|
| 7 |
-
|
| 8 |
-
beforeEach(() => {
|
| 9 |
-
TestBed.configureTestingModule({});
|
| 10 |
-
service = TestBed.inject(QuizService);
|
| 11 |
-
});
|
| 12 |
-
|
| 13 |
-
it('should be created', () => {
|
| 14 |
-
expect(service).toBeTruthy();
|
| 15 |
-
});
|
| 16 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/app/quiz/quiz.service.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
| 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 QuizService {
|
| 9 |
-
|
| 10 |
-
// hf.space => HF backend; else => local Flask
|
| 11 |
-
private baseUrl = location.hostname.endsWith('hf.space')
|
| 12 |
-
? 'https://pykara-py-match-backend.hf.space'
|
| 13 |
-
: 'http://localhost:5000';
|
| 14 |
-
|
| 15 |
-
//private apiUrl = 'http://localhost:5000'; // Your Flask backend URL
|
| 16 |
-
|
| 17 |
-
constructor(private http: HttpClient) { }
|
| 18 |
-
|
| 19 |
-
// Start the quiz by sending the profile and role
|
| 20 |
-
startQuiz(nQuestions: number, batchSize: number, role: string, profile: any): Observable<any> {
|
| 21 |
-
return this.http.post<any>(`${this.baseUrl}/q/start`, {
|
| 22 |
-
n_questions: nQuestions,
|
| 23 |
-
batch_size: batchSize,
|
| 24 |
-
role: role,
|
| 25 |
-
profile: profile
|
| 26 |
-
});
|
| 27 |
-
}
|
| 28 |
-
|
| 29 |
-
// Fetch next question
|
| 30 |
-
nextQuestion(sessionId: string, selectedColor: string): Observable<any> {
|
| 31 |
-
return this.http.post<any>(`${this.baseUrl}/q/next`, {
|
| 32 |
-
session_id: sessionId,
|
| 33 |
-
selected_color: selectedColor
|
| 34 |
-
});
|
| 35 |
-
}
|
| 36 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/app/services/llm-qa.service.ts
CHANGED
|
@@ -63,25 +63,23 @@ export interface NextResponse {
|
|
| 63 |
|
| 64 |
@Injectable({ providedIn: 'root' })
|
| 65 |
export class LlmQaService {
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
private
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
// Change this to your API origin if needed.
|
| 73 |
-
//private readonly api = 'http://localhost:5000';
|
| 74 |
-
//private baseUrl = 'http://127.0.0.1:5000/api/questions';
|
| 75 |
-
//test
|
| 76 |
-
constructor(private http: HttpClient) { }
|
| 77 |
-
|
| 78 |
|
|
|
|
|
|
|
| 79 |
|
| 80 |
-
start(req: StartRequest) {
|
| 81 |
return this.http.post<StartResponse>(`${this.api}/llm/start`, req);
|
| 82 |
}
|
| 83 |
|
| 84 |
-
next(req: NextRequest) {
|
| 85 |
return this.http.post<NextResponse>(`${this.api}/llm/next`, req);
|
| 86 |
}
|
| 87 |
}
|
|
|
|
| 63 |
|
| 64 |
@Injectable({ providedIn: 'root' })
|
| 65 |
export class LlmQaService {
|
| 66 |
+
private readonly api: string;
|
| 67 |
+
private readonly baseUrl: string;
|
| 68 |
|
| 69 |
+
constructor(private http: HttpClient) {
|
| 70 |
+
// Initialize URLs based on the current hostname
|
| 71 |
+
this.api = typeof location !== 'undefined' && location.hostname.endsWith('hf.space')
|
| 72 |
+
? 'https://pykara-py-match-backend.hf.space'
|
| 73 |
+
: 'http://localhost:5000';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
+
this.baseUrl = `${this.api}/api/questions`;
|
| 76 |
+
}
|
| 77 |
|
| 78 |
+
start(req: StartRequest): Observable<StartResponse> {
|
| 79 |
return this.http.post<StartResponse>(`${this.api}/llm/start`, req);
|
| 80 |
}
|
| 81 |
|
| 82 |
+
next(req: NextRequest): Observable<NextResponse> {
|
| 83 |
return this.http.post<NextResponse>(`${this.api}/llm/next`, req);
|
| 84 |
}
|
| 85 |
}
|
src/app/user-preferences/user-preferences.component.css
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Reuse Question Answer styles for shared hero and info-card UI */
|
| 2 |
+
@import url('../question-answer/question-answer.component.css');
|
| 3 |
+
|
| 4 |
+
/* Professional Styles for User Preferences Component - Light theme to match question-answer */
|
| 5 |
+
.preferences-container {
|
| 6 |
+
background: #f8fafc;
|
| 7 |
+
padding: 7.5vw 0 1.5vw 0;
|
| 8 |
+
color: #1a202c;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
.preferences-content {
|
| 12 |
+
background: white;
|
| 13 |
+
border-radius: 20px;
|
| 14 |
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
|
| 15 |
+
overflow: hidden;
|
| 16 |
+
border: 1px solid #e3e5e8;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
.preferences-section {
|
| 20 |
+
padding: 3rem;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
.preferences-header {
|
| 24 |
+
display: flex;
|
| 25 |
+
gap: 3rem;
|
| 26 |
+
margin-bottom: unset !important;
|
| 27 |
+
align-items: flex-start;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
.preferences-image {
|
| 31 |
+
flex: 0 0 300px;
|
| 32 |
+
border-radius: 16px;
|
| 33 |
+
overflow: hidden;
|
| 34 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
| 35 |
+
position: relative;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.preferences-image::before {
|
| 39 |
+
content: "";
|
| 40 |
+
position: absolute;
|
| 41 |
+
inset: 0;
|
| 42 |
+
background: linear-gradient(135deg, rgba(243, 165, 76, 0.1), rgba(0, 255, 136, 0.1));
|
| 43 |
+
z-index: 1;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.preferences-image img {
|
| 47 |
+
width: 100%;
|
| 48 |
+
height: 200px;
|
| 49 |
+
object-fit: cover;
|
| 50 |
+
position: relative;
|
| 51 |
+
z-index: 0;
|
| 52 |
+
border-radius: 16px;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
.preferences-info {
|
| 56 |
+
flex: 1;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.section-description {
|
| 60 |
+
color: #4a5568;
|
| 61 |
+
font-size: 1rem;
|
| 62 |
+
font-weight: 500;
|
| 63 |
+
line-height: 1.6;
|
| 64 |
+
margin: 0;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
/* Progress Indicator (match QA) */
|
| 68 |
+
.progress-indicator {
|
| 69 |
+
display: flex;
|
| 70 |
+
align-items: center;
|
| 71 |
+
gap: 1rem;
|
| 72 |
+
margin-top: 1rem;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.progress-bar {
|
| 76 |
+
flex: 1;
|
| 77 |
+
height: 8px;
|
| 78 |
+
background: #e2e8f0;
|
| 79 |
+
border-radius: 4px;
|
| 80 |
+
overflow: hidden;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
.progress-fill {
|
| 84 |
+
height: 100%;
|
| 85 |
+
background: linear-gradient(135deg, #f3a54c, #ffcb7a);
|
| 86 |
+
border-radius: 4px;
|
| 87 |
+
transition: width 0.4s ease;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.progress-indicator small {
|
| 91 |
+
color: #4a5568;
|
| 92 |
+
font-weight: 600;
|
| 93 |
+
min-width: 80px;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
/* Category Sections */
|
| 97 |
+
.category-section {
|
| 98 |
+
margin-bottom: 3rem;
|
| 99 |
+
padding-bottom: 2rem;
|
| 100 |
+
border-bottom: 1px solid #e2e8f0;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.category-section:last-of-type {
|
| 104 |
+
border-bottom: none;
|
| 105 |
+
margin-bottom: 2rem;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
/* category-title is a button; reset to look like a heading */
|
| 109 |
+
.category-title {
|
| 110 |
+
background: none;
|
| 111 |
+
border: none;
|
| 112 |
+
padding: 0;
|
| 113 |
+
margin: 0 0 1.5rem 0;
|
| 114 |
+
text-align: left;
|
| 115 |
+
font: inherit;
|
| 116 |
+
cursor: default;
|
| 117 |
+
|
| 118 |
+
font-size: 1.5rem;
|
| 119 |
+
font-weight: 700;
|
| 120 |
+
color: #2d3748;
|
| 121 |
+
padding-bottom: 0.5rem;
|
| 122 |
+
border-bottom: 2px solid #f3a54c;
|
| 123 |
+
display: inline-block;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.category-title:focus {
|
| 127 |
+
outline: none;
|
| 128 |
+
box-shadow: none;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
/* Preferences Form */
|
| 132 |
+
.preferences-form {
|
| 133 |
+
margin-top: 2rem;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
/* Form Controls */
|
| 137 |
+
.form-control {
|
| 138 |
+
width: 100%;
|
| 139 |
+
padding: 0.875rem 1rem;
|
| 140 |
+
border: 2px solid #e2e8f0;
|
| 141 |
+
border-radius: 12px;
|
| 142 |
+
font-size: 0.95rem;
|
| 143 |
+
transition: all 0.3s ease;
|
| 144 |
+
background: white;
|
| 145 |
+
color: #1a202c;
|
| 146 |
+
font-family: inherit;
|
| 147 |
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
.form-control:focus {
|
| 151 |
+
outline: none;
|
| 152 |
+
border-color: #f3a54c;
|
| 153 |
+
box-shadow: 0 0 0 3px rgba(243, 165, 76, 0.1);
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
.form-control::placeholder {
|
| 157 |
+
color: #718096;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
/* Checkbox Options */
|
| 161 |
+
.checkbox-options {
|
| 162 |
+
display: flex;
|
| 163 |
+
flex-direction: column;
|
| 164 |
+
align-items: stretch;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.checkbox-options .question-card {
|
| 168 |
+
width: 100%;
|
| 169 |
+
min-width: 0;
|
| 170 |
+
box-sizing: border-box;
|
| 171 |
+
max-height: 260px;
|
| 172 |
+
overflow-y: auto;
|
| 173 |
+
overflow-x: hidden;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.checkbox-option {
|
| 177 |
+
display: flex;
|
| 178 |
+
align-items: center;
|
| 179 |
+
gap: 0.75rem;
|
| 180 |
+
padding: 0.2vw;
|
| 181 |
+
background: #f7fafc;
|
| 182 |
+
border-radius: 12px;
|
| 183 |
+
cursor: pointer;
|
| 184 |
+
transition: all 0.3s ease;
|
| 185 |
+
position: relative;
|
| 186 |
+
border: 2px solid transparent;
|
| 187 |
+
color: #2d3748;
|
| 188 |
+
margin-bottom: unset !important;
|
| 189 |
+
}
|
| 190 |
+
.checkbox-options {
|
| 191 |
+
overflow: scroll;
|
| 192 |
+
height: 8vh;
|
| 193 |
+
}
|
| 194 |
+
.checkbox-option:hover {
|
| 195 |
+
background: #edf2f7;
|
| 196 |
+
border-color: #f3a54c;
|
| 197 |
+
transform: translateY(-2px);
|
| 198 |
+
box-shadow: 0 4px 12px rgba(243, 165, 76, 0.15);
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
.checkbox-option input {
|
| 202 |
+
position: absolute;
|
| 203 |
+
opacity: 0;
|
| 204 |
+
cursor: pointer;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
.checkmark {
|
| 208 |
+
position: relative;
|
| 209 |
+
height: 20px;
|
| 210 |
+
width: 20px;
|
| 211 |
+
background-color: white;
|
| 212 |
+
border: 2px solid #cbd5e0;
|
| 213 |
+
border-radius: 6px;
|
| 214 |
+
transition: all 0.3s ease;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
.checkbox-option input:checked ~ .checkmark {
|
| 218 |
+
background-color: #f3a54c;
|
| 219 |
+
border-color: #f3a54c;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
.checkmark:after {
|
| 223 |
+
content: "";
|
| 224 |
+
position: absolute;
|
| 225 |
+
display: none;
|
| 226 |
+
left: 6px;
|
| 227 |
+
top: 2px;
|
| 228 |
+
width: 4px;
|
| 229 |
+
height: 8px;
|
| 230 |
+
border: solid white;
|
| 231 |
+
border-width: 0 2px 2px 0;
|
| 232 |
+
transform: rotate(45deg);
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
.checkbox-option input:checked ~ .checkmark:after {
|
| 236 |
+
display: block;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
/* Questions Grid - ensure consistent card widths */
|
| 240 |
+
.questions-grid {
|
| 241 |
+
display: grid;
|
| 242 |
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
| 243 |
+
gap: unset !important;
|
| 244 |
+
margin-bottom: 2rem;
|
| 245 |
+
align-items: start;
|
| 246 |
+
justify-items: stretch;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
.question-card {
|
| 250 |
+
background: white;
|
| 251 |
+
border: 2px solid #f7fafc;
|
| 252 |
+
border-radius: 16px;
|
| 253 |
+
padding: 1.5rem;
|
| 254 |
+
transition: all 0.3s ease;
|
| 255 |
+
position: relative;
|
| 256 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
| 257 |
+
width: 100%;
|
| 258 |
+
box-sizing: border-box;
|
| 259 |
+
height: 18vh;
|
| 260 |
+
overflow-y: auto;
|
| 261 |
+
color: #1a202c;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
.question-card:hover {
|
| 265 |
+
border-color: #f3a54c;
|
| 266 |
+
transform: translateY(-2px);
|
| 267 |
+
box-shadow: 0 8px 25px rgba(243, 165, 76, 0.15);
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
.question-card label {
|
| 271 |
+
display: flex;
|
| 272 |
+
margin-bottom: 0.75rem;
|
| 273 |
+
font-weight: 600;
|
| 274 |
+
color: #2d3748;
|
| 275 |
+
font-size: 0.95rem;
|
| 276 |
+
/*line-height: 1.4;*/
|
| 277 |
+
overflow-wrap: anywhere;
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
.question-card.answered {
|
| 281 |
+
border-color: #00d68f !important;
|
| 282 |
+
background: linear-gradient(135deg, #e6fff5 60%, #f7fafc 100%);
|
| 283 |
+
box-shadow: 0 4px 16px rgba(0, 214, 143, 0.12);
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
/* Submit Section */
|
| 287 |
+
.submit-section {
|
| 288 |
+
text-align: center;
|
| 289 |
+
padding: 2rem 0 0;
|
| 290 |
+
border-top: 1px solid #e2e8f0;
|
| 291 |
+
margin-top: 2rem;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
.submit-btn {
|
| 295 |
+
padding: 1rem 2.5rem;
|
| 296 |
+
font-size: 1.1rem;
|
| 297 |
+
background: linear-gradient(135deg, #f3a54c, #ffcb7a);
|
| 298 |
+
color: #111;
|
| 299 |
+
border: none;
|
| 300 |
+
border-radius: 12px;
|
| 301 |
+
font-weight: 600;
|
| 302 |
+
cursor: pointer;
|
| 303 |
+
transition: all 0.3s ease;
|
| 304 |
+
box-shadow: 0 4px 15px rgba(243, 165, 76, 0.3);
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
.submit-btn:hover {
|
| 308 |
+
transform: translateY(-2px);
|
| 309 |
+
box-shadow: 0 8px 25px rgba(243, 165, 76, 0.4);
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
.submit-btn:disabled {
|
| 313 |
+
opacity: 0.6;
|
| 314 |
+
cursor: not-allowed;
|
| 315 |
+
transform: none !important;
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
/* Modal Styles - light */
|
| 319 |
+
.modal-backdrop {
|
| 320 |
+
position: fixed;
|
| 321 |
+
inset: 0;
|
| 322 |
+
background: rgba(0, 0, 0, 0.6);
|
| 323 |
+
display: flex;
|
| 324 |
+
justify-content: center;
|
| 325 |
+
align-items: center;
|
| 326 |
+
z-index: 1000;
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
.modal {
|
| 330 |
+
background: white;
|
| 331 |
+
padding: 3rem;
|
| 332 |
+
border-radius: 16px;
|
| 333 |
+
max-width: 460px;
|
| 334 |
+
text-align: center;
|
| 335 |
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
| 336 |
+
border: 1px solid #e3e5e8;
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
.success-title {
|
| 340 |
+
color: #48bb78;
|
| 341 |
+
font-size: 1.5rem;
|
| 342 |
+
margin-bottom: 1rem;
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
.error-title {
|
| 346 |
+
color: #e53e3e;
|
| 347 |
+
font-size: 1.5rem;
|
| 348 |
+
margin-bottom: 1rem;
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
.modal p {
|
| 352 |
+
color: #4a5568;
|
| 353 |
+
margin-bottom: 2rem;
|
| 354 |
+
line-height: 1.5;
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
.modal-actions {
|
| 358 |
+
display: flex;
|
| 359 |
+
justify-content: center;
|
| 360 |
+
gap: 1rem;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
/* Empty State */
|
| 364 |
+
.empty-state {
|
| 365 |
+
text-align: center;
|
| 366 |
+
padding: 4rem 2rem;
|
| 367 |
+
color: #718096;
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
.empty-state-text {
|
| 371 |
+
font-size: 1.1rem;
|
| 372 |
+
}
|
| 373 |
+
|
| 374 |
+
/* Responsive Design for Preferences */
|
| 375 |
+
@media (max-width: 1024px) {
|
| 376 |
+
.questions-grid {
|
| 377 |
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
| 378 |
+
}
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
@media (max-width: 768px) {
|
| 382 |
+
.preferences-header {
|
| 383 |
+
flex-direction: column;
|
| 384 |
+
gap: 2rem;
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
.preferences-image {
|
| 388 |
+
flex: none;
|
| 389 |
+
width: 100%;
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
.preferences-section {
|
| 393 |
+
padding: 2rem;
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
.questions-grid {
|
| 397 |
+
grid-template-columns: 1fr;
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
.category-title {
|
| 401 |
+
font-size: 1.3rem;
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
.modal {
|
| 405 |
+
margin: 1rem;
|
| 406 |
+
padding: 2rem;
|
| 407 |
+
}
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
@media (max-width: 480px) {
|
| 411 |
+
.preferences-section {
|
| 412 |
+
padding: 1.5rem;
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
.questions-grid {
|
| 416 |
+
gap: 1rem;
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
.question-card {
|
| 420 |
+
padding: 1.25rem;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
.category-title {
|
| 424 |
+
font-size: 1.2rem;
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
.progress-indicator {
|
| 428 |
+
flex-direction: column;
|
| 429 |
+
align-items: stretch;
|
| 430 |
+
gap: 0.5rem;
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
.modal {
|
| 434 |
+
padding: 1.5rem;
|
| 435 |
+
}
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
|
src/app/user-preferences/user-preferences.component.html
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div class="professional-page">
|
| 2 |
+
<!-- Loader Overlay -->
|
| 3 |
+
<div *ngIf="!questions.length" class="loader-overlay">
|
| 4 |
+
<div class="loader-container">
|
| 5 |
+
<img src="assets/Loader.gif" alt="Loading..." class="loader-gif" />
|
| 6 |
+
</div>
|
| 7 |
+
</div>
|
| 8 |
+
|
| 9 |
+
<!-- Loader Overlay (same as llm-quiz) -->
|
| 10 |
+
<div class="loader-overlay" *ngIf="isLoading">
|
| 11 |
+
<div class="loader-container">
|
| 12 |
+
<img src="assets/Loader.gif" alt="Loading..." class="loader-gif" />
|
| 13 |
+
</div>
|
| 14 |
+
</div>
|
| 15 |
+
|
| 16 |
+
<!-- Hero Section -->
|
| 17 |
+
<section class="hero">
|
| 18 |
+
<div class="content" [ngStyle]="heroContentStyle">
|
| 19 |
+
<div class="info-card">
|
| 20 |
+
<div class="info-card__icon">
|
| 21 |
+
<i class="fa-solid fa-sliders"></i>
|
| 22 |
+
</div>
|
| 23 |
+
<div class="info-card__content">
|
| 24 |
+
<h1 class="info-card__title"><i class="fa-solid fa-sliders"></i> Preferences</h1>
|
| 25 |
+
<p class="info-card__text">
|
| 26 |
+
Tell us what matters to you most. These preferences guide better matching and help highlight alignment.
|
| 27 |
+
</p>
|
| 28 |
+
<div class="info-card__actions">
|
| 29 |
+
<button class="btn btn-primary" (click)="scrollToPrefSection()">
|
| 30 |
+
Set Preferences
|
| 31 |
+
</button>
|
| 32 |
+
</div>
|
| 33 |
+
</div>
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
</section>
|
| 37 |
+
|
| 38 |
+
<!-- Preferences Form -->
|
| 39 |
+
<div id="pref-section" class="preferences-container" *ngIf="questions.length">
|
| 40 |
+
<div class="container">
|
| 41 |
+
<!-- Category Navigation (like QA) -->
|
| 42 |
+
<div class="category-nav-wrapper">
|
| 43 |
+
<button class="btn btn-secondary" style="margin-bottom: 1rem;" (click)="goBackToQuestions()">
|
| 44 |
+
<i class="fa fa-arrow-left"></i> Go Back
|
| 45 |
+
</button>
|
| 46 |
+
<nav class="category-nav" aria-label="Question categories">
|
| 47 |
+
<button *ngFor="let cat of categoriesMeta" type="button"
|
| 48 |
+
(click)="selectCategory(cat.key)"
|
| 49 |
+
[class.active]="selectedCategory === cat.key"
|
| 50 |
+
[class.completed]="getCategoryAnsweredCount(cat.key) === getQuestionsByCategory(cat.key).length && getQuestionsByCategory(cat.key).length > 0"
|
| 51 |
+
[attr.aria-selected]="selectedCategory === cat.key"
|
| 52 |
+
class="nav-chip">
|
| 53 |
+
<i class="fa-solid" [ngClass]="cat.icon"></i>
|
| 54 |
+
<span>{{ cat.title }}</span>
|
| 55 |
+
<span class="chip-progress" *ngIf="getQuestionsByCategory(cat.key).length">
|
| 56 |
+
{{ getCategoryAnsweredCount(cat.key) }}/{{ getQuestionsByCategory(cat.key).length }}
|
| 57 |
+
</span>
|
| 58 |
+
</button>
|
| 59 |
+
</nav>
|
| 60 |
+
</div>
|
| 61 |
+
|
| 62 |
+
<div class="preferences-content category-content">
|
| 63 |
+
<ng-container *ngFor="let cat of categoriesMeta; let last = last">
|
| 64 |
+
<section *ngIf="selectedCategory === cat.key" class="preferences-section category-section">
|
| 65 |
+
<div class="preferences-header category-header">
|
| 66 |
+
<div class="preferences-image category-image">
|
| 67 |
+
<img [src]="getCategoryImage(cat.key)" [alt]="cat.title" loading="lazy" />
|
| 68 |
+
</div>
|
| 69 |
+
<div class="preferences-info category-info">
|
| 70 |
+
<div class="title-section">
|
| 71 |
+
<h2><i class="fa-solid" [ngClass]="cat.icon"></i> {{ cat.title }}</h2>
|
| 72 |
+
<p class="section-description">Configure your matching preferences for this section.</p>
|
| 73 |
+
<div class="progress-indicator">
|
| 74 |
+
<div class="progress-bar">
|
| 75 |
+
<div class="progress-fill" [style.width.%]="getCategoryPercent(cat.key)"></div>
|
| 76 |
+
</div>
|
| 77 |
+
<small>{{ getCategoryProgressLabel(cat.key) }}</small>
|
| 78 |
+
</div>
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
|
| 83 |
+
<form [formGroup]="form" (ngSubmit)="submit()" class="preferences-form">
|
| 84 |
+
<div class="questions-grid">
|
| 85 |
+
<div class="question-card" *ngFor="let q of getQuestionsByCategory(cat.key)">
|
| 86 |
+
<label [attr.for]="q.column_key">{{ q.question }}</label>
|
| 87 |
+
|
| 88 |
+
<!-- Select -->
|
| 89 |
+
<select *ngIf="q.input_type === 'select'"
|
| 90 |
+
[formControlName]="q.column_key"
|
| 91 |
+
[id]="q.column_key"
|
| 92 |
+
class="form-control"
|
| 93 |
+
(change)="onInputChange(q.column_key)">
|
| 94 |
+
<option value="">Select an option</option>
|
| 95 |
+
<option *ngFor="let opt of getOptions(q)" [value]="opt">{{ opt }}</option>
|
| 96 |
+
</select>
|
| 97 |
+
|
| 98 |
+
<!-- Text -->
|
| 99 |
+
<input *ngIf="q.input_type === 'text'"
|
| 100 |
+
type="text"
|
| 101 |
+
[formControlName]="q.column_key"
|
| 102 |
+
[id]="q.column_key"
|
| 103 |
+
placeholder="Enter your preference"
|
| 104 |
+
class="form-control"
|
| 105 |
+
(input)="onInputChange(q.column_key)" />
|
| 106 |
+
|
| 107 |
+
<!-- Multi select (checkbox group) -->
|
| 108 |
+
<div *ngIf="q.input_type === 'multi_select'" class="checkbox-options">
|
| 109 |
+
<label class="checkbox-option" *ngFor="let opt of getOptions(q)">
|
| 110 |
+
<input type="checkbox"
|
| 111 |
+
[value]="opt"
|
| 112 |
+
(change)="onCheckboxChange($event, q.column_key); onInputChange(q.column_key)" />
|
| 113 |
+
<span class="checkmark"></span>
|
| 114 |
+
{{ opt }}
|
| 115 |
+
</label>
|
| 116 |
+
</div>
|
| 117 |
+
</div>
|
| 118 |
+
</div>
|
| 119 |
+
|
| 120 |
+
<div class="submit-section" *ngIf="last">
|
| 121 |
+
<button class="btn submit-btn" type="submit">
|
| 122 |
+
<i class="fa-solid fa-floppy-disk"></i> Save Preferences
|
| 123 |
+
</button>
|
| 124 |
+
</div>
|
| 125 |
+
</form>
|
| 126 |
+
</section>
|
| 127 |
+
</ng-container>
|
| 128 |
+
</div>
|
| 129 |
+
</div>
|
| 130 |
+
</div>
|
| 131 |
+
|
| 132 |
+
<div class="empty-state" *ngIf="!questions.length && !isLoading">
|
| 133 |
+
<p class="empty-state-text">Loading your preference questions...</p>
|
| 134 |
+
</div>
|
| 135 |
+
</div>
|
src/app/user-preferences/user-preferences.component.ts
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component, OnInit, Renderer2, ElementRef } from '@angular/core';
|
| 2 |
+
import { FormBuilder, FormGroup, FormArray, FormControl, ReactiveFormsModule } from '@angular/forms';
|
| 3 |
+
import { CommonModule } from '@angular/common';
|
| 4 |
+
import { Router, ActivatedRoute } from '@angular/router';
|
| 5 |
+
import { UserPreferencesService, ExpectationQuestion } from './user-preferences.service';
|
| 6 |
+
|
| 7 |
+
@Component({
|
| 8 |
+
selector: 'app-user-preferences',
|
| 9 |
+
standalone: true,
|
| 10 |
+
imports: [CommonModule, ReactiveFormsModule],
|
| 11 |
+
templateUrl: './user-preferences.component.html',
|
| 12 |
+
styleUrls: ['./user-preferences.component.css']
|
| 13 |
+
})
|
| 14 |
+
export class UserPreferencesComponent implements OnInit {
|
| 15 |
+
questions: ExpectationQuestion[] = [];
|
| 16 |
+
form!: FormGroup;
|
| 17 |
+
user_id = 1; // fallback; will be overridden by query/localStorage
|
| 18 |
+
role: string | null = null;
|
| 19 |
+
isLoading = true;
|
| 20 |
+
showModal = false;
|
| 21 |
+
progress = 0;
|
| 22 |
+
savedSuccessfully = false;
|
| 23 |
+
|
| 24 |
+
// Merge Family and Personality & Values into one tab
|
| 25 |
+
categoriesMeta: Array<{ key: string; title: string; icon: string }> = [
|
| 26 |
+
{ key: 'personal_education', title: 'Personal & Education', icon: 'fa-user-graduate' },
|
| 27 |
+
{ key: 'family_values', title: 'Family & Values', icon: 'fa-people-roof' },
|
| 28 |
+
{ key: 'lifestyle', title: 'Lifestyle & Habits', icon: 'fa-heart' },
|
| 29 |
+
{ key: 'finances_dealbreakers', title: 'Finances & Deal Breakers', icon: 'fa-sack-dollar' },
|
| 30 |
+
{ key: 'interests', title: 'Interests & Social', icon: 'fa-bullseye' }
|
| 31 |
+
];
|
| 32 |
+
selectedCategory: string = 'personal_education';
|
| 33 |
+
|
| 34 |
+
constructor(
|
| 35 |
+
private service: UserPreferencesService,
|
| 36 |
+
private fb: FormBuilder,
|
| 37 |
+
private router: Router,
|
| 38 |
+
private route: ActivatedRoute,
|
| 39 |
+
private renderer: Renderer2,
|
| 40 |
+
private el: ElementRef
|
| 41 |
+
) { }
|
| 42 |
+
|
| 43 |
+
// Match QA: background helpers
|
| 44 |
+
get heroBackgroundStyle() {
|
| 45 |
+
const role = (this.role || 'marriage').toLowerCase();
|
| 46 |
+
const img = this.getBackgroundForRole(role);
|
| 47 |
+
return { 'background-image': `linear-gradient(rgba(0,0,0,0.35), rgba(0,0,0,0.35)), url(${img})` } as { [k: string]: string };
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
get heroContentStyle() {
|
| 51 |
+
return {
|
| 52 |
+
...this.heroBackgroundStyle,
|
| 53 |
+
'background-repeat': 'no-repeat',
|
| 54 |
+
'background-position': 'center center',
|
| 55 |
+
'background-attachment': 'fixed',
|
| 56 |
+
'background-size': 'cover',
|
| 57 |
+
'width': '100%',
|
| 58 |
+
'height': '100vh'
|
| 59 |
+
} as { [k: string]: string };
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
private getBackgroundForRole(role: string): string {
|
| 63 |
+
const backgrounds: Record<string, string> = {
|
| 64 |
+
marriage: 'assets/wedding.png',
|
| 65 |
+
interview: 'assets/interview.png',
|
| 66 |
+
partnership: 'assets/partnership.png'
|
| 67 |
+
};
|
| 68 |
+
return backgrounds[role] || 'assets/default-bg.png';
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
ngOnInit(): void {
|
| 72 |
+
// Enhanced parameter capture - check both query params and localStorage
|
| 73 |
+
this.route.queryParamMap.subscribe(params => {
|
| 74 |
+
const qpUser = params.get('user_id');
|
| 75 |
+
const qpRole = params.get('role');
|
| 76 |
+
|
| 77 |
+
if (qpUser) {
|
| 78 |
+
this.user_id = Number(qpUser);
|
| 79 |
+
// Also store in localStorage for consistency
|
| 80 |
+
localStorage.setItem('user_id', qpUser);
|
| 81 |
+
} else {
|
| 82 |
+
// Fallback to localStorage
|
| 83 |
+
const lsUser = localStorage.getItem('user_id');
|
| 84 |
+
if (lsUser) this.user_id = Number(lsUser);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
this.role = qpRole;
|
| 88 |
+
|
| 89 |
+
console.log('UserPreferences: user_id=', this.user_id, 'role=', this.role);
|
| 90 |
+
});
|
| 91 |
+
|
| 92 |
+
this.service.getQuestions().subscribe({
|
| 93 |
+
next: (data) => {
|
| 94 |
+
this.questions = data;
|
| 95 |
+
this.buildForm();
|
| 96 |
+
this.setInitialSelectedCategory();
|
| 97 |
+
this.isLoading = false;
|
| 98 |
+
},
|
| 99 |
+
error: () => {
|
| 100 |
+
this.isLoading = false;
|
| 101 |
+
}
|
| 102 |
+
});
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
buildForm(): void {
|
| 106 |
+
const group: any = {};
|
| 107 |
+
this.questions.forEach(q => {
|
| 108 |
+
if (q.input_type === 'multi_select') {
|
| 109 |
+
group[q.column_key] = this.fb.array([]);
|
| 110 |
+
} else {
|
| 111 |
+
group[q.column_key] = new FormControl('');
|
| 112 |
+
}
|
| 113 |
+
});
|
| 114 |
+
this.form = this.fb.group(group);
|
| 115 |
+
|
| 116 |
+
// Initialize progress after form is built
|
| 117 |
+
this.updateProgress();
|
| 118 |
+
|
| 119 |
+
// Keep progress up-to-date for all input types
|
| 120 |
+
this.form.valueChanges.subscribe(() => this.updateProgress());
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
// Normalize options to a string array for template binding
|
| 124 |
+
getOptions(q: ExpectationQuestion): string[] {
|
| 125 |
+
const opts = q.options;
|
| 126 |
+
if (Array.isArray(opts)) return (opts as string[]).filter(Boolean);
|
| 127 |
+
if (typeof opts === 'string') return opts.split(',').map(s => s.trim()).filter(Boolean);
|
| 128 |
+
return [];
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
// Get questions by category
|
| 132 |
+
getQuestionsByCategory(category: string): ExpectationQuestion[] {
|
| 133 |
+
if (category === 'personal_education') {
|
| 134 |
+
return this.questions.filter(q => q.category === 'demographics' || q.category === 'educationcareer');
|
| 135 |
+
}
|
| 136 |
+
if (category === 'finances_dealbreakers') {
|
| 137 |
+
return this.questions.filter(q => q.category === 'finances' || q.category === 'dealbreakers');
|
| 138 |
+
}
|
| 139 |
+
if (category === 'family_values') {
|
| 140 |
+
return this.questions.filter(q => q.category === 'family' || q.category === 'values');
|
| 141 |
+
}
|
| 142 |
+
return this.questions.filter(q => q.category === category);
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
selectCategory(key: string): void {
|
| 146 |
+
this.selectedCategory = key;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
private setInitialSelectedCategory(): void {
|
| 150 |
+
const firstWithQuestions = this.categoriesMeta.find(c => this.getQuestionsByCategory(c.key).length > 0);
|
| 151 |
+
if (firstWithQuestions) {
|
| 152 |
+
this.selectedCategory = firstWithQuestions.key;
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
getCategoryAnsweredCount(key: string): number {
|
| 157 |
+
const qs = this.getQuestionsByCategory(key);
|
| 158 |
+
return qs.reduce((acc, q) => {
|
| 159 |
+
const ctrl = this.form?.get(q.column_key);
|
| 160 |
+
if (!ctrl) return acc;
|
| 161 |
+
const val = ctrl.value;
|
| 162 |
+
if (Array.isArray(val)) return acc + (val.length > 0 ? 1 : 0);
|
| 163 |
+
return acc + (!!val && val.toString().trim().length > 0 ? 1 : 0);
|
| 164 |
+
}, 0);
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
getCategoryPercent(key: string): number {
|
| 168 |
+
const total = this.getQuestionsByCategory(key).length || 1;
|
| 169 |
+
const answered = this.getCategoryAnsweredCount(key);
|
| 170 |
+
return Math.round((answered / total) * 100);
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
getCategoryProgressLabel(key: string): string {
|
| 174 |
+
const total = this.getQuestionsByCategory(key).length;
|
| 175 |
+
const answered = this.getCategoryAnsweredCount(key);
|
| 176 |
+
return `${answered}/${total} answered`;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
getCategoryImage(key: string): string {
|
| 180 |
+
const images: Record<string, string> = {
|
| 181 |
+
personal_education: 'assets/marriage-match/1.png',
|
| 182 |
+
lifestyle: 'assets/marriage-match/3.png',
|
| 183 |
+
family_values: 'assets/marriage-match/4.png',
|
| 184 |
+
interests: 'assets/marriage-match/6.png',
|
| 185 |
+
finances_dealbreakers: 'assets/marriage-match/7.png'
|
| 186 |
+
};
|
| 187 |
+
return images[key] || 'assets/marriage-match/9.png';
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
onCheckboxChange(event: any, columnKey: string) {
|
| 191 |
+
const formArray: FormArray = this.form.get(columnKey) as FormArray;
|
| 192 |
+
if (event.target.checked) {
|
| 193 |
+
formArray.push(new FormControl(event.target.value));
|
| 194 |
+
} else {
|
| 195 |
+
const index = formArray.controls.findIndex(x => x.value === event.target.value);
|
| 196 |
+
formArray.removeAt(index);
|
| 197 |
+
}
|
| 198 |
+
this.updateProgress();
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
onInputChange(columnKey: string): void {
|
| 202 |
+
// Auto-move to next category if current is completed and not last
|
| 203 |
+
const currentIndex = this.categoriesMeta.findIndex(cat => cat.key === this.selectedCategory);
|
| 204 |
+
const currentCat = this.categoriesMeta[currentIndex];
|
| 205 |
+
const total = this.getQuestionsByCategory(currentCat.key).length;
|
| 206 |
+
const answered = this.getCategoryAnsweredCount(currentCat.key);
|
| 207 |
+
if (total > 0 && answered === total && currentIndex < this.categoriesMeta.length - 1) {
|
| 208 |
+
// Move to next category
|
| 209 |
+
this.selectedCategory = this.categoriesMeta[currentIndex + 1].key;
|
| 210 |
+
}
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
scrollToPrefSection(): void {
|
| 214 |
+
const el = document.getElementById('pref-section');
|
| 215 |
+
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
updateProgress(): void {
|
| 219 |
+
const total = Object.keys(this.form.controls).length || 1;
|
| 220 |
+
const filled = Object.values(this.form.value).filter(v => {
|
| 221 |
+
if (Array.isArray(v)) return v.length > 0;
|
| 222 |
+
return !!v && v.toString().trim().length > 0;
|
| 223 |
+
}).length;
|
| 224 |
+
this.progress = Math.round((filled / total) * 100);
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
submit(): void {
|
| 228 |
+
if (this.form.valid) {
|
| 229 |
+
this.savedSuccessfully = false;
|
| 230 |
+
this.showModal = true;
|
| 231 |
+
|
| 232 |
+
const payload = {
|
| 233 |
+
user_id: this.user_id,
|
| 234 |
+
...this.form.value
|
| 235 |
+
};
|
| 236 |
+
|
| 237 |
+
this.service.saveResponse(payload).subscribe({
|
| 238 |
+
next: () => {
|
| 239 |
+
this.savedSuccessfully = true;
|
| 240 |
+
// Redirect to LLM quiz after successful save
|
| 241 |
+
this.router.navigate(['/llmquiz'], {
|
| 242 |
+
queryParams: {
|
| 243 |
+
user_id: this.user_id,
|
| 244 |
+
role: this.role || 'marriage',
|
| 245 |
+
autostart: '1'
|
| 246 |
+
}
|
| 247 |
+
});
|
| 248 |
+
},
|
| 249 |
+
error: (err) => {
|
| 250 |
+
this.savedSuccessfully = false;
|
| 251 |
+
console.error('Error saving preferences:', err);
|
| 252 |
+
}
|
| 253 |
+
});
|
| 254 |
+
}
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
closeModal(): void {
|
| 258 |
+
this.showModal = false;
|
| 259 |
+
if (this.savedSuccessfully) {
|
| 260 |
+
// Redirect to LLM quiz after successful save
|
| 261 |
+
this.router.navigate(['/llmquiz'], {
|
| 262 |
+
queryParams: {
|
| 263 |
+
user_id: this.user_id,
|
| 264 |
+
role: this.role || 'marriage',
|
| 265 |
+
autostart: '1'
|
| 266 |
+
}
|
| 267 |
+
});
|
| 268 |
+
}
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
goBackToQuestions(): void {
|
| 272 |
+
this.router.navigate(['/question-answer'], {
|
| 273 |
+
queryParams: {
|
| 274 |
+
user_id: this.user_id,
|
| 275 |
+
role: this.role || 'marriage',
|
| 276 |
+
scrollTo: 'questions-section'
|
| 277 |
+
}
|
| 278 |
+
});
|
| 279 |
+
}
|
| 280 |
+
}
|
src/app/user-preferences/user-preferences.service.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@angular/core';
|
| 2 |
+
import { HttpClient } from '@angular/common/http';
|
| 3 |
+
import { Observable, of } from 'rxjs';
|
| 4 |
+
|
| 5 |
+
interface ExpectationQuestion {
|
| 6 |
+
id: number;
|
| 7 |
+
question: string;
|
| 8 |
+
options?: string | string[];
|
| 9 |
+
input_type: string;
|
| 10 |
+
column_key: string;
|
| 11 |
+
category: string;
|
| 12 |
+
required?: boolean;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
interface UserExpectations {
|
| 16 |
+
pref_age_range?: string;
|
| 17 |
+
pref_location?: string;
|
| 18 |
+
pref_education_level?: string;
|
| 19 |
+
pref_employment_status?: string;
|
| 20 |
+
pref_countries?: string[];
|
| 21 |
+
pref_languages?: string[];
|
| 22 |
+
[key: string]: any;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
@Injectable({
|
| 26 |
+
providedIn: 'root'
|
| 27 |
+
})
|
| 28 |
+
export class UserPreferencesService {
|
| 29 |
+
|
| 30 |
+
private baseUrl: string;
|
| 31 |
+
|
| 32 |
+
constructor(private http: HttpClient) {
|
| 33 |
+
// Initialize baseUrl based on the current hostname
|
| 34 |
+
this.baseUrl = location.hostname.endsWith('hf.space')
|
| 35 |
+
? 'https://pykara-py-match-backend.hf.space/api'
|
| 36 |
+
: 'http://localhost:5000/api';
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
getQuestions(): Observable<ExpectationQuestion[]> {
|
| 40 |
+
return this.http.get<ExpectationQuestion[]>(`${this.baseUrl}/expectation-questions`);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
saveResponse(payload: any): Observable<any> {
|
| 44 |
+
return this.http.post(`${this.baseUrl}/expectation-response`, payload);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
// Add the missing method that MatchingListComponent expects
|
| 48 |
+
getUserExpectations(userId: number): Observable<UserExpectations> {
|
| 49 |
+
// Replace with your actual API endpoint for getting user expectations
|
| 50 |
+
// return this.http.get<UserExpectations>(`${this.baseUrl}/user-expectations/${userId}`);
|
| 51 |
+
|
| 52 |
+
// Temporary fallback - return empty expectations
|
| 53 |
+
return of({});
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// Optional: Add method to save expectations
|
| 57 |
+
saveUserExpectations(userId: number, expectations: UserExpectations): Observable<any> {
|
| 58 |
+
return this.http.post(`${this.baseUrl}/user-expectations/${userId}`, expectations);
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
// Only export once at the end of the file
|
| 63 |
+
export type { UserExpectations, ExpectationQuestion };
|
src/assets/favicon.ico
DELETED
|
Binary file (791 Bytes)
|
|
|
src/assets/interview1.png
DELETED
Git LFS Details
|
src/assets/marriage-match/{11.JPG → 11.png}
RENAMED
|
File without changes
|
src/assets/marriage-match/{14.JPG → 14.png}
RENAMED
|
File without changes
|
src/assets/marriage-match/{16.JPG → 16.png}
RENAMED
|
File without changes
|
src/assets/quiz-bg.png
DELETED
Git LFS Details
|
src/assets/signup.png
DELETED
Git LFS Details
|
src/assets/{1.png → wedding.png}
RENAMED
|
File without changes
|
src/index.html
CHANGED
|
@@ -2,11 +2,12 @@
|
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
-
<title>
|
| 6 |
<base href="/">
|
| 7 |
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 8 |
-
<link rel="icon" type="image/x-icon" href="
|
| 9 |
-
<
|
|
|
|
| 10 |
<link rel="preconnect" href="https://fonts.gstatic.com">
|
| 11 |
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
| 12 |
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
|
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
+
<title>PyMatch</title>
|
| 6 |
<base href="/">
|
| 7 |
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 8 |
+
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
| 9 |
+
<!-- Font Awesome via CDN (ensures icons render immediately in dev) -->
|
| 10 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" integrity="sha512-4oV3h6Fh2bP7o4F1Yc7c5b8J9c1t1tO8S4ZV6Lr2d9Vd8YH9B6kFq5XvQG3vE1Z4w7xKJ7xk3hQxG8i3k8QqQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
| 11 |
<link rel="preconnect" href="https://fonts.gstatic.com">
|
| 12 |
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
| 13 |
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
src/main.ts
CHANGED
|
@@ -3,15 +3,18 @@ import { provideHttpClient } from '@angular/common/http';
|
|
| 3 |
import { provideRouter, Routes } from '@angular/router';
|
| 4 |
import { FormsModule } from '@angular/forms';
|
| 5 |
import { importProvidersFrom } from '@angular/core';
|
|
|
|
|
|
|
| 6 |
|
| 7 |
import { AppComponent } from './app/app.component';
|
| 8 |
import { IntroPageComponent } from './app/intro-page/intro-page.component';
|
| 9 |
import { QuestionAnswerComponent } from './app/question-answer/question-answer.component';
|
| 10 |
import { LlmQuizComponent } from './app/llm-quiz/llm-quiz.component';
|
| 11 |
-
import {
|
| 12 |
-
import {
|
| 13 |
-
|
| 14 |
import { QuestionAnswerService } from './app/question-answer/question-answer-service.service';
|
|
|
|
| 15 |
|
| 16 |
const routes: Routes = [
|
| 17 |
{
|
|
@@ -34,7 +37,12 @@ const routes: Routes = [
|
|
| 34 |
{ path: 'sign-up', redirectTo: 'auth/signup', pathMatch: 'full' },
|
| 35 |
{ path: 'question-answer', component: QuestionAnswerComponent },
|
| 36 |
{ path: 'llmquiz', component: LlmQuizComponent },
|
| 37 |
-
{ path: '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
{ path: '**', redirectTo: '' }
|
| 39 |
];
|
| 40 |
|
|
@@ -43,6 +51,7 @@ bootstrapApplication(AppComponent, {
|
|
| 43 |
provideHttpClient(),
|
| 44 |
provideRouter(routes),
|
| 45 |
importProvidersFrom(FormsModule),
|
| 46 |
-
QuestionAnswerService
|
| 47 |
-
|
|
|
|
| 48 |
});
|
|
|
|
| 3 |
import { provideRouter, Routes } from '@angular/router';
|
| 4 |
import { FormsModule } from '@angular/forms';
|
| 5 |
import { importProvidersFrom } from '@angular/core';
|
| 6 |
+
import { MatchingListComponent } from './app/matching-list/matching-list.component';
|
| 7 |
+
import { UserPreferencesComponent } from './app/user-preferences/user-preferences.component';
|
| 8 |
|
| 9 |
import { AppComponent } from './app/app.component';
|
| 10 |
import { IntroPageComponent } from './app/intro-page/intro-page.component';
|
| 11 |
import { QuestionAnswerComponent } from './app/question-answer/question-answer.component';
|
| 12 |
import { LlmQuizComponent } from './app/llm-quiz/llm-quiz.component';
|
| 13 |
+
//import { SignInComponent } from './app/auth/sign-in/sign-in.component';
|
| 14 |
+
//import { SignUpComponent } from './app/auth/sign-up/sign-up.component';
|
| 15 |
+
|
| 16 |
import { QuestionAnswerService } from './app/question-answer/question-answer-service.service';
|
| 17 |
+
import { provideAnimations } from '@angular/platform-browser/animations';
|
| 18 |
|
| 19 |
const routes: Routes = [
|
| 20 |
{
|
|
|
|
| 37 |
{ path: 'sign-up', redirectTo: 'auth/signup', pathMatch: 'full' },
|
| 38 |
{ path: 'question-answer', component: QuestionAnswerComponent },
|
| 39 |
{ path: 'llmquiz', component: LlmQuizComponent },
|
| 40 |
+
{ path: 'matchinglist', component: MatchingListComponent },
|
| 41 |
+
{ path: 'userPreference', component: UserPreferencesComponent },
|
| 42 |
+
{
|
| 43 |
+
path: 'matchinglist/:id',
|
| 44 |
+
component: MatchingListComponent
|
| 45 |
+
},
|
| 46 |
{ path: '**', redirectTo: '' }
|
| 47 |
];
|
| 48 |
|
|
|
|
| 51 |
provideHttpClient(),
|
| 52 |
provideRouter(routes),
|
| 53 |
importProvidersFrom(FormsModule),
|
| 54 |
+
QuestionAnswerService,
|
| 55 |
+
provideAnimations()
|
| 56 |
+
]
|
| 57 |
});
|
src/styles.css
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
|
|
|
|
|
| 1 |
@font-face {
|
| 2 |
font-family: 'Roliana';
|
| 3 |
src: url('/assets/font/Roliana-Regular.otf') format('opentype');
|
| 4 |
font-weight: normal;
|
| 5 |
font-style: normal;
|
| 6 |
}
|
| 7 |
-
|
| 8 |
@font-face {
|
| 9 |
font-family: 'EBGaramond-Regular';
|
| 10 |
src: url('/assets/font/EBGaramond-Regular.ttf') format('truetype');
|
| 11 |
font-weight: normal;
|
| 12 |
font-style: normal;
|
| 13 |
}
|
| 14 |
-
|
| 15 |
@font-face {
|
| 16 |
font-family: 'EBGaramond-Medium';
|
| 17 |
src: url('/assets/font/EBGaramond-Medium.ttf') format('truetype');
|
|
@@ -95,22 +95,14 @@ html, body {
|
|
| 95 |
letter-spacing: var(--ls);
|
| 96 |
}
|
| 97 |
|
| 98 |
-
.btn:hover {
|
| 99 |
-
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
-
.btn:active {
|
| 103 |
-
transform: translateY(0);
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
.btn.primary {
|
| 107 |
-
background: var(--black);
|
| 108 |
-
color: var(--white);
|
| 109 |
-
}
|
| 110 |
|
| 111 |
.btn-ghost {
|
| 112 |
-
border
|
| 113 |
-
color:
|
|
|
|
| 114 |
}
|
| 115 |
|
| 116 |
/* Skip link for accessibility */
|
|
@@ -124,60 +116,24 @@ html, body {
|
|
| 124 |
border-radius: 6px;
|
| 125 |
letter-spacing: var(--ls);
|
| 126 |
}
|
| 127 |
-
|
| 128 |
-
.skip-link:focus {
|
| 129 |
-
top: 8px;
|
| 130 |
-
}
|
| 131 |
|
| 132 |
/* Hero section */
|
| 133 |
/*.hero { padding: 56px 0 72px; }*/
|
| 134 |
-
.hero h1 {
|
| 135 |
-
margin: 0 0
|
| 136 |
-
font-size: clamp(28px, 4vw, 44px);
|
| 137 |
-
line-height: 1.15;
|
| 138 |
-
letter-spacing: var(--ls);
|
| 139 |
-
}
|
| 140 |
-
|
| 141 |
-
.hero p {
|
| 142 |
-
margin: 0 0 28px 0;
|
| 143 |
-
color: var(--muted);
|
| 144 |
-
max-width: 60ch;
|
| 145 |
-
}
|
| 146 |
|
| 147 |
/* Card (optional info block) */
|
| 148 |
-
.card {
|
| 149 |
-
border: 1px solid var(--border);
|
| 150 |
-
border-radius: 12px;
|
| 151 |
-
padding: 20px;
|
| 152 |
-
background: var(--white);
|
| 153 |
-
letter-spacing: var(--ls);
|
| 154 |
-
}
|
| 155 |
|
| 156 |
/* Simple grid */
|
| 157 |
-
.actions {
|
| 158 |
-
display: flex;
|
| 159 |
-
gap: 12px;
|
| 160 |
-
flex-wrap: wrap;
|
| 161 |
-
}
|
| 162 |
|
| 163 |
-
html, body {
|
| 164 |
-
|
| 165 |
-
}
|
| 166 |
|
| 167 |
-
|
| 168 |
-
margin: 0;
|
| 169 |
-
}
|
| 170 |
-
|
| 171 |
-
span.mdc-tab__text-label {
|
| 172 |
-
color: white !important;
|
| 173 |
-
font-weight: bold !important; /*font-family: EBGaramond-Medium !important;*/
|
| 174 |
-
letter-spacing: var(--ls) !important;
|
| 175 |
-
}
|
| 176 |
|
| 177 |
-
body.mat-typography { font-family:
|
| 178 |
-
letter-spacing: var(--ls);
|
| 179 |
-
}
|
| 180 |
|
| 181 |
-
.mat-h2, .mat-headline-6, .mat-typography .mat-h2, .mat-typography .mat-headline-6, .mat-typography h2, .mat-h3, .mat-subtitle-1, .mat-typography .mat-h3, .mat-typography .mat-subtitle-1, .mat-typography h3, .mat-h1, .mat-headline-5, .mat-typography .mat-h1, .mat-typography .mat-headline-5, .mat-typography h1 { /*font-family: EBGaramond-Medium !important; */
|
| 182 |
-
letter-spacing: var(--ls) !important;
|
| 183 |
-
}
|
|
|
|
| 1 |
+
@import url('../node_modules/@fortawesome/fontawesome-free/css/all.min.css');
|
| 2 |
+
|
| 3 |
@font-face {
|
| 4 |
font-family: 'Roliana';
|
| 5 |
src: url('/assets/font/Roliana-Regular.otf') format('opentype');
|
| 6 |
font-weight: normal;
|
| 7 |
font-style: normal;
|
| 8 |
}
|
|
|
|
| 9 |
@font-face {
|
| 10 |
font-family: 'EBGaramond-Regular';
|
| 11 |
src: url('/assets/font/EBGaramond-Regular.ttf') format('truetype');
|
| 12 |
font-weight: normal;
|
| 13 |
font-style: normal;
|
| 14 |
}
|
|
|
|
| 15 |
@font-face {
|
| 16 |
font-family: 'EBGaramond-Medium';
|
| 17 |
src: url('/assets/font/EBGaramond-Medium.ttf') format('truetype');
|
|
|
|
| 95 |
letter-spacing: var(--ls);
|
| 96 |
}
|
| 97 |
|
| 98 |
+
.btn:hover { transform: translateY(-1px); }
|
| 99 |
+
.btn:active { transform: translateY(0); }
|
| 100 |
+
.btn.primary { background: var(--black); color: var(--white); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
.btn-ghost {
|
| 103 |
+
border: 2px solid #f3a54c;
|
| 104 |
+
color: white;
|
| 105 |
+
background: black;
|
| 106 |
}
|
| 107 |
|
| 108 |
/* Skip link for accessibility */
|
|
|
|
| 116 |
border-radius: 6px;
|
| 117 |
letter-spacing: var(--ls);
|
| 118 |
}
|
| 119 |
+
.skip-link:focus { top: 8px; }
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
/* Hero section */
|
| 122 |
/*.hero { padding: 56px 0 72px; }*/
|
| 123 |
+
.hero h1 { margin: 0 0 12px 0; font-size: clamp(28px, 4vw, 44px); line-height: 1.15; letter-spacing: var(--ls); }
|
| 124 |
+
.hero p { margin: 0 0 28px 0; color: var(--muted); max-width: 60ch; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
/* Card (optional info block) */
|
| 127 |
+
.card { border: 1px solid var(--border); border-radius: 12px; padding: 20px; background: var(--white); letter-spacing: var(--ls); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
/* Simple grid */
|
| 130 |
+
.actions { display: flex; gap: 12px; flex-wrap: wrap; }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
|
| 132 |
+
html, body { height: 100%; }
|
| 133 |
+
body { margin: 0; }
|
|
|
|
| 134 |
|
| 135 |
+
span.mdc-tab__text-label { color: white !important; font-weight:bold !important; /*font-family: EBGaramond-Medium !important;*/ letter-spacing: var(--ls) !important; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
+
body.mat-typography { /*font-family: EBGaramond-Medium !important;*/ letter-spacing: var(--ls); }
|
|
|
|
|
|
|
| 138 |
|
| 139 |
+
.mat-h2, .mat-headline-6, .mat-typography .mat-h2, .mat-typography .mat-headline-6, .mat-typography h2, .mat-h3, .mat-subtitle-1, .mat-typography .mat-h3, .mat-typography .mat-subtitle-1, .mat-typography h3, .mat-h1, .mat-headline-5, .mat-typography .mat-h1, .mat-typography .mat-headline-5, .mat-typography h1 { /*font-family: EBGaramond-Medium !important; */letter-spacing: var(--ls) !important; }
|
|
|
|
|
|