Anupriya commited on
Commit
04830fa
·
1 Parent(s): cdc8731

shared button component

Browse files
src/app/app.module.ts CHANGED
@@ -2,19 +2,19 @@ import { NgModule } from '@angular/core';
2
  import { BrowserModule } from '@angular/platform-browser';
3
  import { FormsModule } from '@angular/forms';
4
  import { HttpClientModule } from '@angular/common/http';
 
5
  import { AppRoutingModule } from './app-routing.module';
6
 
7
  // Angular Material
8
  import { MatDialogModule } from '@angular/material/dialog';
9
  import { MatSlideToggleModule } from '@angular/material/slide-toggle';
10
  import { MatIconModule } from '@angular/material/icon';
 
11
 
12
  // Components
13
  import { AppComponent } from './app.component';
14
  import { GenerateQuestionsComponent } from './generate-questions/generate-questions.component';
15
  import { HomeComponent } from './home/home.component';
16
- import { VoiceComponent } from './voice/voice.component';
17
- import { ListenComponent } from './listen/listen.component';
18
  import { WritingComponent } from './writing/writing.component';
19
  import { VocabularyBuilderComponent } from './vocabulary-builder/vocabulary-builder.component';
20
  import { FindwordComponent } from './findword/findword.component';
@@ -26,37 +26,39 @@ import { HeaderComponent } from './shared/header/header.component';
26
  import { SignInComponent } from './sign-in/sign-in.component';
27
  import { PronunciationComponent } from './pronunciation/pronunciation.component';
28
  import { FooterComponent } from './footer/footer.component';
 
 
29
 
30
  @NgModule({
31
  declarations: [
32
- AppComponent, // Add AppComponent here
33
- //GenerateQuestionsComponent,
34
  HomeComponent,
35
- VoiceComponent,
36
- ListenComponent,
37
- WritingComponent,
38
- VocabularyBuilderComponent,
39
- FindwordComponent,
40
- ReadingComponent,
41
  AuthenticationComponent,
42
  AuthComponent,
43
  RootRedirectComponent,
44
  PronunciationComponent,
45
- FooterComponent
46
-
 
 
 
 
 
 
 
47
  ],
48
  imports: [
49
  BrowserModule,
50
  AppRoutingModule,
51
  FormsModule,
52
  HttpClientModule,
 
53
  MatDialogModule,
54
  MatSlideToggleModule,
55
  MatIconModule,
56
- HeaderComponent, // standalone component imported for use in declared components
57
- SignInComponent // import the standalone sign-in (which embeds sign-up)
58
  ],
59
  providers: [],
60
- bootstrap: [AppComponent] // Bootstrap AppComponent
61
  })
62
  export class AppModule { }
 
2
  import { BrowserModule } from '@angular/platform-browser';
3
  import { FormsModule } from '@angular/forms';
4
  import { HttpClientModule } from '@angular/common/http';
5
+ import { CommonModule } from '@angular/common';
6
  import { AppRoutingModule } from './app-routing.module';
7
 
8
  // Angular Material
9
  import { MatDialogModule } from '@angular/material/dialog';
10
  import { MatSlideToggleModule } from '@angular/material/slide-toggle';
11
  import { MatIconModule } from '@angular/material/icon';
12
+ import { MatCardModule } from '@angular/material/card';
13
 
14
  // Components
15
  import { AppComponent } from './app.component';
16
  import { GenerateQuestionsComponent } from './generate-questions/generate-questions.component';
17
  import { HomeComponent } from './home/home.component';
 
 
18
  import { WritingComponent } from './writing/writing.component';
19
  import { VocabularyBuilderComponent } from './vocabulary-builder/vocabulary-builder.component';
20
  import { FindwordComponent } from './findword/findword.component';
 
26
  import { SignInComponent } from './sign-in/sign-in.component';
27
  import { PronunciationComponent } from './pronunciation/pronunciation.component';
28
  import { FooterComponent } from './footer/footer.component';
29
+ // If you have AppButtonComponent, import it here as well
30
+ // import { AppButtonComponent } from './app-button/app-button.component';
31
 
32
  @NgModule({
33
  declarations: [
34
+ AppComponent,
 
35
  HomeComponent,
 
 
 
 
 
 
36
  AuthenticationComponent,
37
  AuthComponent,
38
  RootRedirectComponent,
39
  PronunciationComponent,
40
+ FooterComponent,
41
+ //GenerateQuestionsComponent,
42
+ //VocabularyBuilderComponent,
43
+ //FindwordComponent,
44
+ //HeaderComponent,
45
+ //SignInComponent,
46
+ //ReadingComponent,
47
+ //WritingComponent,
48
+ // AppButtonComponent
49
  ],
50
  imports: [
51
  BrowserModule,
52
  AppRoutingModule,
53
  FormsModule,
54
  HttpClientModule,
55
+ CommonModule,
56
  MatDialogModule,
57
  MatSlideToggleModule,
58
  MatIconModule,
59
+ MatCardModule
 
60
  ],
61
  providers: [],
62
+ bootstrap: [AppComponent]
63
  })
64
  export class AppModule { }
src/app/findword/findword.component.css CHANGED
@@ -1,4 +1,3 @@
1
- /* General Styles */
2
  body {
3
  font-family: 'Roboto', sans-serif;
4
  background-color: #f9f9f9;
@@ -6,15 +5,12 @@ body {
6
  padding: 0;
7
  }
8
 
9
-
10
- /* Responsive Card */
11
  .card1 {
12
  background: #fff;
13
  width: 80vw;
14
- /*max-width: 1000px;*/
15
  margin: 4vh auto;
16
  padding: 2vw;
17
- border: 10px solid #009688;
18
  border-radius: 1vw;
19
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
20
  position: absolute;
@@ -39,7 +35,6 @@ body {
39
 
40
  .quiz-image {
41
  width: 100%;
42
- /*max-width: 350px;*/
43
  height: auto;
44
  border-radius: 10px;
45
  }
@@ -82,8 +77,6 @@ h2 {
82
  cursor: not-allowed;
83
  }
84
 
85
- /* Media Queries for Responsiveness */
86
-
87
  @media (max-width: 768px) {
88
  .content-container {
89
  flex-direction: column;
@@ -116,7 +109,6 @@ h2 {
116
  }
117
  }
118
 
119
- /* Game Screen */
120
  .game-screen {
121
  height: 71vh;
122
  display: flex;
@@ -126,7 +118,7 @@ h2 {
126
  width: 85vw;
127
  margin: 4vh auto;
128
  padding: 2vw;
129
- border: 10px solid #009688;
130
  border-radius: 1vw;
131
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
132
  position: absolute;
@@ -135,7 +127,6 @@ h2 {
135
  transform: translate(-50%, -50%);
136
  }
137
 
138
- /* Game Content Layout */
139
  .game-content {
140
  display: flex;
141
  justify-content: space-between;
@@ -143,7 +134,6 @@ h2 {
143
  align-items: center;
144
  }
145
 
146
- /* Cards */
147
  .audio-card, .input-card {
148
  width: 50%;
149
  height: 20vw;
@@ -158,7 +148,6 @@ h2 {
158
  align-items: center;
159
  }
160
 
161
- /* Input Wrapper */
162
  .input-card {
163
  display: flex;
164
  flex-direction: column;
@@ -194,19 +183,16 @@ h2 {
194
  opacity: 0.8;
195
  }
196
 
197
- /* Green border for correct input */
198
  .input-wrapper input.correct {
199
- border: 2px solid #4caf50 !important; /* green */
200
  background-color: #e8f5e9;
201
  }
202
 
203
- /* Red border for incorrect input */
204
  .input-wrapper input.error {
205
- border: 2px solid #f44336 !important; /* red */
206
  background-color: #ffebee;
207
  }
208
 
209
-
210
  .error-message {
211
  color: #ff4d4d;
212
  font-weight: bold;
@@ -215,30 +201,6 @@ h2 {
215
  text-align: center;
216
  }
217
 
218
- /* Submit Button */
219
- .submit-btn {
220
- width: 85%;
221
- background-color: #006780;
222
- color: white;
223
- font-size: 1rem;
224
- padding: 1rem;
225
- border: none;
226
- border-radius: 8px;
227
- cursor: pointer;
228
- transition: background-color 0.3s;
229
- text-align: center;
230
- }
231
-
232
- .submit-btn:hover {
233
- background-color: #004d5c;
234
- }
235
-
236
- .submit-btn:disabled {
237
- background-color: #cccccc;
238
- cursor: not-allowed;
239
- }
240
-
241
- /* Action Buttons */
242
  .action-buttons {
243
  width: 100%;
244
  display: flex;
@@ -290,7 +252,6 @@ h2 {
290
  cursor: not-allowed;
291
  }
292
 
293
- /* Generate Button Section */
294
  .generate-buttons {
295
  display: flex;
296
  gap: 10px;
@@ -332,7 +293,6 @@ h2 {
332
  background-color: #e68900;
333
  }
334
 
335
- /* Responsive */
336
  @media (max-width: 768px) {
337
  .game-content {
338
  flex-direction: column;
@@ -354,7 +314,6 @@ h2 {
354
  }
355
  }
356
 
357
- /* Popup */
358
  .popup-overlay {
359
  position: fixed;
360
  top: 0;
@@ -369,14 +328,16 @@ h2 {
369
  }
370
 
371
  .popup-content {
372
- background-color: #daf5ff;
373
  padding: 2vw;
374
  border-radius: 8px;
375
  text-align: center;
 
 
376
  }
377
 
378
  .popup-content p {
379
- font-size: 1vw;
380
  margin-bottom: 2vw;
381
  color: #004d5c;
382
  font-weight: bold;
@@ -394,23 +355,6 @@ h2 {
394
  }
395
  }
396
 
397
- .close-btn {
398
- padding: 0.7vw;
399
- font-size: 1vw;
400
- background-color: #009688;
401
- color: white;
402
- border: none;
403
- border-radius: 8px;
404
- cursor: pointer;
405
- transition: background-color 0.3s;
406
- font-weight: bold;
407
- }
408
-
409
- .close-btn:hover {
410
- background-color: #00796b;
411
- }
412
-
413
- /* Game Header */
414
  .game-header {
415
  position: relative;
416
  width: 100%;
@@ -421,49 +365,25 @@ h2 {
421
  }
422
 
423
  .back-btn {
424
- position: absolute;
425
  left: 0;
426
  top: 40%;
427
- transform: translateY(-50%);
428
- background: transparent;
429
- border: none;
430
- font-size: 2rem;
431
- color: #006780;
432
- cursor: pointer;
433
- padding: 0 1rem;
434
- transition: transform 0.3s;
435
  width: 6vw;
436
  }
437
 
438
- .back-btn:hover {
439
- transform: translateY(-50%) scale(1.1);
440
- }
441
-
442
  .game-title {
443
  font-size: 2vw;
444
  color: #004d5c;
445
- /*font-family: 'Super Cartoon', sans-serif;*/
446
  margin: 0;
447
  text-align: center;
448
  }
449
 
450
-
451
-
452
-
453
- /* Optional: Modify the appearance of the disabled "Sentence" button specifically */
454
  .info-btn:disabled {
455
- background-color: #b0bec5; /* Light gray color for the disabled button */
456
- color: #757575; /* Dark gray color for text */
457
  cursor: not-allowed;
458
  opacity: 0.6;
459
  }
460
 
461
-
462
-
463
- /*step 2 left aside css start here*/
464
-
465
-
466
- /* ===== Step 2: Left Audio Panel — Kid-friendly Player ===== */
467
  .audio-card--kids {
468
  --primary: #006780;
469
  --accent: #009688;
@@ -525,7 +445,6 @@ h2 {
525
  filter: grayscale(0.2);
526
  }
527
 
528
- /* Big circular play button */
529
  .play-btn {
530
  position: relative;
531
  width: 65px;
@@ -591,7 +510,6 @@ h2 {
591
  }
592
  }
593
 
594
- /* Wave shell (equalizer bars) */
595
  .wave-shell {
596
  display: flex;
597
  align-items: flex-end;
@@ -608,7 +526,7 @@ h2 {
608
  background: var(--bar);
609
  border-radius: 4px;
610
  animation: wave 1s ease-in-out infinite;
611
- animation-play-state: paused; /* start paused */
612
  }
613
 
614
  .ac-player.is-playing .wave-shell .bar {
@@ -627,7 +545,6 @@ h2 {
627
  }
628
  }
629
 
630
- /* Timeline / progress */
631
  .timeline {
632
  position: relative;
633
  height: 10px;
@@ -651,7 +568,6 @@ h2 {
651
  transition: width 0.15s linear;
652
  }
653
 
654
- /* Time labels */
655
  .time {
656
  width: 80%;
657
  max-width: 520px;
@@ -661,7 +577,6 @@ h2 {
661
  color: #004d5c;
662
  }
663
 
664
- /* Small helper text */
665
  .ac-hint {
666
  font-size: 0.95rem;
667
  color: #004d5c;
@@ -671,7 +586,6 @@ h2 {
671
  border-radius: 10px;
672
  }
673
 
674
- /* Responsive */
675
  @media (max-width: 768px) {
676
  .play-btn {
677
  width: 84px;
@@ -692,11 +606,6 @@ h2 {
692
  }
693
  }
694
 
695
-
696
- /*step =2 right aside css wstart herwe */
697
-
698
-
699
- /* === Step 2: Right-side kid panel === */
700
  .kid-panel {
701
  --primary: #006780;
702
  --accent: #009688;
@@ -711,7 +620,6 @@ h2 {
711
  position: relative;
712
  }
713
 
714
- /* top row */
715
  .kp-top {
716
  display: flex;
717
  align-items: center;
@@ -725,12 +633,10 @@ h2 {
725
  font-weight: 800;
726
  }
727
 
728
- /* attempts (hearts) */
729
  .kp-attempts .heart {
730
  font-size: 1.15rem;
731
  margin-left: 6px;
732
  transition: opacity 0.25s ease, transform 0.25s ease;
733
-
734
  }
735
 
736
  .kp-attempts .heart.is-off {
@@ -738,7 +644,6 @@ h2 {
738
  transform: scale(0.88);
739
  }
740
 
741
- /* input with subtle animation */
742
  .kp-input-wrap {
743
  position: relative;
744
  display: grid;
@@ -766,7 +671,6 @@ h2 {
766
  border-color: var(--accent);
767
  }
768
 
769
- /* success + error states (reuse your classes) */
770
  .kp-input.correct {
771
  border-color: #4caf50 !important;
772
  background: #e8f5e9;
@@ -777,7 +681,6 @@ h2 {
777
  background: #ffebee;
778
  }
779
 
780
- /* pop tick */
781
  .ok-badge {
782
  position: absolute;
783
  right: 8%;
@@ -824,7 +727,6 @@ h2 {
824
  }
825
  }
826
 
827
- /* subtle shake on wrong */
828
  .kid-panel.is-shake .kp-input {
829
  animation: kpShake 0.35s ease;
830
  }
@@ -847,7 +749,6 @@ h2 {
847
  }
848
  }
849
 
850
- /* primary submit re-using your color token */
851
  .kp-submit {
852
  width: 44%;
853
  margin: 0 auto;
@@ -856,7 +757,6 @@ h2 {
856
  font-size: 1.2vw;
857
  }
858
 
859
- /* info panel for meaning/example */
860
  .info-panel {
861
  background: #ffffffd9;
862
  border: 1px dashed var(--accent);
@@ -891,7 +791,6 @@ h2 {
891
  font-size: 0.98rem;
892
  }
893
 
894
- /* responsive tweaks */
895
  @media (max-width: 768px) {
896
  .kp-title {
897
  font-size: 1.05rem;
@@ -911,8 +810,6 @@ h2 {
911
  }
912
  }
913
 
914
-
915
- /* Shared action bar layout */
916
  .action-bar {
917
  display: flex;
918
  justify-content: space-between;
@@ -927,14 +824,13 @@ h2 {
927
  flex-wrap: wrap;
928
  }
929
 
930
- /* Base button using your spec */
931
  .py-btn {
932
  background-color: #006780;
933
  color: #ffffff;
934
  border: none;
935
  padding: 15px 32px;
936
- font-size: 1.2vw; /* per your request */
937
- border-radius: 0.5vw; /* per your request */
938
  cursor: pointer;
939
  transition: background-color 0.3s, transform 0.3s;
940
  font-weight: bold;
@@ -944,80 +840,49 @@ h2 {
944
  line-height: 1;
945
  }
946
 
947
- /* Hover (your spec) */
948
  .py-btn:hover:not(:disabled) {
949
  background-color: #18788f;
950
  transform: scale(1.05);
951
  }
952
 
953
- /* Press feedback */
954
  .py-btn:active:not(:disabled) {
955
  transform: scale(0.98);
956
  }
957
 
958
- /* Disabled */
959
  .py-btn:disabled {
960
  opacity: 0.55;
961
  cursor: not-allowed;
962
  transform: none;
963
  }
964
 
965
- /* Focus ring for keyboard users */
966
  .py-btn:focus-visible {
967
  outline: 3px solid #94c7d6;
968
  outline-offset: 2px;
969
  }
970
 
971
- /* Optional tiny icon motion on hover */
972
  .py-btn .btn__icon {
973
  display: inline-block;
974
  transform: translateY(0);
975
  transition: transform 0.25s ease;
976
-
977
  }
978
 
979
  .py-btn:hover:not(:disabled) .btn__icon {
980
  transform: translateY(-2px);
981
  }
982
 
983
- /* Small-screen fallback so 1.5vw does not get too small */
984
  @media (max-width: 768px) {
985
  .py-btn {
986
- font-size: 16px; /* fallback for phones/tablets */
987
- border-radius: 10px; /* visual balance on small screens */
988
  padding: 12px 22px;
989
  }
990
  }
991
 
992
-
993
- .close-btn1 {
994
- background-color: #009688;
995
- color: black;
996
- border: none;
997
- padding: 0.4vw 0.9vw;
998
- border-radius: 50%;
999
- cursor: pointer;
1000
- border: 1px solid black;
1001
- width: 3vw;
1002
- top: -5vw;
1003
  right: -3.5vw;
1004
- position: absolute;
1005
- height: 3vw;
1006
- font-size: 1.3vw;
1007
- font-weight: bold;
1008
  }
1009
 
1010
- .close-btn1:hover {
1011
- transform: scale(1.06);
1012
- box-shadow: 0 6px 14px rgba(0,0,0,.22);
1013
- background: #f7fafc;
1014
- }
1015
-
1016
- .close-btn1:active {
1017
- transform: scale(0.98);
1018
- }
1019
-
1020
- /* Clean, static image at the left side (no blur, no animation) */
1021
  .left-illustration {
1022
  position: absolute;
1023
  left: -2vw;
@@ -1033,7 +898,6 @@ h2 {
1033
  image-rendering: crisp-edges;
1034
  }
1035
 
1036
- /* Smaller width on narrow screens */
1037
  @media (max-width: 768px) {
1038
  .left-illustration {
1039
  width: 170px;
 
 
1
  body {
2
  font-family: 'Roboto', sans-serif;
3
  background-color: #f9f9f9;
 
5
  padding: 0;
6
  }
7
 
 
 
8
  .card1 {
9
  background: #fff;
10
  width: 80vw;
 
11
  margin: 4vh auto;
12
  padding: 2vw;
13
+ border: 10px solid var(--main-accent-color);
14
  border-radius: 1vw;
15
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
16
  position: absolute;
 
35
 
36
  .quiz-image {
37
  width: 100%;
 
38
  height: auto;
39
  border-radius: 10px;
40
  }
 
77
  cursor: not-allowed;
78
  }
79
 
 
 
80
  @media (max-width: 768px) {
81
  .content-container {
82
  flex-direction: column;
 
109
  }
110
  }
111
 
 
112
  .game-screen {
113
  height: 71vh;
114
  display: flex;
 
118
  width: 85vw;
119
  margin: 4vh auto;
120
  padding: 2vw;
121
+ border: 10px solid var(--main-accent-color);
122
  border-radius: 1vw;
123
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
124
  position: absolute;
 
127
  transform: translate(-50%, -50%);
128
  }
129
 
 
130
  .game-content {
131
  display: flex;
132
  justify-content: space-between;
 
134
  align-items: center;
135
  }
136
 
 
137
  .audio-card, .input-card {
138
  width: 50%;
139
  height: 20vw;
 
148
  align-items: center;
149
  }
150
 
 
151
  .input-card {
152
  display: flex;
153
  flex-direction: column;
 
183
  opacity: 0.8;
184
  }
185
 
 
186
  .input-wrapper input.correct {
187
+ border: 2px solid #4caf50 !important;
188
  background-color: #e8f5e9;
189
  }
190
 
 
191
  .input-wrapper input.error {
192
+ border: 2px solid #f44336 !important;
193
  background-color: #ffebee;
194
  }
195
 
 
196
  .error-message {
197
  color: #ff4d4d;
198
  font-weight: bold;
 
201
  text-align: center;
202
  }
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  .action-buttons {
205
  width: 100%;
206
  display: flex;
 
252
  cursor: not-allowed;
253
  }
254
 
 
255
  .generate-buttons {
256
  display: flex;
257
  gap: 10px;
 
293
  background-color: #e68900;
294
  }
295
 
 
296
  @media (max-width: 768px) {
297
  .game-content {
298
  flex-direction: column;
 
314
  }
315
  }
316
 
 
317
  .popup-overlay {
318
  position: fixed;
319
  top: 0;
 
328
  }
329
 
330
  .popup-content {
331
+ background-color: #ffffff;
332
  padding: 2vw;
333
  border-radius: 8px;
334
  text-align: center;
335
+ border: 10px solid var(--main-accent-color);
336
+ max-width: 50vw;
337
  }
338
 
339
  .popup-content p {
340
+ font-size: 1.3vw;
341
  margin-bottom: 2vw;
342
  color: #004d5c;
343
  font-weight: bold;
 
355
  }
356
  }
357
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  .game-header {
359
  position: relative;
360
  width: 100%;
 
365
  }
366
 
367
  .back-btn {
 
368
  left: 0;
369
  top: 40%;
 
 
 
 
 
 
 
 
370
  width: 6vw;
371
  }
372
 
 
 
 
 
373
  .game-title {
374
  font-size: 2vw;
375
  color: #004d5c;
 
376
  margin: 0;
377
  text-align: center;
378
  }
379
 
 
 
 
 
380
  .info-btn:disabled {
381
+ background-color: #b0bec5;
382
+ color: #757575;
383
  cursor: not-allowed;
384
  opacity: 0.6;
385
  }
386
 
 
 
 
 
 
 
387
  .audio-card--kids {
388
  --primary: #006780;
389
  --accent: #009688;
 
445
  filter: grayscale(0.2);
446
  }
447
 
 
448
  .play-btn {
449
  position: relative;
450
  width: 65px;
 
510
  }
511
  }
512
 
 
513
  .wave-shell {
514
  display: flex;
515
  align-items: flex-end;
 
526
  background: var(--bar);
527
  border-radius: 4px;
528
  animation: wave 1s ease-in-out infinite;
529
+ animation-play-state: paused;
530
  }
531
 
532
  .ac-player.is-playing .wave-shell .bar {
 
545
  }
546
  }
547
 
 
548
  .timeline {
549
  position: relative;
550
  height: 10px;
 
568
  transition: width 0.15s linear;
569
  }
570
 
 
571
  .time {
572
  width: 80%;
573
  max-width: 520px;
 
577
  color: #004d5c;
578
  }
579
 
 
580
  .ac-hint {
581
  font-size: 0.95rem;
582
  color: #004d5c;
 
586
  border-radius: 10px;
587
  }
588
 
 
589
  @media (max-width: 768px) {
590
  .play-btn {
591
  width: 84px;
 
606
  }
607
  }
608
 
 
 
 
 
 
609
  .kid-panel {
610
  --primary: #006780;
611
  --accent: #009688;
 
620
  position: relative;
621
  }
622
 
 
623
  .kp-top {
624
  display: flex;
625
  align-items: center;
 
633
  font-weight: 800;
634
  }
635
 
 
636
  .kp-attempts .heart {
637
  font-size: 1.15rem;
638
  margin-left: 6px;
639
  transition: opacity 0.25s ease, transform 0.25s ease;
 
640
  }
641
 
642
  .kp-attempts .heart.is-off {
 
644
  transform: scale(0.88);
645
  }
646
 
 
647
  .kp-input-wrap {
648
  position: relative;
649
  display: grid;
 
671
  border-color: var(--accent);
672
  }
673
 
 
674
  .kp-input.correct {
675
  border-color: #4caf50 !important;
676
  background: #e8f5e9;
 
681
  background: #ffebee;
682
  }
683
 
 
684
  .ok-badge {
685
  position: absolute;
686
  right: 8%;
 
727
  }
728
  }
729
 
 
730
  .kid-panel.is-shake .kp-input {
731
  animation: kpShake 0.35s ease;
732
  }
 
749
  }
750
  }
751
 
 
752
  .kp-submit {
753
  width: 44%;
754
  margin: 0 auto;
 
757
  font-size: 1.2vw;
758
  }
759
 
 
760
  .info-panel {
761
  background: #ffffffd9;
762
  border: 1px dashed var(--accent);
 
791
  font-size: 0.98rem;
792
  }
793
 
 
794
  @media (max-width: 768px) {
795
  .kp-title {
796
  font-size: 1.05rem;
 
810
  }
811
  }
812
 
 
 
813
  .action-bar {
814
  display: flex;
815
  justify-content: space-between;
 
824
  flex-wrap: wrap;
825
  }
826
 
 
827
  .py-btn {
828
  background-color: #006780;
829
  color: #ffffff;
830
  border: none;
831
  padding: 15px 32px;
832
+ font-size: 1.2vw;
833
+ border-radius: 0.5vw;
834
  cursor: pointer;
835
  transition: background-color 0.3s, transform 0.3s;
836
  font-weight: bold;
 
840
  line-height: 1;
841
  }
842
 
 
843
  .py-btn:hover:not(:disabled) {
844
  background-color: #18788f;
845
  transform: scale(1.05);
846
  }
847
 
 
848
  .py-btn:active:not(:disabled) {
849
  transform: scale(0.98);
850
  }
851
 
 
852
  .py-btn:disabled {
853
  opacity: 0.55;
854
  cursor: not-allowed;
855
  transform: none;
856
  }
857
 
 
858
  .py-btn:focus-visible {
859
  outline: 3px solid #94c7d6;
860
  outline-offset: 2px;
861
  }
862
 
 
863
  .py-btn .btn__icon {
864
  display: inline-block;
865
  transform: translateY(0);
866
  transition: transform 0.25s ease;
 
867
  }
868
 
869
  .py-btn:hover:not(:disabled) .btn__icon {
870
  transform: translateY(-2px);
871
  }
872
 
 
873
  @media (max-width: 768px) {
874
  .py-btn {
875
+ font-size: 16px;
876
+ border-radius: 10px;
877
  padding: 12px 22px;
878
  }
879
  }
880
 
881
+ .user-guide-close-icon {
882
+ top: -5.4vw;
 
 
 
 
 
 
 
 
 
883
  right: -3.5vw;
 
 
 
 
884
  }
885
 
 
 
 
 
 
 
 
 
 
 
 
886
  .left-illustration {
887
  position: absolute;
888
  left: -2vw;
 
898
  image-rendering: crisp-edges;
899
  }
900
 
 
901
  @media (max-width: 768px) {
902
  .left-illustration {
903
  width: 170px;
src/app/findword/findword.component.html CHANGED
@@ -1,7 +1,6 @@
1
  <div class="full-container">
2
  <app-header [title]="'Find the Word'"></app-header>
3
 
4
-
5
  <img src="assets/images/grammar-bg.png" alt="Chat Background" class="grammar-bg" />
6
 
7
  <div class="findword-container">
@@ -18,25 +17,19 @@
18
  If needed, they can listen again. The exercise also has buttons to show the word’s meaning and an example sentence.
19
  A reset option lets students try again. This activity helps students practice listening and writing skills in an easy and engaging way, making learning more enjoyable and effective.
20
  </p>
21
- <button (click)="startGame()" class="submit-button">Start Learning</button>
22
  </div>
23
  </div>
24
  </div>
25
 
26
-
27
-
28
  <div *ngIf="step === 2" class="game-screen">
29
  <div class="game-header">
30
  <img src="assets/images/back.png" alt="Go Back" class="back-btn" (click)="goBack()" />
31
  <h2 class="game-title">Listen & Type</h2>
32
- <!--<img class="wave-photo" src="assets/images/find_word/audio.png" alt="Learning image">-->
33
- <button class="close-btn1" (click)=" closeToStart()" aria-label="Close">✖</button>
34
  </div>
35
 
36
  <div class="game-content">
37
-
38
- <!-- Audio Section -->
39
- <!-- Audio Section (Left) -->
40
  <div class="audio-card audio-card--kids">
41
  <div class="ac-header">
42
  <button (click)="fetchAudio()"
@@ -44,22 +37,18 @@
44
  class="btn generate-btn ac-generate"
45
  aria-label="Generate a new question">
46
  <span *ngIf="!isLoading">Generate audio</span>
47
- <!-- <span *ngIf="isLoading" class="spinner" aria-hidden="true"></span>-->
48
  <span *ngIf="isLoading">Generating</span>
49
  </button>
50
  </div>
51
- <!-- Left mascot (decorative) -->
52
  <img class="left-illustration"
53
  src="assets/images/find_word/audio.png"
54
  alt="" />
55
- <!-- Player Shell -->
56
  <div class="ac-player" [class.is-disabled]="!audioUrl" [class.is-playing]="isPlaying">
57
  <button class="play-btn"
58
  [disabled]="!audioUrl"
59
  (click)="togglePlayback()"
60
  [attr.aria-pressed]="isPlaying"
61
  [attr.aria-label]="isPlaying ? 'Pause audio' : 'Play audio'">
62
- <!-- Inline SVG icons (no external deps) -->
63
  <svg *ngIf="!isPlaying" viewBox="0 0 24 24" class="icon">
64
  <path d="M8 5v14l11-7z"></path>
65
  </svg>
@@ -69,25 +58,19 @@
69
  <span class="pulse" aria-hidden="true"></span>
70
  </button>
71
 
72
-
73
- <!-- Wave shell (animated bars while playing) -->
74
  <div class="wave-shell" aria-hidden="true">
75
  <span class="bar" *ngFor="let b of bars; let i = index" [style.animation-delay.ms]="(i%6)*120"></span>
76
  </div>
77
 
78
- <!-- Timeline -->
79
  <div class="timeline" [class.is-disabled]="!audioUrl">
80
  <div class="progress" [style.width.%]="progress"></div>
81
  </div>
82
 
83
-
84
-
85
  <div class="ac-hint" *ngIf="!audioUrl">
86
  Tap <strong>Generate audio</strong> to load the audio.
87
  </div>
88
  </div>
89
 
90
- <!-- Keep the audio element; wire player events -->
91
  <audio #audioPlayer
92
  (timeupdate)="onTimeUpdate()"
93
  (ended)="onAudioEnded()"
@@ -98,30 +81,17 @@
98
  </audio>
99
  </div>
100
 
101
-
102
-
103
-
104
-
105
-
106
-
107
- <!-- RIGHT SIDE: Kid-friendly input panel -->
108
  <div class="input-card kid-panel"
109
  [class.is-correct]="isCorrect"
110
  [class.is-shake]="ui.shake">
111
-
112
- <!-- Top row: title + attempts -->
113
  <div class="kp-top">
114
  <h3 class="kp-title">Type the word</h3>
115
-
116
- <!-- Attempts with hearts -->
117
  <div class="kp-attempts" aria-label="Attempts left">
118
  <span class="heart" [class.is-off]="attemptsLeft < 1"></span>
119
  <span class="heart" [class.is-off]="attemptsLeft < 2"></span>
120
  <span class="heart" [class.is-off]="attemptsLeft < 3"></span>
121
  </div>
122
  </div>
123
-
124
- <!-- Input + success badge -->
125
  <div class="kp-input-wrap">
126
  <input type="text"
127
  class="kp-input"
@@ -130,8 +100,6 @@
130
  (ngModelChange)="onInputChange()"
131
  placeholder="Type what you heard"
132
  [ngClass]="{ 'correct': isCorrect, 'error': !isCorrect && validationMessage }" />
133
-
134
- <!-- Pop tick on correct -->
135
  <div class="ok-badge" *ngIf="isCorrect || ui?.pulseOk" aria-hidden="true">
136
  <svg viewBox="0 0 24 24" class="ok-icon">
137
  <circle cx="12" cy="12" r="10"></circle>
@@ -139,61 +107,40 @@
139
  </svg>
140
  </div>
141
  </div>
142
-
143
- <!-- Validation message (only when needed) -->
144
  <p *ngIf="validationMessage" class="error-message">{{ validationMessage }}</p>
145
-
146
- <!-- Primary action -->
147
- <button (click)="validateWord()"
148
- [disabled]="!canSubmit || attemptsLeft === 0"
149
- class="btn submit-btn kp-submit">
150
  Submit
151
- </button>
152
-
153
  <div class="action-bar">
154
  <div class="left-buttons">
155
- <button class="py-btn"
156
- (click)="showMeaningPanel()"
157
- [disabled]="isMeaningButtonDisabled">
158
  <span class="btn__icon" aria-hidden="true">📘</span>
159
  <span>Meaning</span>
160
- </button>
161
 
162
- <button class="py-btn"
163
- (click)="showSentencePanel()"
164
- [disabled]="isExampleButtonDisabled">
165
  <span class="btn__icon" aria-hidden="true">✍️</span>
166
  <span>Example</span>
167
- </button>
168
  </div>
169
 
170
- <button class="py-btn"
171
- (click)="nextQuestion()"
172
- [disabled]="attemptsLeft > 0 && !isCorrect">
173
  <span class="btn__icon" aria-hidden="true">⟲</span>
174
  <span>Reset</span>
175
- </button>
176
  </div>
177
-
178
-
179
  </div>
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
  </div>
188
  </div>
189
 
190
-
191
-
192
-
193
  <div *ngIf="isPopupVisible" class="popup-overlay">
194
  <div class="popup-content">
195
  <p>{{ popupMessage }}</p>
196
- <button class="btn close-btn" (click)="closePopup()">Close</button>
197
  </div>
198
  </div>
199
  </div>
 
1
  <div class="full-container">
2
  <app-header [title]="'Find the Word'"></app-header>
3
 
 
4
  <img src="assets/images/grammar-bg.png" alt="Chat Background" class="grammar-bg" />
5
 
6
  <div class="findword-container">
 
17
  If needed, they can listen again. The exercise also has buttons to show the word’s meaning and an example sentence.
18
  A reset option lets students try again. This activity helps students practice listening and writing skills in an easy and engaging way, making learning more enjoyable and effective.
19
  </p>
20
+ <app-button (click)="startGame()">Start Learning</app-button>
21
  </div>
22
  </div>
23
  </div>
24
 
 
 
25
  <div *ngIf="step === 2" class="game-screen">
26
  <div class="game-header">
27
  <img src="assets/images/back.png" alt="Go Back" class="back-btn" (click)="goBack()" />
28
  <h2 class="game-title">Listen & Type</h2>
29
+ <button class="user-guide-close-icon" (click)=" closeToStart()">×</button>
 
30
  </div>
31
 
32
  <div class="game-content">
 
 
 
33
  <div class="audio-card audio-card--kids">
34
  <div class="ac-header">
35
  <button (click)="fetchAudio()"
 
37
  class="btn generate-btn ac-generate"
38
  aria-label="Generate a new question">
39
  <span *ngIf="!isLoading">Generate audio</span>
 
40
  <span *ngIf="isLoading">Generating</span>
41
  </button>
42
  </div>
 
43
  <img class="left-illustration"
44
  src="assets/images/find_word/audio.png"
45
  alt="" />
 
46
  <div class="ac-player" [class.is-disabled]="!audioUrl" [class.is-playing]="isPlaying">
47
  <button class="play-btn"
48
  [disabled]="!audioUrl"
49
  (click)="togglePlayback()"
50
  [attr.aria-pressed]="isPlaying"
51
  [attr.aria-label]="isPlaying ? 'Pause audio' : 'Play audio'">
 
52
  <svg *ngIf="!isPlaying" viewBox="0 0 24 24" class="icon">
53
  <path d="M8 5v14l11-7z"></path>
54
  </svg>
 
58
  <span class="pulse" aria-hidden="true"></span>
59
  </button>
60
 
 
 
61
  <div class="wave-shell" aria-hidden="true">
62
  <span class="bar" *ngFor="let b of bars; let i = index" [style.animation-delay.ms]="(i%6)*120"></span>
63
  </div>
64
 
 
65
  <div class="timeline" [class.is-disabled]="!audioUrl">
66
  <div class="progress" [style.width.%]="progress"></div>
67
  </div>
68
 
 
 
69
  <div class="ac-hint" *ngIf="!audioUrl">
70
  Tap <strong>Generate audio</strong> to load the audio.
71
  </div>
72
  </div>
73
 
 
74
  <audio #audioPlayer
75
  (timeupdate)="onTimeUpdate()"
76
  (ended)="onAudioEnded()"
 
81
  </audio>
82
  </div>
83
 
 
 
 
 
 
 
 
84
  <div class="input-card kid-panel"
85
  [class.is-correct]="isCorrect"
86
  [class.is-shake]="ui.shake">
 
 
87
  <div class="kp-top">
88
  <h3 class="kp-title">Type the word</h3>
 
 
89
  <div class="kp-attempts" aria-label="Attempts left">
90
  <span class="heart" [class.is-off]="attemptsLeft < 1"></span>
91
  <span class="heart" [class.is-off]="attemptsLeft < 2"></span>
92
  <span class="heart" [class.is-off]="attemptsLeft < 3"></span>
93
  </div>
94
  </div>
 
 
95
  <div class="kp-input-wrap">
96
  <input type="text"
97
  class="kp-input"
 
100
  (ngModelChange)="onInputChange()"
101
  placeholder="Type what you heard"
102
  [ngClass]="{ 'correct': isCorrect, 'error': !isCorrect && validationMessage }" />
 
 
103
  <div class="ok-badge" *ngIf="isCorrect || ui?.pulseOk" aria-hidden="true">
104
  <svg viewBox="0 0 24 24" class="ok-icon">
105
  <circle cx="12" cy="12" r="10"></circle>
 
107
  </svg>
108
  </div>
109
  </div>
 
 
110
  <p *ngIf="validationMessage" class="error-message">{{ validationMessage }}</p>
111
+ <app-button (click)="validateWord()"
112
+ [disabled]="!canSubmit || attemptsLeft === 0">
 
 
 
113
  Submit
114
+ </app-button>
 
115
  <div class="action-bar">
116
  <div class="left-buttons">
117
+ <app-button (click)="showMeaningPanel()"
118
+ [disabled]="isMeaningButtonDisabled">
 
119
  <span class="btn__icon" aria-hidden="true">📘</span>
120
  <span>Meaning</span>
121
+ </app-button>
122
 
123
+ <app-button (click)="showSentencePanel()"
124
+ [disabled]="isExampleButtonDisabled">
 
125
  <span class="btn__icon" aria-hidden="true">✍️</span>
126
  <span>Example</span>
127
+ </app-button>
128
  </div>
129
 
130
+ <app-button (click)="nextQuestion()"
131
+ [disabled]="attemptsLeft > 0 && !isCorrect">
 
132
  <span class="btn__icon" aria-hidden="true">⟲</span>
133
  <span>Reset</span>
134
+ </app-button>
135
  </div>
 
 
136
  </div>
 
 
 
 
 
 
 
137
  </div>
138
  </div>
139
 
 
 
 
140
  <div *ngIf="isPopupVisible" class="popup-overlay">
141
  <div class="popup-content">
142
  <p>{{ popupMessage }}</p>
143
+ <app-button (click)="closePopup()">Close</app-button>
144
  </div>
145
  </div>
146
  </div>
src/app/findword/findword.component.ts CHANGED
@@ -1,17 +1,22 @@
1
  import { Component, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
2
  import { FindwordService } from '../findword/findword.service';
3
  import { Router } from '@angular/router';
 
 
 
 
4
 
5
  @Component({
6
  selector: 'app-findword',
7
  templateUrl: './findword.component.html',
8
- styleUrl: './findword.component.css'
 
 
9
  })
10
  export class FindwordComponent {
11
  @ViewChild('audioPlayer', { static: false }) audioPlayer?: ElementRef<HTMLAudioElement>;
12
  @ViewChild('audioPlayer') audioRef!: ElementRef<HTMLAudioElement>;
13
 
14
-
15
  step: number = 1;
16
  progress: number = 0;
17
  audioUrl: string | null = null;
@@ -31,8 +36,6 @@ export class FindwordComponent {
31
  audioFinished: boolean = false;
32
  canSubmit: boolean = false;
33
  isPlaying: boolean = false;
34
-
35
-
36
  currentTimeDisplay = '0:00';
37
  durationDisplay = '0:00';
38
  constructor(
@@ -41,7 +44,6 @@ export class FindwordComponent {
41
  private router: Router
42
  ) { }
43
 
44
- // Fetch audio from backend
45
  fetchAudio() {
46
  this.isLoading = true;
47
  this.correctWord = null;
@@ -69,10 +71,7 @@ export class FindwordComponent {
69
  this.wordSentence = response.sentence;
70
  console.log("✅ Correct Word Set:", this.correctWord);
71
 
72
- // ✅ use service helper (works on local & HF)
73
  this.audioUrl = this.findwordService.buildAssetUrl(response.audio_file_path);
74
-
75
-
76
  this.cd.detectChanges();
77
 
78
  setTimeout(() => {
@@ -122,61 +121,8 @@ export class FindwordComponent {
122
  this.isPopupVisible = true;
123
  }
124
 
125
- //validateWord() {
126
- // if (this.attemptsLeft <= 0) {
127
- // // Show Meaning and Example buttons after all attempts are used
128
- // this.showMeaning = true;
129
- // this.showSentence = true;
130
- // return;
131
- // }
132
-
133
- // console.log(`📝 Validating answer: ${this.userInput} (Attempts left: ${this.attemptsLeft})`);
134
-
135
- // if (!this.correctWord) {
136
- // console.error("❌ Error: No correct word set for validation!");
137
- // this.validationMessage = 'Error validating word: Missing correct word.';
138
- // this.showMeaning = true;
139
- // this.showSentence = true;
140
- // return;
141
- // }
142
-
143
- // const requestData = {
144
- // user_input: this.userInput.trim(),
145
- // correct_word: this.correctWord.trim().replace('.', '')
146
- // };
147
- // console.log("📤 Sending validation request:", requestData);
148
-
149
- // this.findwordService.validateWord(this.userInput, this.correctWord).subscribe(response => {
150
- // console.log("📥 Validation Response:", response);
151
-
152
- // if (response.status === 'success') {
153
- // this.validationMessage = "✅ Correct!";
154
- // this.isCorrect = true;
155
- // this.attemptsLeft = 0;
156
- // this.popupMessage = this.validationMessage;
157
- // this.isGenerateDisabled = true;
158
- // } else {
159
- // this.attemptsLeft--;
160
- // if (this.attemptsLeft === 0) {
161
- // this.validationMessage = `❌ Incorrect! The correct word was '${this.correctWord}'.`;
162
- // this.correctWord = response.correct_word;
163
- // this.isGenerateDisabled = true;
164
- // this.showMeaning = true;
165
- // this.showSentence = true;
166
- // } else {
167
- // this.validationMessage = `❌ Incorrect! You have ${this.attemptsLeft} attempt(s) left.`;
168
- // }
169
- // this.isCorrect = false;
170
- // }
171
- // }, error => {
172
- // console.error("❌ Validation API Error:", error);
173
- // this.validationMessage = 'Error validating word: API failed.';
174
- // });
175
- //}
176
-
177
  validateWord() {
178
  if (this.attemptsLeft <= 0) {
179
- // Show Meaning and Example buttons after all attempts are used
180
  this.showMeaning = true;
181
  this.showSentence = true;
182
  return;
@@ -204,11 +150,8 @@ export class FindwordComponent {
204
  if (response.status === 'success') {
205
  this.validationMessage = "✅ Correct!";
206
  this.isCorrect = true;
207
-
208
- // ⬅ added: success pulse animation
209
  this.ui.pulseOk = true;
210
  setTimeout(() => (this.ui.pulseOk = false), 1200);
211
-
212
  this.attemptsLeft = 0;
213
  this.popupMessage = this.validationMessage;
214
  this.isGenerateDisabled = true;
@@ -224,8 +167,6 @@ export class FindwordComponent {
224
  this.validationMessage = `❌ Incorrect! You have ${this.attemptsLeft} attempt(s) left.`;
225
  }
226
  this.isCorrect = false;
227
-
228
- // ⬅ added: gentle shake on wrong answer
229
  this.ui.shake = true;
230
  setTimeout(() => (this.ui.shake = false), 400);
231
  }
@@ -235,7 +176,6 @@ export class FindwordComponent {
235
  });
236
  }
237
 
238
-
239
  onAudioReady() {
240
  console.log("🎧 Audio is ready, trying to play...");
241
  if (this.audioPlayer) {
@@ -251,11 +191,6 @@ export class FindwordComponent {
251
  this.audioPlayer.nativeElement.pause();
252
  }
253
  }
254
- //playAudio() {
255
- // if (!this.audioUrl) {
256
- // console.error("❌ Error: Audio URL is empty!");
257
- // return;
258
- // }
259
  playAudio() {
260
  if (this.audioPlayer?.nativeElement) {
261
  this.audioPlayer.nativeElement.play();
@@ -281,8 +216,8 @@ export class FindwordComponent {
281
  this.validationMessage = '';
282
  this.attemptsLeft = 3;
283
  this.correctWord = null;
284
- this.showMeaning = false; // Hide Meaning button
285
- this.showSentence = false; // Hide Example button
286
  this.isCorrect = false;
287
  this.isLoading = false;
288
  this.fetchAudio();
@@ -327,20 +262,14 @@ export class FindwordComponent {
327
  this.isPopupVisible = false;
328
  }
329
 
330
-
331
-
332
- // Meaning button logic
333
  get isMeaningButtonDisabled(): boolean {
334
  return !(this.isCorrect || this.attemptsLeft <= 2);
335
  }
336
 
337
- // Example button logic
338
  get isExampleButtonDisabled(): boolean {
339
  return !(this.isCorrect || this.attemptsLeft === 0);
340
  }
341
 
342
-
343
- // 12 bars looks lively without being noisy for kids
344
  bars = Array.from({ length: 12 });
345
 
346
  togglePlayback(): void {
@@ -351,7 +280,6 @@ export class FindwordComponent {
351
  audio.play().then(() => {
352
  this.isPlaying = true;
353
  }).catch(() => {
354
- // Autoplay guard
355
  this.isPlaying = false;
356
  });
357
  } else {
@@ -367,7 +295,6 @@ export class FindwordComponent {
367
  this.currentTimeDisplay = '0:00';
368
  this.progress = 0;
369
  this.isPlaying = false;
370
- // Optional: lock typing until finished, if your flow needs it
371
  if (typeof this.audioFinished !== 'undefined') {
372
  this.audioFinished = false;
373
  }
@@ -385,7 +312,6 @@ export class FindwordComponent {
385
  onAudioEnded(): void {
386
  this.isPlaying = false;
387
  this.progress = 100;
388
- // Enable typing only after full listen (matches your current UX)
389
  if (typeof this.audioFinished !== 'undefined') {
390
  this.audioFinished = true;
391
  }
@@ -398,7 +324,6 @@ export class FindwordComponent {
398
  return `${m}:${s.toString().padStart(2, '0')}`;
399
  }
400
 
401
- // Optional: if your existing fetchAudio resets the state, ensure it also:
402
  private resetPlayerUI(): void {
403
  this.isPlaying = false;
404
  this.progress = 0;
@@ -406,7 +331,6 @@ export class FindwordComponent {
406
  this.durationDisplay = '0:00';
407
  }
408
 
409
-
410
  ui = {
411
  pulseOk: false,
412
  shake: false,
@@ -416,37 +340,28 @@ export class FindwordComponent {
416
  exampleText: ''
417
  };
418
 
419
-
420
-
421
  private resetStep2State(): void {
422
- // stop & clear audio
423
  const audio = this.audioRef?.nativeElement;
424
  if (audio) {
425
  try { audio.pause(); } catch { }
426
  audio.currentTime = 0;
427
  }
428
- this.audioUrl = null; // forces "Generate audio" again
429
  this.isPlaying = false;
430
  this.progress = 0;
431
  this.currentTimeDisplay = '0:00';
432
  this.durationDisplay = '0:00';
433
-
434
- // quiz state
435
  this.userInput = '';
436
  this.validationMessage = '';
437
  this.isCorrect = false;
438
  this.attemptsLeft = 3;
439
  this.canSubmit = false;
440
- this.audioFinished = false; // input stays disabled until they finish the new audio
441
-
442
- // metadata/buttons
443
  this.correctWord = null;
444
  this.wordMeaning = null;
445
  this.wordSentence = null;
446
- this.isGenerateDisabled = false; // enable the "Generate audio" button
447
  this.isLoading = false;
448
-
449
- // meaning/example UI (if you use the `ui` object)
450
  this.showMeaning = false;
451
  this.showSentence = false;
452
  if (this.ui) {
@@ -457,22 +372,17 @@ export class FindwordComponent {
457
  this.ui.meaningText = '';
458
  this.ui.exampleText = '';
459
  }
460
-
461
- // popup
462
  this.isPopupVisible = false;
463
  this.popupMessage = '';
464
  }
465
 
466
  closeToStart(): void {
467
- // Safely stop and reset audio if present
468
  const audio = this.audioRef?.nativeElement;
469
  if (audio) {
470
  try { audio.pause(); } catch { }
471
  audio.currentTime = 0;
472
  }
473
  this.isPlaying = false;
474
-
475
- // Clear key Step 2 data so the page is fresh when user comes back
476
  this.audioUrl = null;
477
  this.userInput = '';
478
  this.validationMessage = '';
@@ -486,11 +396,8 @@ export class FindwordComponent {
486
  this.showSentence = false;
487
  this.isPopupVisible = false;
488
  this.popupMessage = '';
489
-
490
- // Go to starting page (Step 1)
491
  this.step = 1;
492
  }
493
-
494
  }
495
 
496
 
 
1
  import { Component, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
2
  import { FindwordService } from '../findword/findword.service';
3
  import { Router } from '@angular/router';
4
+ import { HeaderComponent } from '../shared/header/header.component';
5
+ import { ButtonComponent } from '../shared/button/button.component';
6
+ import { CommonModule } from '@angular/common';
7
+ import { FormsModule } from '@angular/forms';
8
 
9
  @Component({
10
  selector: 'app-findword',
11
  templateUrl: './findword.component.html',
12
+ styleUrl: './findword.component.css',
13
+ standalone: true,
14
+ imports: [ButtonComponent, CommonModule, FormsModule, HeaderComponent]
15
  })
16
  export class FindwordComponent {
17
  @ViewChild('audioPlayer', { static: false }) audioPlayer?: ElementRef<HTMLAudioElement>;
18
  @ViewChild('audioPlayer') audioRef!: ElementRef<HTMLAudioElement>;
19
 
 
20
  step: number = 1;
21
  progress: number = 0;
22
  audioUrl: string | null = null;
 
36
  audioFinished: boolean = false;
37
  canSubmit: boolean = false;
38
  isPlaying: boolean = false;
 
 
39
  currentTimeDisplay = '0:00';
40
  durationDisplay = '0:00';
41
  constructor(
 
44
  private router: Router
45
  ) { }
46
 
 
47
  fetchAudio() {
48
  this.isLoading = true;
49
  this.correctWord = null;
 
71
  this.wordSentence = response.sentence;
72
  console.log("✅ Correct Word Set:", this.correctWord);
73
 
 
74
  this.audioUrl = this.findwordService.buildAssetUrl(response.audio_file_path);
 
 
75
  this.cd.detectChanges();
76
 
77
  setTimeout(() => {
 
121
  this.isPopupVisible = true;
122
  }
123
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  validateWord() {
125
  if (this.attemptsLeft <= 0) {
 
126
  this.showMeaning = true;
127
  this.showSentence = true;
128
  return;
 
150
  if (response.status === 'success') {
151
  this.validationMessage = "✅ Correct!";
152
  this.isCorrect = true;
 
 
153
  this.ui.pulseOk = true;
154
  setTimeout(() => (this.ui.pulseOk = false), 1200);
 
155
  this.attemptsLeft = 0;
156
  this.popupMessage = this.validationMessage;
157
  this.isGenerateDisabled = true;
 
167
  this.validationMessage = `❌ Incorrect! You have ${this.attemptsLeft} attempt(s) left.`;
168
  }
169
  this.isCorrect = false;
 
 
170
  this.ui.shake = true;
171
  setTimeout(() => (this.ui.shake = false), 400);
172
  }
 
176
  });
177
  }
178
 
 
179
  onAudioReady() {
180
  console.log("🎧 Audio is ready, trying to play...");
181
  if (this.audioPlayer) {
 
191
  this.audioPlayer.nativeElement.pause();
192
  }
193
  }
 
 
 
 
 
194
  playAudio() {
195
  if (this.audioPlayer?.nativeElement) {
196
  this.audioPlayer.nativeElement.play();
 
216
  this.validationMessage = '';
217
  this.attemptsLeft = 3;
218
  this.correctWord = null;
219
+ this.showMeaning = false;
220
+ this.showSentence = false;
221
  this.isCorrect = false;
222
  this.isLoading = false;
223
  this.fetchAudio();
 
262
  this.isPopupVisible = false;
263
  }
264
 
 
 
 
265
  get isMeaningButtonDisabled(): boolean {
266
  return !(this.isCorrect || this.attemptsLeft <= 2);
267
  }
268
 
 
269
  get isExampleButtonDisabled(): boolean {
270
  return !(this.isCorrect || this.attemptsLeft === 0);
271
  }
272
 
 
 
273
  bars = Array.from({ length: 12 });
274
 
275
  togglePlayback(): void {
 
280
  audio.play().then(() => {
281
  this.isPlaying = true;
282
  }).catch(() => {
 
283
  this.isPlaying = false;
284
  });
285
  } else {
 
295
  this.currentTimeDisplay = '0:00';
296
  this.progress = 0;
297
  this.isPlaying = false;
 
298
  if (typeof this.audioFinished !== 'undefined') {
299
  this.audioFinished = false;
300
  }
 
312
  onAudioEnded(): void {
313
  this.isPlaying = false;
314
  this.progress = 100;
 
315
  if (typeof this.audioFinished !== 'undefined') {
316
  this.audioFinished = true;
317
  }
 
324
  return `${m}:${s.toString().padStart(2, '0')}`;
325
  }
326
 
 
327
  private resetPlayerUI(): void {
328
  this.isPlaying = false;
329
  this.progress = 0;
 
331
  this.durationDisplay = '0:00';
332
  }
333
 
 
334
  ui = {
335
  pulseOk: false,
336
  shake: false,
 
340
  exampleText: ''
341
  };
342
 
 
 
343
  private resetStep2State(): void {
 
344
  const audio = this.audioRef?.nativeElement;
345
  if (audio) {
346
  try { audio.pause(); } catch { }
347
  audio.currentTime = 0;
348
  }
349
+ this.audioUrl = null;
350
  this.isPlaying = false;
351
  this.progress = 0;
352
  this.currentTimeDisplay = '0:00';
353
  this.durationDisplay = '0:00';
 
 
354
  this.userInput = '';
355
  this.validationMessage = '';
356
  this.isCorrect = false;
357
  this.attemptsLeft = 3;
358
  this.canSubmit = false;
359
+ this.audioFinished = false;
 
 
360
  this.correctWord = null;
361
  this.wordMeaning = null;
362
  this.wordSentence = null;
363
+ this.isGenerateDisabled = false;
364
  this.isLoading = false;
 
 
365
  this.showMeaning = false;
366
  this.showSentence = false;
367
  if (this.ui) {
 
372
  this.ui.meaningText = '';
373
  this.ui.exampleText = '';
374
  }
 
 
375
  this.isPopupVisible = false;
376
  this.popupMessage = '';
377
  }
378
 
379
  closeToStart(): void {
 
380
  const audio = this.audioRef?.nativeElement;
381
  if (audio) {
382
  try { audio.pause(); } catch { }
383
  audio.currentTime = 0;
384
  }
385
  this.isPlaying = false;
 
 
386
  this.audioUrl = null;
387
  this.userInput = '';
388
  this.validationMessage = '';
 
396
  this.showSentence = false;
397
  this.isPopupVisible = false;
398
  this.popupMessage = '';
 
 
399
  this.step = 1;
400
  }
 
401
  }
402
 
403
 
src/app/footer/footer.component.css CHANGED
@@ -21,7 +21,7 @@
21
  padding: 1vw;
22
  z-index: 2001;
23
  overflow: visible;
24
- border: 10px solid #009688;
25
  box-sizing: border-box;
26
  font-weight: lighter;
27
  }
@@ -35,30 +35,10 @@
35
  }
36
 
37
  .user-guide-close-icon {
38
- position: absolute;
39
  top: -22px;
40
  right: -22px;
41
- background: #009688;
42
- border: none;
43
- width: 44px;
44
- height: 44px;
45
- border-radius: 50%;
46
- display: flex;
47
- align-items: center;
48
- justify-content: center;
49
- font-size: 2vw;
50
- color: black;
51
- cursor: pointer;
52
- z-index: 2010;
53
- box-shadow: 0 2px 8px rgba(93,145,195,0.18);
54
- transition: background 0.2s, color 0.2s;
55
-
56
  }
57
 
58
- .user-guide-close-icon:hover {
59
- background: white;
60
- color: black;
61
- }
62
 
63
  .user-guide-modal li {
64
  line-height: 1.7;
@@ -79,10 +59,10 @@
79
  font-weight: 600;
80
  }
81
 
82
- .user-guide-modal a:hover {
83
- color: #009688;
84
- text-decoration: underline;
85
- }
86
 
87
  footer {
88
  background: linear-gradient(to right, #011022, #01030a);
@@ -223,7 +203,7 @@ footer .social-icons {
223
  justify-content: center;
224
  line-height: 1;
225
  }
226
-
227
  .footer-watermark-row {
228
  text-align: center;
229
  margin-top: 8px;
 
21
  padding: 1vw;
22
  z-index: 2001;
23
  overflow: visible;
24
+ border: 10px solid var(--main-accent-color);
25
  box-sizing: border-box;
26
  font-weight: lighter;
27
  }
 
35
  }
36
 
37
  .user-guide-close-icon {
 
38
  top: -22px;
39
  right: -22px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
41
 
 
 
 
 
42
 
43
  .user-guide-modal li {
44
  line-height: 1.7;
 
59
  font-weight: 600;
60
  }
61
 
62
+ .user-guide-modal a:hover {
63
+ color: #009688;
64
+ text-decoration: underline;
65
+ }
66
 
67
  footer {
68
  background: linear-gradient(to right, #011022, #01030a);
 
203
  justify-content: center;
204
  line-height: 1;
205
  }
206
+
207
  .footer-watermark-row {
208
  text-align: center;
209
  margin-top: 8px;
src/app/home/home.component.css CHANGED
@@ -306,7 +306,7 @@ h1 {
306
  padding: 1vw;
307
  z-index: 2001;
308
  overflow: visible;
309
- border: 10px solid #009688;
310
  box-sizing: border-box;
311
  }
312
 
@@ -319,30 +319,10 @@ h1 {
319
  }
320
 
321
  .user-guide-close-icon {
322
- position: absolute;
323
  top: -22px;
324
  right: -22px;
325
- background: #009688;
326
- border: none;
327
- width: 44px;
328
- height: 44px;
329
- border-radius: 50%;
330
- display: flex;
331
- align-items: center;
332
- justify-content: center;
333
- font-size: 2vw;
334
- color: black;
335
- cursor: pointer;
336
- z-index: 2010;
337
- box-shadow: 0 2px 8px rgba(93,145,195,0.18);
338
- transition: background 0.2s, color 0.2s;
339
  }
340
 
341
- .user-guide-close-icon:hover {
342
- background: white;
343
- color: black;
344
- }
345
-
346
  .user-guide-modal li {
347
  line-height: 1.7;
348
  font-size: 1.1vw;
 
306
  padding: 1vw;
307
  z-index: 2001;
308
  overflow: visible;
309
+ border: 10px solid var(--main-accent-color);
310
  box-sizing: border-box;
311
  }
312
 
 
319
  }
320
 
321
  .user-guide-close-icon {
 
322
  top: -22px;
323
  right: -22px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  }
325
 
 
 
 
 
 
326
  .user-guide-modal li {
327
  line-height: 1.7;
328
  font-size: 1.1vw;
src/app/listen/listen.component.css CHANGED
@@ -1,4 +1,3 @@
1
-
2
  body, html {
3
  margin: 0;
4
  padding: 0;
@@ -10,10 +9,6 @@ body, html {
10
  background-color: rgb(35 34 32 / 43%);
11
  }
12
 
13
-
14
-
15
-
16
- /* Listen Card Style */
17
  .listen-card1 {
18
  background: rgba(255, 255, 255, 0.9);
19
  width: 80vw;
@@ -27,14 +22,13 @@ body, html {
27
  align-items: flex-start;
28
  padding: 3vw;
29
  gap: 2vw;
30
- border: 10px solid #009688;
31
  border-radius: 1vw;
32
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
33
  margin: 2vw auto;
34
  max-width: 90%;
35
  }
36
 
37
- /* Left content */
38
  .listen-card1 .left-content {
39
  flex: 1;
40
  max-width: 50%;
@@ -53,7 +47,6 @@ body, html {
53
  text-align: justify;
54
  }
55
 
56
- /* Right content */
57
  .listen-card1 .video-upload-container {
58
  flex: 1;
59
  max-width: 50%;
@@ -70,7 +63,6 @@ body, html {
70
  width: 88%;
71
  max-width: 100%;
72
  border-radius: 1vw;
73
- /* box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.2); */
74
  object-fit: contain;
75
  margin-left: 4vw;
76
  }
@@ -87,63 +79,6 @@ body, html {
87
  display: none;
88
  }
89
 
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
- button,
99
- .generate-btn,
100
- .upload-btn,
101
- .reset-btn,
102
- .next-btn,
103
- .prev-btn,
104
- .nav-arrow,
105
- .custom-file-upload,
106
- .replace-btn {
107
- background-color: #006780;
108
- color: white;
109
- border: none;
110
- padding: 15px 32px;
111
- font-size: 1.5vw;
112
- border-radius: 0.5vw;
113
- cursor: pointer;
114
- transition: background-color 0.3s, transform 0.3s;
115
- font-weight: bold;
116
- }
117
-
118
- /* Hover effect */
119
- button:hover,
120
- .generate-btn:hover,
121
- .upload-btn:hover,
122
- .reset-btn:hover,
123
- .next-btn:hover,
124
- .prev-btn:hover,
125
- .nav-arrow:hover,
126
- .custom-file-upload:hover,
127
- .replace-btn:hover {
128
- background-color: #18788f;
129
- transform: scale(1.05);
130
- }
131
-
132
- /* Keep gray for normal disabled buttons */
133
- button:disabled:not(.correct):not(.incorrect),
134
- .generate-btn:disabled,
135
- .upload-btn:disabled {
136
- background-color: #b0b0b0 !important;
137
- color: #ffffff;
138
- cursor: not-allowed;
139
- opacity: 0.6;
140
- transform: none;
141
- }
142
-
143
-
144
-
145
-
146
- /* Loader Overlay */
147
  .loader-overlay {
148
  position: fixed;
149
  top: 0;
@@ -158,9 +93,9 @@ button,
158
  }
159
 
160
  .loader {
161
- font-size: 15px; /* Font size */
162
- width: 1.5em; /* Width of the loader */
163
- height: 1.5em; /* Height of the loader */
164
  border-radius: 50%;
165
  position: relative;
166
  text-indent: -9999em;
@@ -202,13 +137,6 @@ button,
202
  }
203
  }
204
 
205
- /*the question page css*/
206
-
207
- .center-title {
208
- text-align: center;
209
- margin-bottom: 1vw;
210
- }
211
-
212
  .question-block {
213
  background: #ffffff;
214
  padding: 2vw;
@@ -217,11 +145,9 @@ button,
217
  max-width: 80%;
218
  margin: 2vw auto;
219
  position: relative;
220
- border: 10px solid #009688;
221
  border-radius: 1vw;
222
- /* Inside shadow */
223
- box-shadow: inset 0 0 10px rgba(0, 150, 136, 0.5), /* inner glow */
224
- 0 0.4vw 0.8vw rgba(0, 0, 0, 0.2); /* existing outer shadow */
225
  }
226
 
227
  .options-container {
@@ -232,8 +158,6 @@ button,
232
  align-items: center;
233
  }
234
 
235
-
236
-
237
  .options-container button {
238
  background-color: #ffffff;
239
  color: #333;
@@ -244,18 +168,16 @@ button,
244
  font-weight: 600;
245
  display: flex;
246
  align-items: center;
247
- justify-content: center; /* ✅ center text horizontally */
248
  box-shadow: 0 0.2vw 0.5vw rgba(0, 0, 0, 0.1);
249
- min-height: 6vw; /* ✅ ensures consistent height */
250
- height: 100%; /* ensure buttons stretch inside grid cells */
251
  text-align: center;
252
- /*padding: 1vw;*/
253
- white-space: normal; /* allow text to wrap */
254
- word-break: break-word; /* break long words if needed */
255
  width: 21vw;
256
  }
257
 
258
-
259
  .options-container button:hover {
260
  background-color: #f0f9ff;
261
  transform: scale(1.01);
@@ -276,8 +198,6 @@ button,
276
  font-weight: bold;
277
  }
278
 
279
-
280
-
281
  .incorrect {
282
  background-color: #f443367d !important;
283
  border-color: #C62828 !important;
@@ -285,26 +205,6 @@ button,
285
  font-weight: bold;
286
  }
287
 
288
-
289
-
290
- .correct-label,
291
- .incorrect-label {
292
- font-size: 1.5vw;
293
- font-weight: bold;
294
- display: inline-block;
295
- display: block;
296
- text-align: center;
297
- width: 100%;
298
- }
299
-
300
- .correct-label {
301
- color: #2e7d32;
302
- }
303
-
304
- .incorrect-label {
305
- color: #d32f2f;
306
- }
307
-
308
  .question-footer {
309
  display: flex;
310
  justify-content: space-between;
@@ -317,13 +217,16 @@ button,
317
  gap: 1vw;
318
  }
319
 
320
-
321
  .question-heading {
322
  font-size: 2vw;
323
  color: #006780;
324
  margin: 0;
325
  text-align: center;
326
  font-weight: bold;
 
 
 
 
327
  }
328
 
329
  .question-text {
@@ -333,17 +236,6 @@ button,
333
  text-align: left;
334
  }
335
 
336
-
337
- .question-decor-image {
338
- position: absolute;
339
- top: 1vw;
340
- right: 1vw;
341
- width: 5vw;
342
- height: auto;
343
- /* z-index: 2;*/
344
- }
345
-
346
-
347
  .question-content-row {
348
  display: flex;
349
  justify-content: space-between;
@@ -353,16 +245,12 @@ button,
353
  }
354
 
355
  .question-side-image img {
356
- width: 39vw;
357
  height: auto;
358
  max-height: 16vw;
359
  object-fit: contain;
360
  }
361
 
362
-
363
-
364
- /*suceesful message */
365
-
366
  .upload-popup {
367
  position: fixed;
368
  top: 2vw;
@@ -399,16 +287,12 @@ button,
399
  }
400
  }
401
 
402
-
403
  .button-group {
404
  display: flex;
405
  gap: 1vw;
406
  margin-top: 1vw;
407
  }
408
 
409
-
410
- /*header question command close buttomn*/
411
-
412
  .question-header-row {
413
  display: flex;
414
  justify-content: space-between;
@@ -416,27 +300,25 @@ button,
416
  margin-bottom: 1vw;
417
  }
418
 
419
- .question-heading {
420
- flex: 1;
421
- text-align: center;
422
- font-size: 2vw;
423
- color: #006780;
424
- font-weight: bold;
425
- margin: 0;
426
  }
427
 
428
- .close-btn {
429
- background-color: #ffffff;
430
- color: #0097a7;
431
- border: none;
432
- padding: 0.4vw 0.9vw;
433
- border-radius: 50%;
434
- cursor: pointer;
435
- border: 1px solid black;
436
- width: 3vw;
437
- top: -27px;
438
- left: 98.5%;
439
  position: absolute;
440
- height: 3vw;
 
 
 
 
 
 
441
  }
442
 
 
 
 
 
 
1
  body, html {
2
  margin: 0;
3
  padding: 0;
 
9
  background-color: rgb(35 34 32 / 43%);
10
  }
11
 
 
 
 
 
12
  .listen-card1 {
13
  background: rgba(255, 255, 255, 0.9);
14
  width: 80vw;
 
22
  align-items: flex-start;
23
  padding: 3vw;
24
  gap: 2vw;
25
+ border: 10px solid var(--main-accent-color);
26
  border-radius: 1vw;
27
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
28
  margin: 2vw auto;
29
  max-width: 90%;
30
  }
31
 
 
32
  .listen-card1 .left-content {
33
  flex: 1;
34
  max-width: 50%;
 
47
  text-align: justify;
48
  }
49
 
 
50
  .listen-card1 .video-upload-container {
51
  flex: 1;
52
  max-width: 50%;
 
63
  width: 88%;
64
  max-width: 100%;
65
  border-radius: 1vw;
 
66
  object-fit: contain;
67
  margin-left: 4vw;
68
  }
 
79
  display: none;
80
  }
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  .loader-overlay {
83
  position: fixed;
84
  top: 0;
 
93
  }
94
 
95
  .loader {
96
+ font-size: 15px;
97
+ width: 1.5em;
98
+ height: 1.5em;
99
  border-radius: 50%;
100
  position: relative;
101
  text-indent: -9999em;
 
137
  }
138
  }
139
 
 
 
 
 
 
 
 
140
  .question-block {
141
  background: #ffffff;
142
  padding: 2vw;
 
145
  max-width: 80%;
146
  margin: 2vw auto;
147
  position: relative;
148
+ border: 10px solid var(--main-accent-color);
149
  border-radius: 1vw;
150
+ box-shadow: inset 0 0 10px rgba(0, 150, 136, 0.5), 0 0.4vw 0.8vw rgba(0, 0, 0, 0.2);
 
 
151
  }
152
 
153
  .options-container {
 
158
  align-items: center;
159
  }
160
 
 
 
161
  .options-container button {
162
  background-color: #ffffff;
163
  color: #333;
 
168
  font-weight: 600;
169
  display: flex;
170
  align-items: center;
171
+ justify-content: center;
172
  box-shadow: 0 0.2vw 0.5vw rgba(0, 0, 0, 0.1);
173
+ min-height: 6vw;
174
+ height: 100%;
175
  text-align: center;
176
+ white-space: normal;
177
+ word-break: break-word;
 
178
  width: 21vw;
179
  }
180
 
 
181
  .options-container button:hover {
182
  background-color: #f0f9ff;
183
  transform: scale(1.01);
 
198
  font-weight: bold;
199
  }
200
 
 
 
201
  .incorrect {
202
  background-color: #f443367d !important;
203
  border-color: #C62828 !important;
 
205
  font-weight: bold;
206
  }
207
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  .question-footer {
209
  display: flex;
210
  justify-content: space-between;
 
217
  gap: 1vw;
218
  }
219
 
 
220
  .question-heading {
221
  font-size: 2vw;
222
  color: #006780;
223
  margin: 0;
224
  text-align: center;
225
  font-weight: bold;
226
+ flex: 1;
227
+ text-align: center;
228
+ font-weight: bold;
229
+ margin: 0;
230
  }
231
 
232
  .question-text {
 
236
  text-align: left;
237
  }
238
 
 
 
 
 
 
 
 
 
 
 
 
239
  .question-content-row {
240
  display: flex;
241
  justify-content: space-between;
 
245
  }
246
 
247
  .question-side-image img {
248
+ width: 34vw;
249
  height: auto;
250
  max-height: 16vw;
251
  object-fit: contain;
252
  }
253
 
 
 
 
 
254
  .upload-popup {
255
  position: fixed;
256
  top: 2vw;
 
287
  }
288
  }
289
 
 
290
  .button-group {
291
  display: flex;
292
  gap: 1vw;
293
  margin-top: 1vw;
294
  }
295
 
 
 
 
296
  .question-header-row {
297
  display: flex;
298
  justify-content: space-between;
 
300
  margin-bottom: 1vw;
301
  }
302
 
303
+ .user-guide-close-icon {
304
+ top: -1.2vw;
305
+ right: -1.3vw;
 
 
 
 
306
  }
307
 
308
+
309
+
310
+ /*back arrow button*/
311
+ .back-btn {
 
 
 
 
 
 
 
312
  position: absolute;
313
+ top: 3vw;
314
+ left: 1vw;
315
+ transform: translateY(-50%);
316
+ cursor: pointer;
317
+ padding: 0 1rem;
318
+ transition: transform 0.3s;
319
+ width: 6vw;
320
  }
321
 
322
+ .back-btn:hover {
323
+ transform: translateY(-50%) scale(1.1);
324
+ }
src/app/listen/listen.component.html CHANGED
@@ -1,17 +1,9 @@
1
- <!-- Loader shown while waiting -->
2
  <div *ngIf="isLoading" class="loader-overlay">
3
  <div class="loader"></div>
4
  </div>
5
-
6
  <app-header [title]="'Listen'"></app-header>
7
-
8
-
9
- <!-- Background image (optional) -->
10
  <img src="assets/images/listen.png" alt="Chat Background" class="grammar-bg" />
11
-
12
- <!-- Card Layout for Listen Exercise -->
13
  <div class="listen-card1" *ngIf="questions.length === 0">
14
- <!-- Left Section: Description -->
15
  <div class="left-content">
16
  <h2>Welcome to the Listening Exercise</h2>
17
  <p>
@@ -20,142 +12,108 @@
20
  <strong>Generate Questions</strong> button will be enabled.
21
  Answer questions to practice and strengthen understanding and memory.
22
  </p>
23
-
24
- <button class="generate-btn"
25
- [disabled]="!isGenerateEnabled"
26
- (click)="generateQuestions()">
27
  Generate Questions
28
- </button>
29
  </div>
30
-
31
- <!-- Right Section: Video Upload -->
32
  <div class="video-upload-container">
33
- <!-- Show placeholder image until video is uploaded -->
34
  <div class="video-preview-container" *ngIf="!videoUrl">
35
  <img src="assets/images/listen/listen3.png"
36
  alt="Upload a video"
37
  class="video-placeholder" />
38
  </div>
39
-
40
- <!-- Video preview after upload -->
41
  <div class="video-preview-container" *ngIf="videoUrl">
42
  <video id="videoPreview"
43
  controls
44
  (ended)="onVideoEnded()"
45
  controlsList="nodownload"
46
  (error)="onVideoError()">
47
- <source [src]="videoUrl" [type]="getVideoMimeType(videoUrl)" />
 
48
  Your browser does not support the video tag.
49
  </video>
50
  </div>
51
-
52
- <!-- Upload input -->
53
  <input #uploadInput
54
  type="file"
55
  accept="video/mp4"
56
  (change)="onFileSelected($event)"
57
  class="video-upload-input" />
58
-
59
- <!-- Buttons -->
60
  <div class="button-group">
61
- <button *ngIf="videoUrl"
62
- class="upload-btn"
63
- (click)="toggleVideoPlayPause()">
64
  {{ isVideoPlaying ? 'Pause' : 'Play' }}
65
- </button>
66
-
67
- <button class="replace-btn"
68
- (click)="stopVideoIfPlaying(); uploadInput.click()"
69
- [disabled]="isReplaceDisabled">
70
  {{ uploadSuccess ? 'Replace Video' : 'Upload Video' }}
71
- </button>
72
-
73
- <button *ngIf="videoUrl && isReplaceDisabled"
74
- class="reset-btn"
75
- (click)="goToListen()">
76
  Reset
77
- </button>
78
  </div>
79
  </div>
80
  </div>
81
-
82
-
83
-
84
-
85
  <div *ngIf="questions.length > 0" class="question-navigation">
86
-
87
  <div class="question-block">
88
-
89
  <div class="question-header-row">
90
- <div class="spacer"></div>
91
- <h2 class="question-heading">Question {{ currentQuestionIndex + 1 }} of {{ questions.length }}</h2>
92
- <button class="close-btn" (click)="goToListen()" aria-label="Close">✖</button>
 
 
 
 
 
 
 
 
 
93
  </div>
94
-
95
- <p class="question-text">{{ questions[currentQuestionIndex].question }}</p>
96
-
97
-
98
  <div class="question-content-row">
99
- <!-- Left: Options -->
100
  <div class="options-container">
101
  <button *ngFor="let option of questions[currentQuestionIndex].options; let i = index"
102
  (click)="checkAnswerTemp(i)"
103
  [ngClass]="{
104
- 'selected-option': selectedOptionIndex === i && !validated,
105
- 'correct': validated && option === questions[currentQuestionIndex].answer,
106
- 'incorrect': validated && selectedOptionIndex === i && option !== questions[currentQuestionIndex].answer
107
- }"
108
  [disabled]="validated">
109
  {{ option }}
110
  </button>
111
  </div>
112
-
113
- <!-- Right: Decorative Image -->
114
  <div class="question-side-image">
115
  <img [src]="getFeedbackImage()" alt="Rabbit Feedback Image" />
116
  </div>
117
  </div>
118
-
119
-
120
-
121
  <div class="question-footer">
122
- <!-- Previous Button -->
123
- <button class="prev-btn"
124
- (click)="prevQuestion()"
125
- [disabled]="currentQuestionIndex === 0">
126
  ◀ Previous
127
- </button>
128
-
129
- <!-- Right Side Buttons -->
130
  <div class="right-buttons">
131
- <!-- Submit (before validation) -->
132
- <button *ngIf="!validated"
133
- class="submit-btn"
134
- (click)="validateAnswer(questions[currentQuestionIndex].answer, currentQuestionIndex)"
135
- [disabled]="selectedOptionIndex === null">
136
  Submit
137
- </button>
138
-
139
- <!-- Next (after validation and not last question) -->
140
- <button *ngIf="validated && currentQuestionIndex < questions.length - 1"
141
- class="next-btn"
142
- (click)="nextQuestion()">
143
  Next ▶
144
- </button>
145
-
146
- <!-- Reset (on last question after validation) -->
147
- <button *ngIf="validated && currentQuestionIndex === questions.length - 1"
148
- class="reset-btn"
149
- (click)="resetQuestions()">
150
  Regenerate Questions
151
- </button>
152
  </div>
153
  </div>
154
  </div>
155
  </div>
156
-
157
-
158
- <!-- Top-Right Popup Notification -->
159
  <div *ngIf="popupMessage" class="upload-popup">
160
  {{ popupMessage }}
161
  </div>
 
 
1
  <div *ngIf="isLoading" class="loader-overlay">
2
  <div class="loader"></div>
3
  </div>
 
4
  <app-header [title]="'Listen'"></app-header>
 
 
 
5
  <img src="assets/images/listen.png" alt="Chat Background" class="grammar-bg" />
 
 
6
  <div class="listen-card1" *ngIf="questions.length === 0">
 
7
  <div class="left-content">
8
  <h2>Welcome to the Listening Exercise</h2>
9
  <p>
 
12
  <strong>Generate Questions</strong> button will be enabled.
13
  Answer questions to practice and strengthen understanding and memory.
14
  </p>
15
+ <app-button [disabled]="!isGenerateEnabled"
16
+ (click)="generateQuestions()">
 
 
17
  Generate Questions
18
+ </app-button>
19
  </div>
 
 
20
  <div class="video-upload-container">
 
21
  <div class="video-preview-container" *ngIf="!videoUrl">
22
  <img src="assets/images/listen/listen3.png"
23
  alt="Upload a video"
24
  class="video-placeholder" />
25
  </div>
 
 
26
  <div class="video-preview-container" *ngIf="videoUrl">
27
  <video id="videoPreview"
28
  controls
29
  (ended)="onVideoEnded()"
30
  controlsList="nodownload"
31
  (error)="onVideoError()">
32
+ <source [src]="videoUrl"
33
+ [type]="getVideoMimeType(videoUrl)" />
34
  Your browser does not support the video tag.
35
  </video>
36
  </div>
 
 
37
  <input #uploadInput
38
  type="file"
39
  accept="video/mp4"
40
  (change)="onFileSelected($event)"
41
  class="video-upload-input" />
 
 
42
  <div class="button-group">
43
+ <app-button *ngIf="videoUrl"
44
+ (click)="toggleVideoPlayPause()">
 
45
  {{ isVideoPlaying ? 'Pause' : 'Play' }}
46
+ </app-button>
47
+ <app-button (click)="stopVideoIfPlaying(); uploadInput.click()"
48
+ [disabled]="isReplaceDisabled">
 
 
49
  {{ uploadSuccess ? 'Replace Video' : 'Upload Video' }}
50
+ </app-button>
51
+ <app-button *ngIf="videoUrl && isReplaceDisabled"
52
+ (click)="goToListen()">
 
 
53
  Reset
54
+ </app-button>
55
  </div>
56
  </div>
57
  </div>
 
 
 
 
58
  <div *ngIf="questions.length > 0" class="question-navigation">
 
59
  <div class="question-block">
 
60
  <div class="question-header-row">
61
+ <img src="assets/images/back.png"
62
+ alt="Go Back"
63
+ class="back-btn"
64
+ (click)="goBack()" />
65
+ <h2 class="question-heading">
66
+ Question {{ currentQuestionIndex + 1 }} of {{ questions.length }}
67
+ </h2>
68
+ <button class="user-guide-close-icon"
69
+ (click)="goToListen()"
70
+ aria-label="Close">
71
+ ×
72
+ </button>
73
  </div>
74
+ <p class="question-text">
75
+ {{ questions[currentQuestionIndex].question }}
76
+ </p>
 
77
  <div class="question-content-row">
 
78
  <div class="options-container">
79
  <button *ngFor="let option of questions[currentQuestionIndex].options; let i = index"
80
  (click)="checkAnswerTemp(i)"
81
  [ngClass]="{
82
+ 'selected-option': selectedOptionIndex === i && !validated,
83
+ 'correct': validated && option === questions[currentQuestionIndex].answer,
84
+ 'incorrect': validated && selectedOptionIndex === i && option !== questions[currentQuestionIndex].answer
85
+ }"
86
  [disabled]="validated">
87
  {{ option }}
88
  </button>
89
  </div>
 
 
90
  <div class="question-side-image">
91
  <img [src]="getFeedbackImage()" alt="Rabbit Feedback Image" />
92
  </div>
93
  </div>
 
 
 
94
  <div class="question-footer">
95
+ <app-button (click)="prevQuestion()"
96
+ [disabled]="currentQuestionIndex === 0">
 
 
97
  ◀ Previous
98
+ </app-button>
 
 
99
  <div class="right-buttons">
100
+ <app-button *ngIf="!validated"
101
+ (click)="validateAnswer(questions[currentQuestionIndex].answer, currentQuestionIndex)"
102
+ [disabled]="selectedOptionIndex === null">
 
 
103
  Submit
104
+ </app-button>
105
+ <app-button *ngIf="validated && currentQuestionIndex < questions.length - 1"
106
+ (click)="nextQuestion()">
 
 
 
107
  Next ▶
108
+ </app-button>
109
+ <app-button *ngIf="validated && currentQuestionIndex === questions.length - 1"
110
+ (click)="resetQuestions()">
 
 
 
111
  Regenerate Questions
112
+ </app-button>
113
  </div>
114
  </div>
115
  </div>
116
  </div>
 
 
 
117
  <div *ngIf="popupMessage" class="upload-popup">
118
  {{ popupMessage }}
119
  </div>
src/app/listen/listen.component.ts CHANGED
@@ -2,9 +2,14 @@ import { Component, OnInit } from '@angular/core';
2
  import { HttpClient } from '@angular/common/http';
3
  import { ListenService } from './listen.service';
4
  import { Router } from '@angular/router';
 
 
 
5
 
6
  @Component({
7
  selector: 'app-listen',
 
 
8
  templateUrl: './listen.component.html',
9
  styleUrls: ['./listen.component.css']
10
  })
@@ -27,10 +32,8 @@ export class ListenComponent implements OnInit {
27
  questionsDisplayed: boolean = false;
28
 
29
  isVideoPlaying: boolean = false;
30
-
31
  isReplaceDisabled: boolean = false;
32
 
33
-
34
  constructor(
35
  private http: HttpClient,
36
  private listenService: ListenService,
@@ -50,77 +53,40 @@ export class ListenComponent implements OnInit {
50
  }
51
  }
52
 
53
- //onFileSelected(event: any): void {
54
- // const file = event.target.files[0];
55
- // if (file && file.name.toLowerCase().endsWith('.mp4')) {
56
- // this.selectedFile = file;
57
- // const formData = new FormData();
58
- // formData.append('video', file);
59
-
60
- // this.listenService.uploadVideo(formData).subscribe({
61
- // next: (response) => {
62
- // this.videoUrl = `http://127.0.0.1:5000/static/videos/${response.filename}`;
63
- // this.uploadSuccess = true;
64
- // this.isGenerateEnabled = false;
65
- // // Show popup after a short delay
66
- // setTimeout(() => {
67
- // this.popupMessage = '✅ Video uploaded successfully!';
68
- // setTimeout(() => this.popupMessage = '', 1000); // Hide after 3s
69
- // }, 1000);
70
- // },
71
- // error: (err) => {
72
- // console.error('Upload failed', err);
73
- // alert('❌ Video upload failed.');
74
- // }
75
- // });
76
- // } else {
77
- // alert('Please upload a valid MP4 file.');
78
- // }
79
- //}
80
-
81
-
82
- onFileSelected(event: any): void {
83
- const file = event.target.files[0];
84
- if (file && file.name.toLowerCase().endsWith('.mp4')) {
85
- this.selectedFile = file;
86
- const formData = new FormData();
87
- formData.append('video', file);
88
-
89
- this.listenService.uploadVideo(formData).subscribe({
90
- next: (response) => {
91
- // Use backend /media/videos/<filename> path for preview (works both local & HF)
92
- const newVideoUrl = this.listenService.getVideoUrl(response.filename);
93
-
94
- this.videoUrl = ''; // Reset first to force re-binding
95
- setTimeout(() => {
96
- this.videoUrl = newVideoUrl;
97
-
98
- const videoElement = document.getElementById('videoPreview') as HTMLVideoElement;
99
- if (videoElement) {
100
- videoElement.load(); // Reload the video player
101
- }
102
-
103
- this.uploadSuccess = true;
104
- this.isGenerateEnabled = false;
105
- this.isVideoPlaying = false;
106
-
107
  setTimeout(() => {
108
- this.popupMessage = '✅ Video uploaded successfully!';
109
- setTimeout(() => this.popupMessage = '', 7000);
110
- }, 1000);
111
- });
112
- },
113
- error: (err) => {
114
- console.error('Upload failed', err);
115
- alert('❌ Video upload failed.');
116
- }
117
- });
118
- } else {
119
- alert('Please upload a valid MP4 file.');
 
 
 
 
 
 
 
 
 
 
120
  }
121
- }
122
-
123
-
124
 
125
  onVideoEnded(): void {
126
  this.isGenerateEnabled = true;
@@ -128,23 +94,18 @@ onFileSelected(event: any): void {
128
  }
129
 
130
  generateQuestions(): void {
131
-
132
- this.stopVideoIfPlaying(); // stop video first
133
-
134
  if (!this.videoUrl) {
135
- alert("Please upload a video first.");
136
  return;
137
  }
138
-
139
  const filename = this.videoUrl.split('/').pop()?.trim();
140
  if (!filename) {
141
- alert("Error: Unable to extract filename.");
142
- console.error("Filename extraction failed from videoUrl:", this.videoUrl);
143
  return;
144
  }
145
-
146
  this.isLoading = true;
147
-
148
  this.listenService.generateQuestions(filename).subscribe({
149
  next: (response) => {
150
  if (response.questions && response.questions.length > 0) {
@@ -158,7 +119,7 @@ onFileSelected(event: any): void {
158
  console.error('Error generating questions:', error);
159
  alert(error.error?.error || 'Failed to generate questions.');
160
  this.isLoading = false;
161
- },
162
  });
163
  }
164
 
@@ -171,18 +132,14 @@ onFileSelected(event: any): void {
171
  this.selectedOptionIndex = index;
172
  }
173
 
174
-
175
  validateAnswer(correctAnswer: string, index: number) {
176
  if (this.selectedOptionIndex === null) return;
177
-
178
  this.validated = true;
179
  const selectedOption = this.questions[index].options[this.selectedOptionIndex];
180
  this.selectedOptions[index] = selectedOption;
181
  this.selectedAnswers[index] = selectedOption === correctAnswer ? 'correct' : 'incorrect';
182
  }
183
 
184
-
185
-
186
  prevQuestion() {
187
  if (this.currentQuestionIndex > 0) {
188
  this.currentQuestionIndex--;
@@ -192,7 +149,6 @@ onFileSelected(event: any): void {
192
  }
193
  }
194
 
195
-
196
  nextQuestion() {
197
  if (this.currentQuestionIndex < this.questions.length - 1) {
198
  this.currentQuestionIndex++;
@@ -202,18 +158,6 @@ onFileSelected(event: any): void {
202
  }
203
  }
204
 
205
-
206
-
207
-
208
- //resetQuestions(): void {
209
- // this.questions = [];
210
- // this.selectedAnswers = [];
211
- // this.questionsDisplayed = false;
212
- // this.currentQuestionIndex = 0; // reset to first
213
- // this.generateQuestions();
214
- //}
215
-
216
-
217
  resetQuestions(): void {
218
  this.questions = [];
219
  this.selectedAnswers = [];
@@ -222,15 +166,12 @@ onFileSelected(event: any): void {
222
  this.selectedOptionIndex = null;
223
  this.currentQuestionIndex = 0;
224
  this.questionsDisplayed = false;
225
-
226
  const filename = this.videoUrl.split('/').pop()?.trim();
227
  if (!filename) {
228
- alert("Error: Unable to extract filename.");
229
  return;
230
  }
231
-
232
  this.isLoading = true;
233
-
234
  this.listenService.generateQuestions(filename).subscribe({
235
  next: (response) => {
236
  if (response.questions && response.questions.length > 0) {
@@ -244,11 +185,10 @@ onFileSelected(event: any): void {
244
  console.error('Error regenerating questions:', error);
245
  alert(error.error?.error || 'Failed to regenerate questions.');
246
  this.isLoading = false;
247
- },
248
  });
249
  }
250
 
251
-
252
  goBack(): void {
253
  this.questions = [];
254
  this.selectedAnswers = [];
@@ -259,10 +199,7 @@ onFileSelected(event: any): void {
259
  this.router.navigate(['/home']);
260
  }
261
 
262
-
263
-
264
  goToListen(): void {
265
- // Clear all state
266
  this.questions = [];
267
  this.selectedAnswers = [];
268
  this.selectedOptions = {};
@@ -270,22 +207,17 @@ onFileSelected(event: any): void {
270
  this.selectedOptionIndex = null;
271
  this.currentQuestionIndex = 0;
272
  this.questionsDisplayed = false;
273
-
274
- // Reset video to trigger image placeholder
275
  this.videoUrl = '';
276
  this.uploadSuccess = false;
277
  this.isGenerateEnabled = false;
278
  this.isVideoPlaying = false;
279
  this.isReplaceDisabled = false;
280
-
281
- // Clear the file input
282
  const uploadInput = document.querySelector('input[type="file"]') as HTMLInputElement;
283
  if (uploadInput) {
284
- uploadInput.value = ''; // Clears the selected file from memory
285
  }
286
  }
287
 
288
-
289
  getVideoMimeType(videoUrl: string): string {
290
  if (videoUrl.endsWith('.mp4')) return 'video/mp4';
291
  if (videoUrl.endsWith('.mov')) return 'video/quicktime';
@@ -307,41 +239,24 @@ onFileSelected(event: any): void {
307
 
308
  getFeedbackImage(): string {
309
  if (!this.validated) {
310
- return 'assets/images/listen/group.png'; // Default image
311
  }
312
-
313
  const result = this.selectedAnswers[this.currentQuestionIndex];
314
  if (result === 'correct') {
315
- return 'assets/images/listen/confetti.png'; // Happy image
316
  } else if (result === 'incorrect') {
317
- return 'assets/images/listen/sad.png'; // Sad image
318
  }
319
-
320
- return 'assets/images/listen/group.png'; // Fallback
321
  }
322
 
323
-
324
-
325
- //toggleVideoPlayPause(): void {
326
- // const videoElement = document.getElementById('videoPreview') as HTMLVideoElement;
327
- // if (videoElement) {
328
- // if (videoElement.paused) {
329
- // videoElement.play();
330
- // this.isVideoPlaying = true;
331
- // } else {
332
- // videoElement.pause();
333
- // this.isVideoPlaying = false;
334
- // }
335
- // }
336
- //}
337
-
338
  toggleVideoPlayPause(): void {
339
  const videoElement = document.getElementById('videoPreview') as HTMLVideoElement;
340
  if (videoElement) {
341
  if (videoElement.paused) {
342
  videoElement.play();
343
  this.isVideoPlaying = true;
344
- this.isReplaceDisabled = true; // disable replace permanently on first play
345
  } else {
346
  videoElement.pause();
347
  this.isVideoPlaying = false;
@@ -349,8 +264,6 @@ onFileSelected(event: any): void {
349
  }
350
  }
351
 
352
-
353
-
354
  stopVideoIfPlaying(): void {
355
  const videoElement = document.getElementById('videoPreview') as HTMLVideoElement;
356
  if (videoElement && !videoElement.paused) {
@@ -358,8 +271,4 @@ onFileSelected(event: any): void {
358
  this.isVideoPlaying = false;
359
  }
360
  }
361
-
362
-
363
-
364
-
365
  }
 
2
  import { HttpClient } from '@angular/common/http';
3
  import { ListenService } from './listen.service';
4
  import { Router } from '@angular/router';
5
+ import { ButtonComponent } from '../shared/button/button.component';
6
+ import { HeaderComponent } from '../shared/header/header.component';
7
+ import { CommonModule } from '@angular/common';
8
 
9
  @Component({
10
  selector: 'app-listen',
11
+ standalone: true,
12
+ imports: [ButtonComponent, HeaderComponent, CommonModule],
13
  templateUrl: './listen.component.html',
14
  styleUrls: ['./listen.component.css']
15
  })
 
32
  questionsDisplayed: boolean = false;
33
 
34
  isVideoPlaying: boolean = false;
 
35
  isReplaceDisabled: boolean = false;
36
 
 
37
  constructor(
38
  private http: HttpClient,
39
  private listenService: ListenService,
 
53
  }
54
  }
55
 
56
+ onFileSelected(event: any): void {
57
+ const file = event.target.files[0];
58
+ if (file && file.name.toLowerCase().endsWith('.mp4')) {
59
+ this.selectedFile = file;
60
+ const formData = new FormData();
61
+ formData.append('video', file);
62
+ this.listenService.uploadVideo(formData).subscribe({
63
+ next: (response) => {
64
+ const newVideoUrl = this.listenService.getVideoUrl(response.filename);
65
+ this.videoUrl = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  setTimeout(() => {
67
+ this.videoUrl = newVideoUrl;
68
+ const videoElement = document.getElementById('videoPreview') as HTMLVideoElement;
69
+ if (videoElement) {
70
+ videoElement.load();
71
+ }
72
+ this.uploadSuccess = true;
73
+ this.isGenerateEnabled = false;
74
+ this.isVideoPlaying = false;
75
+ setTimeout(() => {
76
+ this.popupMessage = '✅ Video uploaded successfully!';
77
+ setTimeout(() => (this.popupMessage = ''), 7000);
78
+ }, 1000);
79
+ });
80
+ },
81
+ error: (err) => {
82
+ console.error('Upload failed', err);
83
+ alert('❌ Video upload failed.');
84
+ }
85
+ });
86
+ } else {
87
+ alert('Please upload a valid MP4 file.');
88
+ }
89
  }
 
 
 
90
 
91
  onVideoEnded(): void {
92
  this.isGenerateEnabled = true;
 
94
  }
95
 
96
  generateQuestions(): void {
97
+ this.stopVideoIfPlaying();
 
 
98
  if (!this.videoUrl) {
99
+ alert('Please upload a video first.');
100
  return;
101
  }
 
102
  const filename = this.videoUrl.split('/').pop()?.trim();
103
  if (!filename) {
104
+ alert('Error: Unable to extract filename.');
105
+ console.error('Filename extraction failed from videoUrl:', this.videoUrl);
106
  return;
107
  }
 
108
  this.isLoading = true;
 
109
  this.listenService.generateQuestions(filename).subscribe({
110
  next: (response) => {
111
  if (response.questions && response.questions.length > 0) {
 
119
  console.error('Error generating questions:', error);
120
  alert(error.error?.error || 'Failed to generate questions.');
121
  this.isLoading = false;
122
+ }
123
  });
124
  }
125
 
 
132
  this.selectedOptionIndex = index;
133
  }
134
 
 
135
  validateAnswer(correctAnswer: string, index: number) {
136
  if (this.selectedOptionIndex === null) return;
 
137
  this.validated = true;
138
  const selectedOption = this.questions[index].options[this.selectedOptionIndex];
139
  this.selectedOptions[index] = selectedOption;
140
  this.selectedAnswers[index] = selectedOption === correctAnswer ? 'correct' : 'incorrect';
141
  }
142
 
 
 
143
  prevQuestion() {
144
  if (this.currentQuestionIndex > 0) {
145
  this.currentQuestionIndex--;
 
149
  }
150
  }
151
 
 
152
  nextQuestion() {
153
  if (this.currentQuestionIndex < this.questions.length - 1) {
154
  this.currentQuestionIndex++;
 
158
  }
159
  }
160
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  resetQuestions(): void {
162
  this.questions = [];
163
  this.selectedAnswers = [];
 
166
  this.selectedOptionIndex = null;
167
  this.currentQuestionIndex = 0;
168
  this.questionsDisplayed = false;
 
169
  const filename = this.videoUrl.split('/').pop()?.trim();
170
  if (!filename) {
171
+ alert('Error: Unable to extract filename.');
172
  return;
173
  }
 
174
  this.isLoading = true;
 
175
  this.listenService.generateQuestions(filename).subscribe({
176
  next: (response) => {
177
  if (response.questions && response.questions.length > 0) {
 
185
  console.error('Error regenerating questions:', error);
186
  alert(error.error?.error || 'Failed to regenerate questions.');
187
  this.isLoading = false;
188
+ }
189
  });
190
  }
191
 
 
192
  goBack(): void {
193
  this.questions = [];
194
  this.selectedAnswers = [];
 
199
  this.router.navigate(['/home']);
200
  }
201
 
 
 
202
  goToListen(): void {
 
203
  this.questions = [];
204
  this.selectedAnswers = [];
205
  this.selectedOptions = {};
 
207
  this.selectedOptionIndex = null;
208
  this.currentQuestionIndex = 0;
209
  this.questionsDisplayed = false;
 
 
210
  this.videoUrl = '';
211
  this.uploadSuccess = false;
212
  this.isGenerateEnabled = false;
213
  this.isVideoPlaying = false;
214
  this.isReplaceDisabled = false;
 
 
215
  const uploadInput = document.querySelector('input[type="file"]') as HTMLInputElement;
216
  if (uploadInput) {
217
+ uploadInput.value = '';
218
  }
219
  }
220
 
 
221
  getVideoMimeType(videoUrl: string): string {
222
  if (videoUrl.endsWith('.mp4')) return 'video/mp4';
223
  if (videoUrl.endsWith('.mov')) return 'video/quicktime';
 
239
 
240
  getFeedbackImage(): string {
241
  if (!this.validated) {
242
+ return 'assets/images/listen/group.png';
243
  }
 
244
  const result = this.selectedAnswers[this.currentQuestionIndex];
245
  if (result === 'correct') {
246
+ return 'assets/images/listen/confetti.png';
247
  } else if (result === 'incorrect') {
248
+ return 'assets/images/listen/sad.png';
249
  }
250
+ return 'assets/images/listen/group.png';
 
251
  }
252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  toggleVideoPlayPause(): void {
254
  const videoElement = document.getElementById('videoPreview') as HTMLVideoElement;
255
  if (videoElement) {
256
  if (videoElement.paused) {
257
  videoElement.play();
258
  this.isVideoPlaying = true;
259
+ this.isReplaceDisabled = true;
260
  } else {
261
  videoElement.pause();
262
  this.isVideoPlaying = false;
 
264
  }
265
  }
266
 
 
 
267
  stopVideoIfPlaying(): void {
268
  const videoElement = document.getElementById('videoPreview') as HTMLVideoElement;
269
  if (videoElement && !videoElement.paused) {
 
271
  this.isVideoPlaying = false;
272
  }
273
  }
 
 
 
 
274
  }
src/app/reading/reading.component.css CHANGED
@@ -1,43 +1,40 @@
1
- /* === Container, header, background, main === */
2
  .reading-container {
3
- font-family: 'Segoe UI',sans-serif;
4
  background-size: auto;
5
  background-position: center;
6
  background-attachment: fixed;
7
  width: 100%;
8
- height: 100%
9
  }
10
 
11
  .main-container {
12
  width: 85%;
13
  margin: 5vh auto;
14
- border: 9px solid #009688;
15
  border-radius: 1.5vw;
16
  background: #fff;
17
  padding: 2vw;
18
  box-shadow: 0 .4vw 1vw rgba(0,0,0,.1);
19
- height: 76vh
 
 
 
20
  }
21
 
22
-
23
-
24
- /* ===== Intro split (match reference, minimal side margins) ===== */
25
  .intro-section.split {
26
- padding-inline: 1vw; /* very small left/right padding */
27
  display: block;
28
  }
29
 
30
  .split-shell {
31
  width: 100%;
32
- max-width: none; /* use full inner width */
33
  margin: 0 auto;
34
  display: grid;
35
- grid-template-columns: minmax(320px, 38%) 1fr; /* image left, content right */
36
  align-items: center;
37
- /*gap: 2.2vw;*/
38
  }
39
 
40
- /* Left image card */
41
  .split-left {
42
  display: grid;
43
  place-items: center;
@@ -47,7 +44,6 @@
47
  width: 60%;
48
  }
49
 
50
- /* Right content */
51
  .hero-title {
52
  font-size: 2.5vw;
53
  font-weight: 800;
@@ -62,7 +58,6 @@
62
  margin-bottom: 3vw;
63
  }
64
 
65
- /* Rows like the reference: label + control(s) */
66
  .form-row {
67
  display: flex;
68
  align-items: center;
@@ -77,33 +72,40 @@
77
  font-size: 1.3vw;
78
  }
79
 
80
- /* Inputs and selects (reuse your existing styles) */
81
  .input-wrap, .select-wrap {
82
  position: relative;
83
  flex: 1;
84
  }
85
 
86
- .input-wrap input, .select-wrap select {
 
87
  width: 100%;
88
- padding: 12px 40px 12px 36px;
 
 
89
  border-radius: 12px;
90
  border: 1px solid #cfe2e0;
91
  background: #fff;
92
  color: #0e3e45;
93
  outline: 0;
94
  transition: border-color .2s, box-shadow .2s;
 
95
  }
96
 
97
- .input-wrap input::placeholder {
98
- color: #93a3b8;
99
- }
 
100
 
101
- .input-wrap input:focus, .select-wrap select:focus {
102
- border-color: #009688;
103
- box-shadow: 0 0 0 3px rgba(0,150,136,.12);
104
- }
 
 
 
 
105
 
106
- /* Icons */
107
  .icon-search, .icon-level {
108
  position: absolute;
109
  left: 12px;
@@ -125,7 +127,6 @@
125
  font-size: 16px;
126
  }
127
 
128
- /* Level badge */
129
  .badge {
130
  position: absolute;
131
  right: 10px;
@@ -157,35 +158,6 @@
157
  background: #fff3e9;
158
  }
159
 
160
- /* Generate button aligned like “Get Topic” */
161
- .btn-get {
162
- min-width: 150px;
163
- padding: 5px 16px;
164
- border-radius: 12px;
165
- border: 1px solid #009688;
166
- background: #006780;
167
- color: white;
168
- font-weight: 700;
169
- font-size: 1.5vw;
170
- cursor: pointer;
171
- transition: transform .08s, box-shadow .2s, opacity .2s;
172
- }
173
-
174
- .btn-get:disabled {
175
- /*opacity: .6;*/
176
- background-color: #bbbbbb;
177
- cursor: not-allowed;
178
- }
179
-
180
- .btn-get:not(:disabled):hover {
181
- box-shadow: 0 10px 24px rgba(0,150,136,.22);
182
- }
183
-
184
- .btn-get:not(:disabled):active {
185
- transform: translateY(1px);
186
- }
187
-
188
- /* Suggestions (keep your existing styles; scoped to intro) */
189
  .intro-section.split .suggestion-box {
190
  position: absolute;
191
  top: calc(100% + 8px);
@@ -214,7 +186,6 @@
214
  background: #f1fbfa;
215
  }
216
 
217
- /* Clear (×) inside input */
218
  .input-wrap.clearable {
219
  position: relative;
220
  }
@@ -224,35 +195,31 @@
224
  }
225
 
226
  .clear-btn {
227
- position: absolute;
228
  right: 12px;
229
- top: 55%;
230
- transform: translateY(-53%);
231
- width: 2vw;
232
- height: 2vw;
233
- line-height: 0vw;
234
- text-align: center;
235
- border: 1px solid #cfe2e0;
236
- border-radius: 45%;
237
- background: #006780;
238
- color: white;
239
- cursor: pointer;
240
- padding: 0;
241
  font-size: 2vw;
242
- z-index: 30;
 
 
 
 
243
  }
244
 
245
  .clear-btn:hover {
246
- background: #f1fbfa;
247
- color: #0e3e45;
248
- border-color: #b9d6d3;
249
- }
250
-
251
- .clear-btn:active {
252
- transform: translateY(-50%) scale(.98);
253
  }
254
 
255
- /* Responsive */
256
  @media (max-width: 980px) {
257
  .split-shell {
258
  grid-template-columns: 1fr;
@@ -264,12 +231,10 @@
264
  }
265
  }
266
 
267
-
268
  :root {
269
  --passage-font: 21px;
270
  }
271
 
272
- /* Header */
273
  .reading-head {
274
  display: grid;
275
  grid-template-columns: 44px 1fr auto;
@@ -292,63 +257,36 @@
292
  gap: 8px;
293
  }
294
 
295
- .icon-btn {
296
- width: 45px;
297
- height: 45px;
298
- display: grid;
299
- place-items: center;
300
- /* color: #eaf1ff; */
301
- background: rgb(32 171 107 / 42%);
302
- border: 4px solid rgba(255, 255, 255, .15);
303
- border-radius: 10px;
304
- cursor: pointer;
305
- transition: transform .05s ease, box-shadow .15s ease, background .15s ease;
306
- font-size: x-large;
307
- font-weight: 800;
308
- }
309
-
310
- .icon-btn:hover {
311
- box-shadow: 0 8px 18px rgba(0,0,0,.08);
312
  background: #f7fcfb;
313
- }
314
-
315
- .icon-btn:active {
316
- transform: translateY(1px);
317
- }
318
-
319
- .icon-btn.active {
320
- border-color: #95d0c9;
321
- background: #eefaf8;
322
- }
323
-
324
- .icon-btn.danger {
325
- color: #b1241a;
326
- }
327
-
328
- .icon-btn1 {
329
- width: 8vw;
330
- height: 5vh;
331
- display: grid;
332
- place-items: center;
333
- background: rgb(0 150 136);
334
- border: 2px solid rgba(255, 255, 255, .15);
335
- border-radius: 22px;
336
- cursor: pointer;
337
- transition: transform .05s ease, box-shadow .15s ease, background .15s ease;
338
- font-size: x-large;
339
- font-weight: 800;
340
- top: -4vw;
341
- position: relative;
342
- right: -4vw;
343
- }
344
 
345
- .icon-btn1:hover {
346
- box-shadow: 0 8px 18px rgba(0,0,0,.08);
347
- background: white;
348
- color: black;
349
- }
350
 
351
- /* Meta chips */
352
  .reading-meta {
353
  display: flex;
354
  flex-wrap: wrap;
@@ -387,7 +325,6 @@
387
  color: #b35a11;
388
  }
389
 
390
- /* Passage area */
391
  .passage-shell {
392
  position: relative;
393
  border: 1px solid #e7f1f0;
@@ -395,13 +332,12 @@
395
  background: #f3efef;
396
  padding: 14px 16px;
397
  box-shadow: inset 0 1px 0 rgba(255,255,255,.7);
398
- max-height: 46vh;
399
  overflow: auto;
400
  font-size: 1.4vw;
401
  line-height: 1.8;
402
  }
403
 
404
- /* Soft scrollbars */
405
  .passage-shell::-webkit-scrollbar {
406
  width: 12px;
407
  }
@@ -416,13 +352,11 @@
416
  background: #f7fcfb;
417
  }
418
 
419
- /* Typography (A−/A+) */
420
  .passage-text {
421
  font-family: Georgia, "Times New Roman", serif;
422
  font-size: var(--passage-font);
423
  line-height: 1.5;
424
  color: #1e3a3f;
425
- /*letter-spacing: .2px;*/
426
  text-align: left;
427
  hyphens: auto;
428
  white-space: pre-wrap;
@@ -447,7 +381,6 @@
447
  font-weight: 700;
448
  }
449
 
450
- /* Footer buttons */
451
  .reading-actions {
452
  display: flex;
453
  gap: 10px;
@@ -455,77 +388,26 @@
455
  margin-top: 30px;
456
  }
457
 
458
- .btn-primary, .btn-danger {
459
- min-width: 210px;
460
- padding: 12px 18px;
461
- border-radius: 12px;
462
- font-weight: 700;
463
- font-size: 1.3vw;
464
- cursor: pointer;
465
- border: 1px solid transparent;
466
- transition: transform .06s, box-shadow .18s, opacity .18s;
467
- }
468
-
469
- .btn-primary {
470
- color: #fff;
471
- background: #006780;
472
- border-color: #006780;
473
- }
474
-
475
- .btn-primary:hover {
476
- box-shadow: 0 12px 24px rgba(0,103,128,.25);
477
- }
478
-
479
- .btn-primary:disabled {
480
- opacity: .6;
481
- cursor: not-allowed;
482
- }
483
-
484
- .btn-danger {
485
- color: #fff;
486
- background: #006780;
487
- border-color: #006780;
488
- }
489
-
490
- .btn-danger:hover {
491
- box-shadow: 0 12px 24px rgba(0,103,128,.25);
492
- }
493
-
494
- /* Back button (left) reuses .icon-btn */
495
  .back {
496
  grid-column: 1 / 2;
497
  }
498
 
499
- /* Responsive */
500
  @media (max-width: 720px) {
501
  .reading-title {
502
  font-size: 22px;
503
  }
504
-
505
- .btn-primary, .btn-danger {
506
- min-width: 160px;
507
- }
508
  }
509
 
510
- /* Keep all MCQ UI inside the white card */
511
- .main-container {
512
- background: #fff;
513
- overflow: visible;
514
- }
515
- /* you already have this */
516
-
517
- /* ---- MCQ card ---- */
518
  .mcq-card {
519
  width: 100%;
520
- margin: 0; /* no side gaps */
521
- background: #fff; /* same white as container */
522
  border: 1px solid #d9ecea;
523
  border-radius: 16px;
524
  padding: 16px;
525
  box-shadow: 0 10px 24px rgba(0,0,0,.06);
526
  }
527
 
528
- /* Header inside the card */
529
  .mcq-card__header {
530
  display: grid;
531
  grid-template-columns: 44px 1fr 44px;
@@ -548,7 +430,6 @@
548
  justify-content: flex-end;
549
  }
550
 
551
- /* Question pill */
552
  .quiz-pill {
553
  display: flex;
554
  align-items: flex-start;
@@ -576,7 +457,6 @@
576
  line-height: 1.6;
577
  }
578
 
579
- /* Options list */
580
  .quiz-options {
581
  list-style: none;
582
  margin: 0;
@@ -592,7 +472,6 @@
592
  }
593
  }
594
 
595
- /* Option pill */
596
  .quiz-option-pill {
597
  display: flex;
598
  align-items: center;
@@ -620,7 +499,6 @@
620
  color: #0e3e45;
621
  }
622
 
623
- /* States */
624
  .quiz-option-pill.is-selected {
625
  border-color: #a8d8d3;
626
  background: #eefaf8;
@@ -636,7 +514,6 @@
636
  background: #fff1ef;
637
  }
638
 
639
- /* Hide the native radio */
640
  .visually-hidden {
641
  position: absolute !important;
642
  inset: auto auto auto auto !important;
@@ -647,7 +524,6 @@
647
  white-space: nowrap;
648
  }
649
 
650
- /* Answer status */
651
  .answer-status {
652
  display: flex;
653
  flex-wrap: wrap;
@@ -682,68 +558,17 @@
682
  color: #3a5b60;
683
  }
684
 
685
- /* Footer buttons */
686
  .mcq-card__footer {
687
  display: flex;
688
  gap: 10px;
689
  justify-content: end;
690
  margin-top: 6vw;
 
 
691
  }
692
 
693
- .submit-btn, .next-btn, .reset-btn {
694
- min-width: 160px;
695
- padding: 12px 16px;
696
- border-radius: 12px;
697
- font-weight: 700;
698
- cursor: pointer;
699
- /*border: 1px solid transparent;*/
700
- transition: transform .06s, box-shadow .18s, opacity .18s;
701
- font-size: 1.5vw;
702
- }
703
-
704
- .submit-btn {
705
- background: #006780;
706
- color: #fff;
707
- border-color: #006780;
708
- }
709
-
710
- .submit-btn:hover {
711
- box-shadow: 0 12px 24px rgba(0,103,128,.25);
712
- }
713
-
714
- .submit-btn:disabled {
715
- box-shadow: none;
716
- transform: none;
717
- background-color: #cccccc;
718
- color: #666666;
719
- cursor: not-allowed;
720
- }
721
-
722
- .next-btn {
723
- background: #006780;
724
- color: #fff;
725
- border-color: #006780;
726
- }
727
-
728
- .next-btn:hover {
729
- box-shadow: 0 12px 24px rgba(0,103,128,.25);
730
- }
731
-
732
- .reset-btn {
733
- background: #e5483b;
734
- color: #fff;
735
- border-color: #e5483b;
736
- }
737
-
738
- .reset-btn:hover {
739
- box-shadow: 0 12px 24px rgba(229,72,59,.25);
740
- }
741
-
742
-
743
-
744
- /* Loader Overlay */
745
  .loader-overlay {
746
- position: fixed;
747
  top: 0;
748
  left: 0;
749
  width: 100%;
@@ -799,8 +624,6 @@
799
  }
800
  }
801
 
802
-
803
-
804
  .popup-overlay {
805
  position: fixed;
806
  top: 0;
@@ -814,7 +637,6 @@
814
  z-index: 1000;
815
  }
816
 
817
- /* Card */
818
  .popup-content {
819
  background-color: #fff;
820
  padding: 2vw;
@@ -825,35 +647,12 @@
825
  width: 80%;
826
  }
827
 
828
-
829
-
830
  .popup-content p {
831
  font-size: 1.4vw;
832
  color: #dc3545;
833
  margin-bottom: 1vw;
834
  }
835
 
836
- /* Close button */
837
- .close-btn1 {
838
- padding: 0.8vw 1.5vw;
839
- font-size: 1.2vw;
840
- background-color: #007bff;
841
- color: #fff;
842
- border: none;
843
- border-radius: 0.5vw;
844
- cursor: pointer;
845
- transition: all 0.3s ease;
846
- }
847
-
848
- .close-btn1:hover {
849
- filter: brightness(0.98);
850
- }
851
-
852
- .close-btn1:active {
853
- transform: translateY(1px);
854
- }
855
-
856
- /* small scale-in animation */
857
  @keyframes popupScale {
858
  from {
859
  transform: scale(.96);
@@ -866,8 +665,6 @@
866
  }
867
  }
868
 
869
-
870
- /* Locked/disabled state for Topic input until a level is chosen */
871
  .input-wrap.locked input {
872
  background: #f7f9fb;
873
  border-color: #dfe9e7;
@@ -879,16 +676,14 @@
879
  opacity: .5;
880
  }
881
 
882
- /* optional: a subtle invisible scrim so nothing inside is clickable */
883
  .input-wrap.locked::after {
884
  content: "";
885
  position: absolute;
886
  inset: 0;
887
  border-radius: 12px;
888
- pointer-events: auto; /* block clicks */
889
  }
890
 
891
- /* Helper note under the field */
892
  .field-hint {
893
  margin: 6px 2px 0;
894
  font-size: 12px;
@@ -904,93 +699,25 @@
904
  line-height: 1;
905
  }
906
 
907
- /* Keep space for the clear (×) button when enabled */
908
  .input-wrap .has-clear {
909
  padding-right: 44px;
910
  }
911
 
912
 
913
- /* === Larger Back Arrow Button === */
914
- .icon-btn.back {
915
- width: 3vw; /* bigger button */
916
- height: 3vw;
917
- border-radius: 14px;
918
- border: 1px solid #cfe2e0;
919
- background: #fff;
920
- display: grid;
921
- place-items: center;
922
- cursor: pointer;
923
- box-shadow: 0 2px 8px rgba(0,0,0,.06);
924
- transition: box-shadow .15s, transform .05s, border-color .15s, background .15s;
925
- }
926
-
927
- .icon-btn.back:hover {
928
- background: #f7fcfb;
929
- border-color: #b9d6d3;
930
- box-shadow: 0 8px 18px rgba(0,0,0,.08);
931
- }
932
-
933
- .icon-btn.back:active {
934
- transform: translateY(1px);
935
- }
936
-
937
- /* visible keyboard focus */
938
- .icon-btn.back:focus-visible {
939
- outline: 3px solid rgba(0,150,136,.35);
940
- outline-offset: 2px;
941
- }
942
-
943
- /* arrow image inside the button */
944
- .icon-btn.back .icon-img {
945
- width: 3vw; /* bigger arrow */
946
- height: 3vw;
947
- object-fit: contain;
948
- display: block;
949
- transition: transform .12s, opacity .12s;
950
- }
951
-
952
- /* subtle nudge on hover */
953
- .icon-btn.back:hover .icon-img {
954
- transform: translateX(-1px);
955
- }
956
-
957
- /* mobile: slightly smaller to fit tight headers */
958
- @media (max-width: 720px) {
959
- .icon-btn.back {
960
- width: 44px;
961
- height: 44px;
962
- border-radius: 12px;
963
- }
964
-
965
- .icon-btn.back .icon-img {
966
- width: 22px;
967
- height: 22px;
968
- }
969
- }
970
-
971
- /* optional: if the header has a teal background */
972
- .header-on-teal .icon-btn.back {
973
- background: #007e74;
974
- border-color: #00756c;
975
  }
976
 
977
- .header-on-teal .icon-btn.back .icon-img {
978
- filter: brightness(0) invert(1); /* make arrow white */
979
- }
980
-
981
 
982
  .passage-text .highlight {
983
  background-color: yellow;
984
  }
985
 
986
-
987
- /* ===== Congratulations modal ===== */
988
  .congrats-overlay {
989
  position: fixed;
990
  inset: 0;
991
  display: grid;
992
  place-items: center;
993
- /*background: rgba(0, 0, 0, 0.45);*/
994
  }
995
 
996
  .congrats-card {
@@ -1009,7 +736,7 @@
1009
  position: relative;
1010
  z-index: 1;
1011
  box-shadow: 0 .4vw 1vw rgba(0, 0, 0, .1);
1012
- border: 9px solid #009688;
1013
  top: 3vw;
1014
  }
1015
 
@@ -1029,7 +756,6 @@
1029
  .congrats-card p {
1030
  margin: 0 0 16px;
1031
  color: #0e3e45;
1032
- /* color: #4ca1af; */
1033
  margin-bottom: 15px;
1034
  font-size: 2.5vw;
1035
  white-space: nowrap;
@@ -1038,7 +764,6 @@
1038
  text-align: center;
1039
  }
1040
 
1041
- /* Score badge */
1042
  .score-badge {
1043
  display: inline-flex;
1044
  align-items: baseline;
@@ -1049,7 +774,6 @@
1049
  background: #eef7f6;
1050
  color: #0e3e45;
1051
  font-weight: 800;
1052
- /* color: #4ca1af; */
1053
  margin-bottom: 15px;
1054
  font-size: 2.5vw;
1055
  white-space: nowrap;
@@ -1058,21 +782,7 @@
1058
  text-align: center;
1059
  }
1060
 
1061
-
1062
-
1063
- .start-over-btn {
1064
- min-width: 180px;
1065
- padding: 12px 16px;
1066
- border-radius: 12px;
1067
- background: #006780;
1068
- color: #fff;
1069
- border: 1px solid #006780;
1070
- font-weight: 800;
1071
- cursor: pointer;
1072
- transition: transform .06s, box-shadow .18s;
1073
- font-size: 2vw;
1074
  }
1075
-
1076
- .start-over-btn:hover {
1077
- box-shadow: 0 12px 24px rgba(0,103,128,.25);
1078
- }
 
 
1
  .reading-container {
2
+ font-family: 'Segoe UI', sans-serif;
3
  background-size: auto;
4
  background-position: center;
5
  background-attachment: fixed;
6
  width: 100%;
7
+ height: 100%;
8
  }
9
 
10
  .main-container {
11
  width: 85%;
12
  margin: 5vh auto;
13
+ border: 9px solid var(--main-accent-color);
14
  border-radius: 1.5vw;
15
  background: #fff;
16
  padding: 2vw;
17
  box-shadow: 0 .4vw 1vw rgba(0,0,0,.1);
18
+ height: 76vh;
19
+ position: relative;
20
+ background: #fff;
21
+ overflow: visible;
22
  }
23
 
 
 
 
24
  .intro-section.split {
25
+ padding-inline: 1vw;
26
  display: block;
27
  }
28
 
29
  .split-shell {
30
  width: 100%;
31
+ max-width: none;
32
  margin: 0 auto;
33
  display: grid;
34
+ grid-template-columns: minmax(320px, 38%) 1fr;
35
  align-items: center;
 
36
  }
37
 
 
38
  .split-left {
39
  display: grid;
40
  place-items: center;
 
44
  width: 60%;
45
  }
46
 
 
47
  .hero-title {
48
  font-size: 2.5vw;
49
  font-weight: 800;
 
58
  margin-bottom: 3vw;
59
  }
60
 
 
61
  .form-row {
62
  display: flex;
63
  align-items: center;
 
72
  font-size: 1.3vw;
73
  }
74
 
 
75
  .input-wrap, .select-wrap {
76
  position: relative;
77
  flex: 1;
78
  }
79
 
80
+ .input-wrap input,
81
+ .select-wrap select {
82
  width: 100%;
83
+ min-width: 220px;
84
+ font-size: 1.1rem;
85
+ padding: 14px 44px 14px 40px;
86
  border-radius: 12px;
87
  border: 1px solid #cfe2e0;
88
  background: #fff;
89
  color: #0e3e45;
90
  outline: 0;
91
  transition: border-color .2s, box-shadow .2s;
92
+ box-sizing: border-box;
93
  }
94
 
95
+ .select-wrap select,
96
+ .select-wrap option {
97
+ font-size: 1.1rem;
98
+ }
99
 
100
+ .input-wrap input::placeholder {
101
+ color: #93a3b8;
102
+ }
103
+
104
+ .input-wrap input:focus, .select-wrap select:focus {
105
+ border-color: #009688;
106
+ box-shadow: 0 0 0 3px rgba(0,150,136,.12);
107
+ }
108
 
 
109
  .icon-search, .icon-level {
110
  position: absolute;
111
  left: 12px;
 
127
  font-size: 16px;
128
  }
129
 
 
130
  .badge {
131
  position: absolute;
132
  right: 10px;
 
158
  background: #fff3e9;
159
  }
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  .intro-section.split .suggestion-box {
162
  position: absolute;
163
  top: calc(100% + 8px);
 
186
  background: #f1fbfa;
187
  }
188
 
 
189
  .input-wrap.clearable {
190
  position: relative;
191
  }
 
195
  }
196
 
197
  .clear-btn {
 
198
  right: 12px;
199
+ top: 4px;
200
+ position: absolute;
201
+ background: #009688;
202
+ border: none;
203
+ width: 44px;
204
+ height: 44px;
205
+ border-radius: 50%;
206
+ display: flex;
207
+ align-items: center;
208
+ justify-content: center;
 
 
209
  font-size: 2vw;
210
+ color: black;
211
+ cursor: pointer;
212
+ z-index: 2010;
213
+ box-shadow: 0 2px 8px rgba(93, 145, 195, 0.18);
214
+ transition: background 0.2s, color 0.2s;
215
  }
216
 
217
  .clear-btn:hover {
218
+ background: white;
219
+ color: black;
220
+ border: 3px solid #009688;
 
 
 
 
221
  }
222
 
 
223
  @media (max-width: 980px) {
224
  .split-shell {
225
  grid-template-columns: 1fr;
 
231
  }
232
  }
233
 
 
234
  :root {
235
  --passage-font: 21px;
236
  }
237
 
 
238
  .reading-head {
239
  display: grid;
240
  grid-template-columns: 44px 1fr auto;
 
257
  gap: 8px;
258
  }
259
 
260
+ .head-actions .icon-btn {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  background: #f7fcfb;
262
+ border: 1px solid #cfe2e0;
263
+ border-radius: 8px;
264
+ padding: 0.4em 1.1em;
265
+ font-size: 1.2vw;
266
+ color: #009688;
267
+ font-weight: 700;
268
+ cursor: pointer;
269
+ transition: background 0.15s, border-color 0.15s, color 0.15s;
270
+ outline: none;
271
+ min-width: 2.5em;
272
+ min-height: 2.5em;
273
+ display: flex;
274
+ align-items: center;
275
+ justify-content: center;
276
+ }
277
+
278
+ .head-actions .icon-btn:hover,
279
+ .head-actions .icon-btn.active {
280
+ background: #e0f7f4;
281
+ border-color: #009688;
282
+ color: #00695c;
283
+ }
 
 
 
 
 
 
 
 
 
284
 
285
+ .head-actions .icon-btn[aria-pressed="true"] {
286
+ background: #b2dfdb;
287
+ color: #004d40;
288
+ }
 
289
 
 
290
  .reading-meta {
291
  display: flex;
292
  flex-wrap: wrap;
 
325
  color: #b35a11;
326
  }
327
 
 
328
  .passage-shell {
329
  position: relative;
330
  border: 1px solid #e7f1f0;
 
332
  background: #f3efef;
333
  padding: 14px 16px;
334
  box-shadow: inset 0 1px 0 rgba(255,255,255,.7);
335
+ max-height: 43vh;
336
  overflow: auto;
337
  font-size: 1.4vw;
338
  line-height: 1.8;
339
  }
340
 
 
341
  .passage-shell::-webkit-scrollbar {
342
  width: 12px;
343
  }
 
352
  background: #f7fcfb;
353
  }
354
 
 
355
  .passage-text {
356
  font-family: Georgia, "Times New Roman", serif;
357
  font-size: var(--passage-font);
358
  line-height: 1.5;
359
  color: #1e3a3f;
 
360
  text-align: left;
361
  hyphens: auto;
362
  white-space: pre-wrap;
 
381
  font-weight: 700;
382
  }
383
 
 
384
  .reading-actions {
385
  display: flex;
386
  gap: 10px;
 
388
  margin-top: 30px;
389
  }
390
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  .back {
392
  grid-column: 1 / 2;
393
  }
394
 
 
395
  @media (max-width: 720px) {
396
  .reading-title {
397
  font-size: 22px;
398
  }
 
 
 
 
399
  }
400
 
 
 
 
 
 
 
 
 
401
  .mcq-card {
402
  width: 100%;
403
+ margin: 0;
404
+ background: #fff;
405
  border: 1px solid #d9ecea;
406
  border-radius: 16px;
407
  padding: 16px;
408
  box-shadow: 0 10px 24px rgba(0,0,0,.06);
409
  }
410
 
 
411
  .mcq-card__header {
412
  display: grid;
413
  grid-template-columns: 44px 1fr 44px;
 
430
  justify-content: flex-end;
431
  }
432
 
 
433
  .quiz-pill {
434
  display: flex;
435
  align-items: flex-start;
 
457
  line-height: 1.6;
458
  }
459
 
 
460
  .quiz-options {
461
  list-style: none;
462
  margin: 0;
 
472
  }
473
  }
474
 
 
475
  .quiz-option-pill {
476
  display: flex;
477
  align-items: center;
 
499
  color: #0e3e45;
500
  }
501
 
 
502
  .quiz-option-pill.is-selected {
503
  border-color: #a8d8d3;
504
  background: #eefaf8;
 
514
  background: #fff1ef;
515
  }
516
 
 
517
  .visually-hidden {
518
  position: absolute !important;
519
  inset: auto auto auto auto !important;
 
524
  white-space: nowrap;
525
  }
526
 
 
527
  .answer-status {
528
  display: flex;
529
  flex-wrap: wrap;
 
558
  color: #3a5b60;
559
  }
560
 
 
561
  .mcq-card__footer {
562
  display: flex;
563
  gap: 10px;
564
  justify-content: end;
565
  margin-top: 6vw;
566
+ align-items: flex-end;
567
+ justify-content: space-between;
568
  }
569
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
  .loader-overlay {
571
+ position: absolute;
572
  top: 0;
573
  left: 0;
574
  width: 100%;
 
624
  }
625
  }
626
 
 
 
627
  .popup-overlay {
628
  position: fixed;
629
  top: 0;
 
637
  z-index: 1000;
638
  }
639
 
 
640
  .popup-content {
641
  background-color: #fff;
642
  padding: 2vw;
 
647
  width: 80%;
648
  }
649
 
 
 
650
  .popup-content p {
651
  font-size: 1.4vw;
652
  color: #dc3545;
653
  margin-bottom: 1vw;
654
  }
655
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
  @keyframes popupScale {
657
  from {
658
  transform: scale(.96);
 
665
  }
666
  }
667
 
 
 
668
  .input-wrap.locked input {
669
  background: #f7f9fb;
670
  border-color: #dfe9e7;
 
676
  opacity: .5;
677
  }
678
 
 
679
  .input-wrap.locked::after {
680
  content: "";
681
  position: absolute;
682
  inset: 0;
683
  border-radius: 12px;
684
+ pointer-events: auto;
685
  }
686
 
 
687
  .field-hint {
688
  margin: 6px 2px 0;
689
  font-size: 12px;
 
699
  line-height: 1;
700
  }
701
 
 
702
  .input-wrap .has-clear {
703
  padding-right: 44px;
704
  }
705
 
706
 
707
+ .icon-img {
708
+ width: 4.5vw;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
709
  }
710
 
 
 
 
 
711
 
712
  .passage-text .highlight {
713
  background-color: yellow;
714
  }
715
 
 
 
716
  .congrats-overlay {
717
  position: fixed;
718
  inset: 0;
719
  display: grid;
720
  place-items: center;
 
721
  }
722
 
723
  .congrats-card {
 
736
  position: relative;
737
  z-index: 1;
738
  box-shadow: 0 .4vw 1vw rgba(0, 0, 0, .1);
739
+ border: 9px solid var(--main-accent-color);
740
  top: 3vw;
741
  }
742
 
 
756
  .congrats-card p {
757
  margin: 0 0 16px;
758
  color: #0e3e45;
 
759
  margin-bottom: 15px;
760
  font-size: 2.5vw;
761
  white-space: nowrap;
 
764
  text-align: center;
765
  }
766
 
 
767
  .score-badge {
768
  display: inline-flex;
769
  align-items: baseline;
 
774
  background: #eef7f6;
775
  color: #0e3e45;
776
  font-weight: 800;
 
777
  margin-bottom: 15px;
778
  font-size: 2.5vw;
779
  white-space: nowrap;
 
782
  text-align: center;
783
  }
784
 
785
+ .user-guide-close-icon {
786
+ right: -21px;
787
+ top: -21px;
 
 
 
 
 
 
 
 
 
 
788
  }
 
 
 
 
src/app/reading/reading.component.html CHANGED
@@ -1,89 +1,39 @@
1
  <div class="reading-container">
2
  <app-header [title]="'Reading'"></app-header>
3
-
4
-
5
- <!-- Background -->
6
  <img src="assets/images/grammar-bg.png" alt="Background" class="grammar-bg" />
7
 
8
- <!-- Main -->
9
  <div class="main-container" *ngIf="!showCongrats">
10
- <!-- INTRO (split left image / right form) -->
11
  <section class="intro-section split" *ngIf="!content && !hasStarted">
12
  <div class="split-shell">
13
- <!-- Left -->
14
  <div class="split-left">
15
- <img class="intro-illustration"
16
- src="assets/images/reading/teacher.png"
17
- alt="Reading and quiz illustration" />
18
  </div>
19
-
20
- <!-- Right -->
21
  <div class="split-right">
22
  <h1 class="hero-title">Welcome to the Reading Exercise</h1>
23
  <p class="hero-copy">
24
- The Reading component is a simple tool that turns any meaningful topic into a short,
25
- age-appropriate passage. It checks the topic first to avoid nonsense and unsafe inputs,
26
- then creates clear content at the chosen level (Easy, Medium, or Hard). After reading,
27
- it generates multiple-choice questions and gives instant feedback. Read-Aloud and A−/A+
28
- controls support different learning needs. This helps students build comprehension and
29
- vocabulary, and saves teachers and parents time during practice, homework, and revision.
30
  </p>
31
-
32
- <!-- Row: Topic -->
33
  <div class="form-row">
34
  <label class="row-label">Topic:</label>
35
-
36
  <div class="input-wrap clearable" [class.locked]="!difficulty">
37
  <span class="icon-search" aria-hidden="true"></span>
38
-
39
- <input type="text"
40
- class="has-clear"
41
- [(ngModel)]="topic"
42
- [placeholder]="difficulty ? 'Enter or select a topic' : 'Select a level first'"
43
- [disabled]="!difficulty"
44
- (focus)="openSuggestions()"
45
- (input)="onTyping()"
46
- (keydown)="onKeydown($event)"
47
- (blur)="hideSuggestionsWithDelay()"
48
- autocomplete="off"
49
- aria-autocomplete="list"
50
- [attr.aria-expanded]="showSuggestions && !!difficulty"
51
- [attr.aria-disabled]="!difficulty" />
52
-
53
- <!-- Clear (×) only when enabled and has text -->
54
- <button class="clear-btn"
55
- *ngIf="difficulty && topic.trim().length"
56
- (mousedown)="$event.preventDefault()"
57
- (click)="onClearTopic()">
58
- ×
59
- </button>
60
-
61
- <!-- Suggestions only after a level is chosen -->
62
  <div class="suggestion-box" *ngIf="difficulty && showSuggestions">
63
  <ng-container *ngIf="filteredSuggestions?.length">
64
- <span *ngFor="let s of filteredSuggestions; let i = index"
65
- (mousedown)="selectSuggestion(s)"
66
- [class.active]="i === activeIndex">{{ s }}</span>
67
  </ng-container>
68
- <!--
69
- <ng-template #noMatches>
70
- <span (mousedown)="selectSuggestion(topic)">Use “{{ topic }}”</span>
71
- </ng-template>
72
- -->
73
  </div>
74
  </div>
75
-
76
- <!-- Tiny helper note shown only when level not chosen -->
77
  <div class="field-hint" *ngIf="!difficulty">
78
- <span class="lock-icon" aria-hidden="true"></span>
79
- Please select a level to enable topic suggestions.
80
  </div>
81
  </div>
82
-
83
- <!-- Row: Level + Generate -->
84
  <div class="form-row">
85
  <label class="row-label">Select Level:</label>
86
-
87
  <div class="select-wrap">
88
  <span class="icon-level" aria-hidden="true"></span>
89
  <select [(ngModel)]="difficulty" (ngModelChange)="onDifficultyChange($event)" required>
@@ -91,189 +41,96 @@
91
  <option [ngValue]="'medium'">Medium</option>
92
  <option [ngValue]="'hard'">Hard</option>
93
  </select>
94
- <span class="badge" *ngIf="difficulty" [attr.data-level]="difficulty">
95
- {{ difficulty | titlecase }}
96
- </span>
97
  </div>
98
-
99
- <button class="btn-get"
100
- (click)="generateContent()
101
- "
102
- [disabled]="!topic.trim() || !difficulty.trim() || isGenerateDisabled">
103
- Generate Passage
104
- </button>
105
  </div>
106
  </div>
107
  </div>
108
-
109
- <!-- Centered loader (uses your .loader-overlay/.loader CSS) -->
110
  <div class="loader-overlay" *ngIf="isGeneratingContent" role="status" aria-live="polite">
111
  <div class="loader">Loading</div>
112
  </div>
113
-
114
- <!-- Error popup (uses existing CSS .popup-overlay/.popup-content) -->
115
  <div class="popup-overlay" *ngIf="showPopup">
116
  <div class="popup-content">
117
-
118
- <p>
119
- {{ errorMessage || 'We could not create the passage right now. Please try again.' }}
120
- </p>
121
- <button class="close-btn1" (click)="closeErrorPopup()">Close</button>
122
  </div>
123
  </div>
124
  </section>
125
 
126
- <!-- READING CARD -->
127
  <div *ngIf="content && !hasStarted" class="reading-card">
128
- <!-- Header -->
129
  <div class="reading-head">
130
- <button class="icon-btn back" (click)="goToIntroSection()" aria-label="Back">
131
- <img src="assets/images/reading/back.png" alt="" class="icon-img" />
132
- </button>
133
-
134
  <h2 class="reading-title">Let’s Start Reading!</h2>
135
-
136
- <!-- Actions: A− A+ Read/Pause Stop -->
137
  <div class="head-actions">
138
  <button class="icon-btn" (click)="decreaseFont()" aria-label="Decrease font">A−</button>
139
  <button class="icon-btn" (click)="increaseFont()" aria-label="Increase font">A+</button>
140
-
141
- <button class="icon-btn"
142
- [class.active]="isReading || ttsPaused"
143
- (click)="toggleReadAloud()"
144
- [attr.aria-pressed]="isReading || ttsPaused"
145
- aria-label="Read aloud">
146
- {{ isReading ? '⏸' : '🔊' }}
147
- </button>
148
-
149
- <!--
150
- <button class="icon-btn danger" (click)="stopReadAloud()" aria-label="Stop reading">■</button>
151
- -->
152
  </div>
153
  </div>
154
-
155
- <!-- Meta -->
156
  <div class="reading-meta">
157
  <span class="chip chip-topic" *ngIf="normalizedTopic || topic">📚 {{ normalizedTopic || topic }}</span>
158
- <span class="chip chip-level" [attr.data-level]="difficulty">
159
- {{
160
- difficulty | titlecase
161
- }}
162
- </span>
163
  </div>
164
-
165
- <!-- Passage -->
166
  <div class="passage-shell">
167
  <div class="passage-text" [innerHTML]="transformContent(content)"></div>
168
  </div>
169
-
170
- <!-- Footer -->
171
  <div class="reading-actions">
172
- <button class="btn-primary"
173
- (click)="stopReadAloud(); generateQuestions()"
174
- [disabled]="isGenerateQuestionDisabled">
175
- Generate Questions
176
- </button>
177
-
178
- <!-- Centered loader while generating questions -->
179
  <div class="loader-overlay" *ngIf="loadingQuestions" aria-live="polite" aria-busy="true">
180
  <span class="loader">Loading</span>
181
  </div>
182
-
183
- <button class="btn-danger" (click)="stopReadAloud(); resetAll()">Reset</button>
184
  </div>
185
  </div>
186
 
187
- <!-- QUESTIONS -->
188
- <!-- MCQ CARD -->
189
  <div class="mcq-card" *ngIf="hasStarted && questions?.length">
190
- <!-- Header inside the card -->
191
  <div class="mcq-card__header">
192
- <button class="icon-btn back" (click)="goBack()" aria-label="Back">
193
- <img src="assets/images/reading/back.png" alt="" class="icon-img" />
194
- </button>
195
 
196
- <h3 class="mcq-card__title">
197
- Question {{ currentQuestionIndex + 1 }} of {{ questions.length }}
198
- </h3>
199
 
 
200
  <div class="mcq-card__actions">
201
- <button class="icon-btn1" (click)="startOver()" aria-label="Close">✖</button>
202
  </div>
203
  </div>
204
-
205
- <!-- Body -->
206
  <div class="mcq-card__body">
207
- <!-- Question pill -->
208
  <div class="quiz-pill quiz-question-pill">
209
  <span class="qq-label">Question:</span>
210
  <span class="qq-text">{{ questions[currentQuestionIndex].question }}</span>
211
  </div>
212
-
213
- <!-- Options -->
214
  <ul class="quiz-options">
215
  <li *ngFor="let option of (questions[currentQuestionIndex]?.options | slice:0:4); let i = index">
216
- <label class="quiz-pill quiz-option-pill"
217
- [ngClass]="{
218
  'is-selected': !questions[currentQuestionIndex].isChecked && getSelectedAnswer() === option,
219
  'is-correct': questions[currentQuestionIndex].isChecked && questions[currentQuestionIndex].correct_answer === option,
220
  'is-incorrect':questions[currentQuestionIndex].isChecked && getSelectedAnswer() === option && option !== questions[currentQuestionIndex].correct_answer
221
  }">
222
- <input type="radio"
223
- class="visually-hidden"
224
- [name]="'q_'+currentQuestionIndex"
225
- [value]="option"
226
- [checked]="getSelectedAnswer() === option"
227
- (change)="setSelectedAnswer(option)"
228
- [disabled]="questions[currentQuestionIndex].isChecked" />
229
  <span class="slot">{{ ['A','B','C','D'][i] }}:</span>
230
  <span class="opt-text">{{ option }}</span>
231
  </label>
232
  </li>
233
  </ul>
234
  </div>
235
-
236
- <!-- Footer inside the card -->
237
  <div class="mcq-card__footer">
238
- <!-- Submit (visible until answered) -->
239
-
240
- <button *ngIf="!questions[currentQuestionIndex].isChecked"
241
- class="submit-btn"
242
- (click)="validateAnswer(); scheduleCongratsIfLast()"
243
- [disabled]="!selectedAnswers[questions[currentQuestionIndex].question]">
244
- Validate
245
- </button>
246
-
247
-
248
- <!-- Next (for Q1 & Q2 after submit) -->
249
- <button *ngIf="questions[currentQuestionIndex].isChecked && currentQuestionIndex < questions.length - 1"
250
- class="next-btn"
251
- (click)="nextQuestion()">
252
- Next ▶
253
- </button>
254
-
255
-
256
  </div>
257
  </div>
258
-
259
-
260
-
261
  </div>
262
 
263
-
264
-
265
-
266
- <!-- Congratulations overlay -->
267
  <div class="congrats-overlay" *ngIf="showCongrats" aria-live="polite" aria-modal="true" role="dialog">
268
  <div class="congrats-card">
269
  <div class="score-badge" aria-label="Your score">
270
  Your Score: <span class="score">{{ scoreCorrect }}</span> / <span class="total">{{ scoreTotal }}</span>
271
  </div>
272
- <h2>{{ congratsTitle }}</h2> <!-- Display the dynamic title -->
273
- <p>{{ congratsMessage }}</p> <!-- Display the dynamic message -->
274
-
275
- <button class="start-over-btn" (click)="startOver()">Start Over</button>
276
  </div>
277
  </div>
278
-
279
  </div>
 
1
  <div class="reading-container">
2
  <app-header [title]="'Reading'"></app-header>
 
 
 
3
  <img src="assets/images/grammar-bg.png" alt="Background" class="grammar-bg" />
4
 
 
5
  <div class="main-container" *ngIf="!showCongrats">
 
6
  <section class="intro-section split" *ngIf="!content && !hasStarted">
7
  <div class="split-shell">
 
8
  <div class="split-left">
9
+ <img class="intro-illustration" src="assets/images/reading/teacher.png" alt="Reading and quiz illustration" />
 
 
10
  </div>
 
 
11
  <div class="split-right">
12
  <h1 class="hero-title">Welcome to the Reading Exercise</h1>
13
  <p class="hero-copy">
14
+ The Reading component is a simple tool that turns any meaningful topic into a short, age-appropriate passage. It checks the topic first to avoid nonsense and unsafe inputs, then creates clear content at the chosen level (Easy, Medium, or Hard). After
15
+ reading, it generates multiple-choice questions and gives instant feedback. Read-Aloud and A−/A+ controls support different learning needs. This helps students build comprehension and vocabulary, and saves teachers and parents
16
+ time during practice, homework, and revision.
 
 
 
17
  </p>
 
 
18
  <div class="form-row">
19
  <label class="row-label">Topic:</label>
 
20
  <div class="input-wrap clearable" [class.locked]="!difficulty">
21
  <span class="icon-search" aria-hidden="true"></span>
22
+ <input type="text" class="has-clear" [(ngModel)]="topic" [placeholder]="difficulty ? 'Enter or select a topic' : 'Select a level first'" [disabled]="!difficulty" (focus)="openSuggestions()" (input)="onTyping()" (keydown)="onKeydown($event)" (blur)="hideSuggestionsWithDelay()"
23
+ autocomplete="off" aria-autocomplete="list" [attr.aria-expanded]="showSuggestions && !!difficulty" [attr.aria-disabled]="!difficulty" />
24
+ <button class="clear-btn" *ngIf="difficulty && topic.trim().length" (mousedown)="$event.preventDefault()" (click)="onClearTopic()">×</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  <div class="suggestion-box" *ngIf="difficulty && showSuggestions">
26
  <ng-container *ngIf="filteredSuggestions?.length">
27
+ <span *ngFor="let s of filteredSuggestions; let i = index" (mousedown)="selectSuggestion(s)" [class.active]="i === activeIndex">{{ s }}</span>
 
 
28
  </ng-container>
 
 
 
 
 
29
  </div>
30
  </div>
 
 
31
  <div class="field-hint" *ngIf="!difficulty">
32
+ <span class="lock-icon" aria-hidden="true"></span> Please select a level to enable topic suggestions.
 
33
  </div>
34
  </div>
 
 
35
  <div class="form-row">
36
  <label class="row-label">Select Level:</label>
 
37
  <div class="select-wrap">
38
  <span class="icon-level" aria-hidden="true"></span>
39
  <select [(ngModel)]="difficulty" (ngModelChange)="onDifficultyChange($event)" required>
 
41
  <option [ngValue]="'medium'">Medium</option>
42
  <option [ngValue]="'hard'">Hard</option>
43
  </select>
44
+ <span class="badge" *ngIf="difficulty" [attr.data-level]="difficulty">{{ difficulty | titlecase }}</span>
 
 
45
  </div>
46
+ <app-button (click)="generateContent()" [disabled]="!topic.trim() || !difficulty.trim() || isGenerateDisabled">Generate Passage</app-button>
 
 
 
 
 
 
47
  </div>
48
  </div>
49
  </div>
 
 
50
  <div class="loader-overlay" *ngIf="isGeneratingContent" role="status" aria-live="polite">
51
  <div class="loader">Loading</div>
52
  </div>
 
 
53
  <div class="popup-overlay" *ngIf="showPopup">
54
  <div class="popup-content">
55
+ <p>{{ errorMessage || 'We could not create the passage right now. Please try again.' }}</p>
56
+ <app-button (click)="closeErrorPopup()">Close</app-button>
 
 
 
57
  </div>
58
  </div>
59
  </section>
60
 
 
61
  <div *ngIf="content && !hasStarted" class="reading-card">
 
62
  <div class="reading-head">
63
+ <img src="assets/images/reading/back.png" (click)="goToIntroSection()" alt="" class="icon-img" />
 
 
 
64
  <h2 class="reading-title">Let’s Start Reading!</h2>
 
 
65
  <div class="head-actions">
66
  <button class="icon-btn" (click)="decreaseFont()" aria-label="Decrease font">A−</button>
67
  <button class="icon-btn" (click)="increaseFont()" aria-label="Increase font">A+</button>
68
+ <button class="icon-btn" [class.active]="isReading || ttsPaused" (click)="toggleReadAloud()" [attr.aria-pressed]="isReading || ttsPaused" aria-label="Read aloud">{{ isReading ? '⏸' : '🔊' }}</button>
 
 
 
 
 
 
 
 
 
 
 
69
  </div>
70
  </div>
 
 
71
  <div class="reading-meta">
72
  <span class="chip chip-topic" *ngIf="normalizedTopic || topic">📚 {{ normalizedTopic || topic }}</span>
73
+ <span class="chip chip-level" [attr.data-level]="difficulty">{{ difficulty | titlecase }}</span>
 
 
 
 
74
  </div>
 
 
75
  <div class="passage-shell">
76
  <div class="passage-text" [innerHTML]="transformContent(content)"></div>
77
  </div>
 
 
78
  <div class="reading-actions">
79
+ <app-button (click)="stopReadAloud(); generateQuestions()" [disabled]="isGenerateQuestionDisabled">Generate Questions</app-button>
 
 
 
 
 
 
80
  <div class="loader-overlay" *ngIf="loadingQuestions" aria-live="polite" aria-busy="true">
81
  <span class="loader">Loading</span>
82
  </div>
83
+ <app-button (click)="stopReadAloud(); resetAll()">Reset</app-button>
 
84
  </div>
85
  </div>
86
 
 
 
87
  <div class="mcq-card" *ngIf="hasStarted && questions?.length">
 
88
  <div class="mcq-card__header">
 
 
 
89
 
90
+ <img src="assets/images/reading/back.png" alt="" class="icon-img" />
 
 
91
 
92
+ <h3 class="mcq-card__title">Question {{ currentQuestionIndex + 1 }} of {{ questions.length }}</h3>
93
  <div class="mcq-card__actions">
94
+ <button class="user-guide-close-icon" (click)="startOver()">×</button>
95
  </div>
96
  </div>
 
 
97
  <div class="mcq-card__body">
 
98
  <div class="quiz-pill quiz-question-pill">
99
  <span class="qq-label">Question:</span>
100
  <span class="qq-text">{{ questions[currentQuestionIndex].question }}</span>
101
  </div>
 
 
102
  <ul class="quiz-options">
103
  <li *ngFor="let option of (questions[currentQuestionIndex]?.options | slice:0:4); let i = index">
104
+ <label class="quiz-pill quiz-option-pill" [ngClass]="{
 
105
  'is-selected': !questions[currentQuestionIndex].isChecked && getSelectedAnswer() === option,
106
  'is-correct': questions[currentQuestionIndex].isChecked && questions[currentQuestionIndex].correct_answer === option,
107
  'is-incorrect':questions[currentQuestionIndex].isChecked && getSelectedAnswer() === option && option !== questions[currentQuestionIndex].correct_answer
108
  }">
109
+ <input type="radio" class="visually-hidden" [name]="'q_'+currentQuestionIndex" [value]="option" [checked]="getSelectedAnswer() === option" (change)="setSelectedAnswer(option)" [disabled]="questions[currentQuestionIndex].isChecked" />
 
 
 
 
 
 
110
  <span class="slot">{{ ['A','B','C','D'][i] }}:</span>
111
  <span class="opt-text">{{ option }}</span>
112
  </label>
113
  </li>
114
  </ul>
115
  </div>
 
 
116
  <div class="mcq-card__footer">
117
+ <app-button *ngIf="currentQuestionIndex > 0" (click)="previousQuestion()">◀ Previous</app-button>
118
+ <div style="display: flex; gap: 10px; justify-content: flex-end; flex: 1;">
119
+ <app-button *ngIf="!questions[currentQuestionIndex].isChecked" (click)="validateAnswer(); scheduleCongratsIfLast()" [disabled]="!selectedAnswers[questions[currentQuestionIndex].question]">Validate</app-button>
120
+ <app-button *ngIf="questions[currentQuestionIndex].isChecked && currentQuestionIndex < questions.length - 1" (click)="nextQuestion()">Next ▶</app-button>
121
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  </div>
123
  </div>
 
 
 
124
  </div>
125
 
 
 
 
 
126
  <div class="congrats-overlay" *ngIf="showCongrats" aria-live="polite" aria-modal="true" role="dialog">
127
  <div class="congrats-card">
128
  <div class="score-badge" aria-label="Your score">
129
  Your Score: <span class="score">{{ scoreCorrect }}</span> / <span class="total">{{ scoreTotal }}</span>
130
  </div>
131
+ <h2>{{ congratsTitle }}</h2>
132
+ <p>{{ congratsMessage }}</p>
133
+ <app-button (click)="startOver()">Start Over</app-button>
 
134
  </div>
135
  </div>
 
136
  </div>
src/app/reading/reading.component.ts CHANGED
@@ -2,16 +2,19 @@ import { Component, ElementRef, ViewChild } from '@angular/core';
2
  import { ReadingService } from './reading.service';
3
  import { Router } from '@angular/router';
4
  import confetti from 'canvas-confetti';
5
-
 
 
 
6
 
7
  @Component({
8
  selector: 'app-reading',
9
  templateUrl: './reading.component.html',
10
- styleUrl: './reading.component.css'
 
 
11
  })
12
-
13
  export class ReadingComponent {
14
- // basic state
15
  loadingQuestions = false;
16
  isGeneratingContent = false;
17
  isGenerateDisabled = false;
@@ -19,31 +22,25 @@ export class ReadingComponent {
19
  showPopup = false;
20
  showSuggestions = false;
21
 
22
-
23
- // quiz state
24
  hasStarted = false;
25
  currentQuestionIndex = 0;
26
  questions: { question: string; options: string[]; correct_answer: string; isChecked?: boolean }[] = [];
27
  selectedAnswers: { [key: string]: string } = {};
28
 
29
- // content state
30
  topic: string = '';
31
  difficulty: 'easy' | 'medium' | 'hard' = 'easy';
32
  content: string = '';
33
  errorMessage: string = '';
34
  normalizedTopic: string = '';
35
 
36
- // suggestions state
37
  filteredSuggestions: string[] = [];
38
  activeIndex = -1;
39
 
40
- // read-aloud + font
41
  fontPx = parseInt(localStorage.getItem('passageFontPx') || '18', 10);
42
  isReading = false;
43
  ttsPaused = false;
44
  private ttsUtterance?: SpeechSynthesisUtterance;
45
 
46
- // topic lists
47
  topicsByDifficulty: Record<'easy' | 'medium' | 'hard', string[]> = {
48
  easy: [
49
  'My Family', 'My School', 'My Neighborhood', 'Community Helpers',
@@ -84,41 +81,26 @@ export class ReadingComponent {
84
 
85
  goToIntroSection(): void {
86
  this.stopReadAloud?.();
87
-
88
- // Show Intro by clearing passage, but DO NOT touch topic/difficulty
89
  this.content = '';
90
  this.hasStarted = false;
91
-
92
- // On Intro, allow user to click "Generate Passage" again
93
  this.isGenerateDisabled = false;
94
-
95
- // No passage now → disable "Generate Questions"
96
  this.refreshGenerateQuestionsState();
97
-
98
- // Make sure any congrats overlay is closed
99
  this.showCongrats = false;
100
  }
101
 
102
-
103
-
104
  generateContent(): void {
105
  this.showPopup = false;
106
  this.errorMessage = '';
107
-
108
  if (!this.topic.trim() || !this.difficulty.trim()) {
109
  this.errorMessage = 'Please enter a topic and select a difficulty level.';
110
  this.showPopup = true;
111
  return;
112
  }
113
-
114
  this.isGenerateDisabled = true;
115
  this.isGeneratingContent = true;
116
-
117
  this.readingService.generateContent(this.topic, this.difficulty).subscribe(
118
  (response) => {
119
  const text = (response?.content || '').trim();
120
-
121
- // ✅ Do not check for exact topic substring
122
  if (!text) {
123
  this.errorMessage = 'The server did not return any content.';
124
  this.showPopup = true;
@@ -128,21 +110,14 @@ export class ReadingComponent {
128
  this.normalizedTopic = '';
129
  return;
130
  }
131
-
132
  this.content = text;
133
-
134
- // Support both shapes: { normalized_topic } or { topic } or fallback to input
135
  this.normalizedTopic = (response?.normalized_topic || response?.topic || this.topic || '').trim();
136
-
137
  this.isGeneratingContent = false;
138
-
139
- // Show passage screen; enable "Generate Questions"
140
  this.hasStarted = false;
141
  this.refreshGenerateQuestionsState();
142
  },
143
  (error) => {
144
  console.error(error);
145
- // ✅ Show backend message if present (your Flask returns {"error": "..."} on 400)
146
  const msg = error?.error?.error || 'Invalid topic. Please enter a meaningful topic.';
147
  this.errorMessage = msg;
148
  this.showPopup = true;
@@ -152,13 +127,10 @@ export class ReadingComponent {
152
  );
153
  }
154
 
155
-
156
- /** Generate MCQs from current passage. */
157
  generateQuestions(): void {
158
  if (!this.content.trim()) return;
159
  this.loadingQuestions = true;
160
  this.isGenerateQuestionDisabled = true;
161
-
162
  this.readingService.generateQuestions(this.content, this.difficulty).subscribe(
163
  (response) => {
164
  this.questions = this.parseQuestions(response.questions);
@@ -171,17 +143,14 @@ export class ReadingComponent {
171
  (error) => {
172
  console.error(error);
173
  this.loadingQuestions = false;
174
- // IMPORTANT: allow user to try again on failure
175
- this.refreshGenerateQuestionsState(); // passage still exists → re-enable button
176
  }
177
  );
178
  }
179
 
180
- /** Parse questions from either JSON (preferred) or old plain-text format. */
181
  parseQuestions(
182
  raw: any
183
  ): { question: string; options: string[]; correct_answer: string; isChecked?: boolean }[] {
184
- // 1) If backend returns an array of question objects (JSON)
185
  if (Array.isArray(raw)) {
186
  return raw
187
  .map((q: any) => ({
@@ -192,55 +161,47 @@ export class ReadingComponent {
192
  }))
193
  .filter(q => q.question && q.options.length === 4);
194
  }
195
-
196
- // 2) Fallback: parse the old plain-text format
197
  const out: { question: string; options: string[]; correct_answer: string; isChecked?: boolean }[] = [];
198
  const text = String(raw || '');
199
  const blocks = text.split('\n\n');
200
-
201
  blocks.forEach(block => {
202
  const obj: any = {};
203
  const lines = block.split('\n');
204
-
205
  obj.question = (lines[0] || '').replace(/^(\d+\.\s*)?Question:\s*/i, '').trim();
206
-
207
  const optionsText = (lines[1] || '')
208
  .replace(/^Options:\s*\[/i, '')
209
  .replace(/\]\s*$/, '')
210
  .trim();
211
-
212
- //obj.options = optionsText ? optionsText.split(', ').map(o => o.trim()) : [];
213
-
214
  obj.options = optionsText
215
  ? optionsText.split(', ').map(o => o.trim()).slice(0, 4)
216
  : [];
217
  obj.correct_answer = (lines[2] || '').replace(/^Correct Answer:\s*/i, '').trim();
218
  obj.isChecked = false;
219
-
220
  if (obj.question && obj.options?.length) out.push(obj);
221
  });
222
-
223
  return out;
224
  }
225
 
226
- /** Record the selected answer for current question. */
227
  setSelectedAnswer(value: string): void {
228
  const curr = this.questions?.[this.currentQuestionIndex];
229
  if (curr) this.selectedAnswers[curr.question] = value;
230
  }
231
 
232
- /** Get the selected answer for current question. */
233
  getSelectedAnswer(): string {
234
  const curr = this.questions?.[this.currentQuestionIndex];
235
  return (curr && this.selectedAnswers[curr.question]) || '';
236
  }
237
 
238
- /** Move to next question. */
239
  nextQuestion(): void {
240
  if (this.currentQuestionIndex < this.questions.length - 1) this.currentQuestionIndex++;
241
  }
242
 
243
- /** Instantly marks current question as checked if an answer is chosen. */
 
 
 
 
 
244
  private markCurrentAsChecked(): void {
245
  const curr = this.questions[this.currentQuestionIndex];
246
  if (curr && this.selectedAnswers[curr.question]) {
@@ -248,73 +209,55 @@ export class ReadingComponent {
248
  }
249
  }
250
 
251
- /** Close the error popup. */
252
  closeErrorPopup(): void { this.showPopup = false; }
253
 
254
- /** Navigate to home route. */
255
  goToHome(): void { this.router.navigate(['/home']); }
256
 
257
- /** Return to content stage and re-enable input. */
258
  goToContentBlock(): void {
259
  this.hasStarted = false;
260
  this.content = '';
261
  this.isGenerateDisabled = false;
262
  }
263
 
264
- /** Reset everything to initial state. */
265
  resetAll(): void {
266
  this.initState();
267
  this.stopReadAloud?.();
268
-
269
- // Inputs / choices
270
  this.topic = '';
271
  this.difficulty = 'easy';
272
  this.normalizedTopic = '';
273
-
274
- // Generated passage and questions
275
  this.content = '';
276
  this.questions = [];
277
  this.currentQuestionIndex = 0;
278
  this.selectedAnswers = {};
279
-
280
- // View flags
281
  this.hasStarted = false;
282
  this.showCongrats = false;
283
-
284
- // UX flags
285
- this.isGenerateDisabled = false; // allow new passage generation
286
- this.isGenerateQuestionDisabled = true; // disabled until a passage exists
287
  this.isGeneratingContent = false;
288
  this.loadingQuestions = false;
289
  this.showPopup = false;
290
  this.errorMessage = '';
291
-
292
- // Score
293
  this.scoreCorrect = 0;
294
  this.scoreTotal = 0;
295
  }
296
 
297
- /** Return from MCQ to passage view. */
298
  goBack(): void {
299
- this.hasStarted = false; // return to passage view
300
- this.refreshGenerateQuestionsState(); // passage exists → enable button
301
  }
302
 
303
- /** Open suggestions for current difficulty. */
304
  openSuggestions(): void {
305
  this.filterSuggestions();
306
  this.showSuggestions = true;
307
  this.activeIndex = -1;
308
  }
309
 
310
- /** Update suggestions on typing. */
311
  onTyping(): void {
312
  this.filterSuggestions();
313
  this.showSuggestions = true;
314
  this.activeIndex = -1;
315
  }
316
 
317
- /** Handle suggestion keyboard navigation. */
318
  onKeydown(event: KeyboardEvent): void {
319
  if (!this.showSuggestions || !this.filteredSuggestions.length) return;
320
  if (event.key === 'ArrowDown') {
@@ -332,17 +275,14 @@ export class ReadingComponent {
332
  }
333
  }
334
 
335
- /** Choose a suggestion and close list. */
336
  selectSuggestion(val: string): void {
337
  this.topic = val || '';
338
  this.showSuggestions = false;
339
  this.isGenerateDisabled = false;
340
  }
341
 
342
- /** Hide suggestions after blur. */
343
  hideSuggestionsWithDelay(): void { setTimeout(() => (this.showSuggestions = false), 120); }
344
 
345
- /** Internal: filter topics based on query+level. */
346
  private filterSuggestions(): void {
347
  const q = (this.topic || '').toLowerCase().trim();
348
  const pool = this.topicsByDifficulty[this.difficulty || 'medium'] || [];
@@ -350,7 +290,6 @@ export class ReadingComponent {
350
  : pool.slice(0, 12);
351
  }
352
 
353
- /** Clear topic and reopen suggestions. */
354
  onClearTopic(): void {
355
  this.topic = '';
356
  this.errorMessage = '';
@@ -359,7 +298,6 @@ export class ReadingComponent {
359
  this.isGenerateDisabled = false;
360
  }
361
 
362
- /** Change difficulty and refresh suggestions. */
363
  onDifficultyChange(val: string): void {
364
  this.difficulty = (val || '') as any;
365
  if (!this.difficulty) {
@@ -373,19 +311,15 @@ export class ReadingComponent {
373
  this.activeIndex = -1;
374
  }
375
 
376
- /** Apply font size CSS variable. */
377
  applyFont(): void {
378
  document.documentElement.style.setProperty('--passage-font', `${this.fontPx}px`);
379
  localStorage.setItem('passageFontPx', String(this.fontPx));
380
  }
381
 
382
- /** Decrease font size (min 14px). */
383
  decreaseFont(): void { this.fontPx = Math.max(14, this.fontPx - 2); this.applyFont(); }
384
 
385
- /** Increase font size (max 30px). */
386
  increaseFont(): void { this.fontPx = Math.min(30, this.fontPx + 2); this.applyFont(); }
387
 
388
- /** Init defaults for the screen. */
389
  private initState(): void {
390
  this.hasStarted = false;
391
  this.isGeneratingContent = false;
@@ -407,10 +341,8 @@ export class ReadingComponent {
407
  this.applyFont();
408
  }
409
 
410
- /** Lifecycle hook to set initial UI. */
411
  ngOnInit(): void { this.initState(); }
412
 
413
- /** Build safe HTML for passage body. */
414
  transformContent(raw: string): string {
415
  try {
416
  const passage = this.extractPassage(raw);
@@ -421,7 +353,6 @@ export class ReadingComponent {
421
  }
422
  }
423
 
424
- /** Extract text between "Passage:" and "Summary:". */
425
  private extractPassage(raw: string): string {
426
  if (!raw) return '';
427
  const p = raw.indexOf('Passage:');
@@ -431,12 +362,10 @@ export class ReadingComponent {
431
  return slice.trim();
432
  }
433
 
434
- /** Minimal HTML escape. */
435
  private escapeHtml(s: string): string {
436
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
437
  }
438
 
439
- /** Toggle speech synthesis for the passage. */
440
  toggleReadAloud(): void {
441
  if (!this.content || typeof window === 'undefined' || !('speechSynthesis' in window)) {
442
  this.errorMessage = 'Read Aloud is not supported in this browser.';
@@ -444,11 +373,9 @@ export class ReadingComponent {
444
  return;
445
  }
446
  const synth = window.speechSynthesis;
447
-
448
  if (this.isReading && !synth.paused) { synth.pause(); this.isReading = false; this.ttsPaused = true; return; }
449
  if (this.ttsPaused) { synth.resume(); this.isReading = true; this.ttsPaused = false; return; }
450
-
451
- this.stopReadAloud(); // fresh start
452
  const text = this.extractPassage(this.content) || this.content;
453
  const u = new SpeechSynthesisUtterance(text);
454
  u.rate = 1.0; u.pitch = 1.0; u.lang = 'en-US';
@@ -459,7 +386,6 @@ export class ReadingComponent {
459
  this.isReading = true;
460
  }
461
 
462
- /** Stop speech and clear flags. */
463
  stopReadAloud(): void {
464
  if (typeof window !== 'undefined' && 'speechSynthesis' in window) {
465
  window.speechSynthesis.cancel();
@@ -469,58 +395,41 @@ export class ReadingComponent {
469
  this.ttsUtterance = undefined;
470
  }
471
 
472
-
473
-
474
-
475
-
476
- // add inside the component class
477
  showCongrats = false;
478
  scoreCorrect = 0;
479
-
480
  private confettiInterval: any = null;
481
-
482
- // scoreCorrect: number = 0; // Correct answers
483
- scoreTotal: number = 3; // Total number of questions
484
  congratsTitle: string = '';
485
  congratsMessage: string = '';
486
 
487
  scheduleCongratsIfLast(): void {
488
- // must be last question AND already marked as checked
489
  const isLast = this.currentQuestionIndex === this.questions.length - 1;
490
  const curr = this.questions[this.currentQuestionIndex];
491
  if (!isLast || !curr?.isChecked) return;
492
-
493
- // small delay so learners can see the final green/red states first
494
  setTimeout(() => {
495
  const { correct, total } = this.computeScore();
496
  this.scoreCorrect = correct;
497
  this.scoreTotal = total;
498
-
499
- // Determine message based on score
500
  if (this.scoreCorrect === this.scoreTotal) {
501
- this.showCongratsMessage("🎉 Congratulations 🎉", "You have completed all questions with a perfect score!");
502
  } else if (this.scoreCorrect > 0) {
503
- this.showCongratsMessage("Good Job!", "You did well, but there is room for improvement.");
504
  } else {
505
- this.showCongratsMessage("Keep Trying!", "Don't give up, try again!");
506
  }
507
-
508
  this.showCongrats = true;
509
- this.triggerConfetti(); // Assuming this is a method that triggers confetti animation.
510
  }, 800);
511
  }
512
 
513
- // Helper function to show the appropriate congratulatory message
514
  showCongratsMessage(title: string, message: string) {
515
  this.congratsTitle = title;
516
  this.congratsMessage = message;
517
  }
518
- /** Compute score without altering validation code. */
519
  private computeScore(): { correct: number; total: number } {
520
  let correct = 0;
521
  const total = this.questions.length;
522
-
523
- // Uses your existing structures: selectedAnswers[...] and question.correct_answer
524
  for (const q of this.questions) {
525
  const chosen = this.selectedAnswers[q.question];
526
  if (chosen && chosen === q.correct_answer) correct++;
@@ -528,10 +437,8 @@ export class ReadingComponent {
528
  return { correct, total };
529
  }
530
 
531
-
532
  private triggerConfetti(): void {
533
  if (this.confettiInterval) clearInterval(this.confettiInterval);
534
-
535
  this.confettiInterval = setInterval(() => {
536
  confetti({
537
  startVelocity: 30,
@@ -551,48 +458,28 @@ export class ReadingComponent {
551
  if (canvas) canvas.remove();
552
  }
553
 
554
-
555
-
556
  startOver(): void {
557
  this.stopConfetti();
558
  this.showCongrats = false;
559
- this.resetAll(); // use your current reset method
560
  }
561
 
562
-
563
  ngOnDestroy(): void {
564
-
565
  this.stopConfetti();
566
  }
567
- // optional: if you want a short “Validating…” state on the button
568
  isValidating = false;
569
- /** Called by the template. Keeps existing validation, then schedules the final modal if last. */
570
  validateAnswer(): void {
571
- // OPTIONAL: short “validating” hold on the button (remove if not needed)
572
  this.isValidating = true;
573
  setTimeout(() => (this.isValidating = false), 300);
574
-
575
- // 👉 IMPORTANT:
576
- // If you already have a function that validates/marks the current question, call it here.
577
- // Example:
578
- // this.checkAnswer(); // or
579
- // this.markCurrentAsChecked(); // or your own method
580
- // If you do not have one, the fallback below will simply mark the current question as checked.
581
-
582
  const curr = this.questions?.[this.currentQuestionIndex];
583
  if (curr && this.selectedAnswers?.[curr.question]) {
584
- curr.isChecked = true; // fallback mark; harmless if your own method did it already
585
  }
586
-
587
- // After the question has been marked, if it is the last, open the modal
588
  this.scheduleCongratsIfLast();
589
  }
590
 
591
-
592
- /** Enable/disable "Generate Questions" based on whether a passage exists */
593
  private refreshGenerateQuestionsState(): void {
594
  this.isGenerateQuestionDisabled = !(this.content && this.content.trim().length > 0);
595
  }
596
-
597
-
598
  }
 
2
  import { ReadingService } from './reading.service';
3
  import { Router } from '@angular/router';
4
  import confetti from 'canvas-confetti';
5
+ import { ButtonComponent } from '../shared/button/button.component';
6
+ import { CommonModule } from '@angular/common';
7
+ import { FormsModule } from '@angular/forms';
8
+ import { HeaderComponent } from '../shared/header/header.component';
9
 
10
  @Component({
11
  selector: 'app-reading',
12
  templateUrl: './reading.component.html',
13
+ styleUrl: './reading.component.css',
14
+ standalone: true,
15
+ imports: [ButtonComponent, CommonModule, FormsModule, HeaderComponent]
16
  })
 
17
  export class ReadingComponent {
 
18
  loadingQuestions = false;
19
  isGeneratingContent = false;
20
  isGenerateDisabled = false;
 
22
  showPopup = false;
23
  showSuggestions = false;
24
 
 
 
25
  hasStarted = false;
26
  currentQuestionIndex = 0;
27
  questions: { question: string; options: string[]; correct_answer: string; isChecked?: boolean }[] = [];
28
  selectedAnswers: { [key: string]: string } = {};
29
 
 
30
  topic: string = '';
31
  difficulty: 'easy' | 'medium' | 'hard' = 'easy';
32
  content: string = '';
33
  errorMessage: string = '';
34
  normalizedTopic: string = '';
35
 
 
36
  filteredSuggestions: string[] = [];
37
  activeIndex = -1;
38
 
 
39
  fontPx = parseInt(localStorage.getItem('passageFontPx') || '18', 10);
40
  isReading = false;
41
  ttsPaused = false;
42
  private ttsUtterance?: SpeechSynthesisUtterance;
43
 
 
44
  topicsByDifficulty: Record<'easy' | 'medium' | 'hard', string[]> = {
45
  easy: [
46
  'My Family', 'My School', 'My Neighborhood', 'Community Helpers',
 
81
 
82
  goToIntroSection(): void {
83
  this.stopReadAloud?.();
 
 
84
  this.content = '';
85
  this.hasStarted = false;
 
 
86
  this.isGenerateDisabled = false;
 
 
87
  this.refreshGenerateQuestionsState();
 
 
88
  this.showCongrats = false;
89
  }
90
 
 
 
91
  generateContent(): void {
92
  this.showPopup = false;
93
  this.errorMessage = '';
 
94
  if (!this.topic.trim() || !this.difficulty.trim()) {
95
  this.errorMessage = 'Please enter a topic and select a difficulty level.';
96
  this.showPopup = true;
97
  return;
98
  }
 
99
  this.isGenerateDisabled = true;
100
  this.isGeneratingContent = true;
 
101
  this.readingService.generateContent(this.topic, this.difficulty).subscribe(
102
  (response) => {
103
  const text = (response?.content || '').trim();
 
 
104
  if (!text) {
105
  this.errorMessage = 'The server did not return any content.';
106
  this.showPopup = true;
 
110
  this.normalizedTopic = '';
111
  return;
112
  }
 
113
  this.content = text;
 
 
114
  this.normalizedTopic = (response?.normalized_topic || response?.topic || this.topic || '').trim();
 
115
  this.isGeneratingContent = false;
 
 
116
  this.hasStarted = false;
117
  this.refreshGenerateQuestionsState();
118
  },
119
  (error) => {
120
  console.error(error);
 
121
  const msg = error?.error?.error || 'Invalid topic. Please enter a meaningful topic.';
122
  this.errorMessage = msg;
123
  this.showPopup = true;
 
127
  );
128
  }
129
 
 
 
130
  generateQuestions(): void {
131
  if (!this.content.trim()) return;
132
  this.loadingQuestions = true;
133
  this.isGenerateQuestionDisabled = true;
 
134
  this.readingService.generateQuestions(this.content, this.difficulty).subscribe(
135
  (response) => {
136
  this.questions = this.parseQuestions(response.questions);
 
143
  (error) => {
144
  console.error(error);
145
  this.loadingQuestions = false;
146
+ this.refreshGenerateQuestionsState();
 
147
  }
148
  );
149
  }
150
 
 
151
  parseQuestions(
152
  raw: any
153
  ): { question: string; options: string[]; correct_answer: string; isChecked?: boolean }[] {
 
154
  if (Array.isArray(raw)) {
155
  return raw
156
  .map((q: any) => ({
 
161
  }))
162
  .filter(q => q.question && q.options.length === 4);
163
  }
 
 
164
  const out: { question: string; options: string[]; correct_answer: string; isChecked?: boolean }[] = [];
165
  const text = String(raw || '');
166
  const blocks = text.split('\n\n');
 
167
  blocks.forEach(block => {
168
  const obj: any = {};
169
  const lines = block.split('\n');
 
170
  obj.question = (lines[0] || '').replace(/^(\d+\.\s*)?Question:\s*/i, '').trim();
 
171
  const optionsText = (lines[1] || '')
172
  .replace(/^Options:\s*\[/i, '')
173
  .replace(/\]\s*$/, '')
174
  .trim();
 
 
 
175
  obj.options = optionsText
176
  ? optionsText.split(', ').map(o => o.trim()).slice(0, 4)
177
  : [];
178
  obj.correct_answer = (lines[2] || '').replace(/^Correct Answer:\s*/i, '').trim();
179
  obj.isChecked = false;
 
180
  if (obj.question && obj.options?.length) out.push(obj);
181
  });
 
182
  return out;
183
  }
184
 
 
185
  setSelectedAnswer(value: string): void {
186
  const curr = this.questions?.[this.currentQuestionIndex];
187
  if (curr) this.selectedAnswers[curr.question] = value;
188
  }
189
 
 
190
  getSelectedAnswer(): string {
191
  const curr = this.questions?.[this.currentQuestionIndex];
192
  return (curr && this.selectedAnswers[curr.question]) || '';
193
  }
194
 
 
195
  nextQuestion(): void {
196
  if (this.currentQuestionIndex < this.questions.length - 1) this.currentQuestionIndex++;
197
  }
198
 
199
+ previousQuestion(): void {
200
+ if (this.currentQuestionIndex > 0) {
201
+ this.currentQuestionIndex--;
202
+ }
203
+ }
204
+
205
  private markCurrentAsChecked(): void {
206
  const curr = this.questions[this.currentQuestionIndex];
207
  if (curr && this.selectedAnswers[curr.question]) {
 
209
  }
210
  }
211
 
 
212
  closeErrorPopup(): void { this.showPopup = false; }
213
 
 
214
  goToHome(): void { this.router.navigate(['/home']); }
215
 
 
216
  goToContentBlock(): void {
217
  this.hasStarted = false;
218
  this.content = '';
219
  this.isGenerateDisabled = false;
220
  }
221
 
 
222
  resetAll(): void {
223
  this.initState();
224
  this.stopReadAloud?.();
 
 
225
  this.topic = '';
226
  this.difficulty = 'easy';
227
  this.normalizedTopic = '';
 
 
228
  this.content = '';
229
  this.questions = [];
230
  this.currentQuestionIndex = 0;
231
  this.selectedAnswers = {};
 
 
232
  this.hasStarted = false;
233
  this.showCongrats = false;
234
+ this.isGenerateDisabled = false;
235
+ this.isGenerateQuestionDisabled = true;
 
 
236
  this.isGeneratingContent = false;
237
  this.loadingQuestions = false;
238
  this.showPopup = false;
239
  this.errorMessage = '';
 
 
240
  this.scoreCorrect = 0;
241
  this.scoreTotal = 0;
242
  }
243
 
 
244
  goBack(): void {
245
+ this.hasStarted = false;
246
+ this.refreshGenerateQuestionsState();
247
  }
248
 
 
249
  openSuggestions(): void {
250
  this.filterSuggestions();
251
  this.showSuggestions = true;
252
  this.activeIndex = -1;
253
  }
254
 
 
255
  onTyping(): void {
256
  this.filterSuggestions();
257
  this.showSuggestions = true;
258
  this.activeIndex = -1;
259
  }
260
 
 
261
  onKeydown(event: KeyboardEvent): void {
262
  if (!this.showSuggestions || !this.filteredSuggestions.length) return;
263
  if (event.key === 'ArrowDown') {
 
275
  }
276
  }
277
 
 
278
  selectSuggestion(val: string): void {
279
  this.topic = val || '';
280
  this.showSuggestions = false;
281
  this.isGenerateDisabled = false;
282
  }
283
 
 
284
  hideSuggestionsWithDelay(): void { setTimeout(() => (this.showSuggestions = false), 120); }
285
 
 
286
  private filterSuggestions(): void {
287
  const q = (this.topic || '').toLowerCase().trim();
288
  const pool = this.topicsByDifficulty[this.difficulty || 'medium'] || [];
 
290
  : pool.slice(0, 12);
291
  }
292
 
 
293
  onClearTopic(): void {
294
  this.topic = '';
295
  this.errorMessage = '';
 
298
  this.isGenerateDisabled = false;
299
  }
300
 
 
301
  onDifficultyChange(val: string): void {
302
  this.difficulty = (val || '') as any;
303
  if (!this.difficulty) {
 
311
  this.activeIndex = -1;
312
  }
313
 
 
314
  applyFont(): void {
315
  document.documentElement.style.setProperty('--passage-font', `${this.fontPx}px`);
316
  localStorage.setItem('passageFontPx', String(this.fontPx));
317
  }
318
 
 
319
  decreaseFont(): void { this.fontPx = Math.max(14, this.fontPx - 2); this.applyFont(); }
320
 
 
321
  increaseFont(): void { this.fontPx = Math.min(30, this.fontPx + 2); this.applyFont(); }
322
 
 
323
  private initState(): void {
324
  this.hasStarted = false;
325
  this.isGeneratingContent = false;
 
341
  this.applyFont();
342
  }
343
 
 
344
  ngOnInit(): void { this.initState(); }
345
 
 
346
  transformContent(raw: string): string {
347
  try {
348
  const passage = this.extractPassage(raw);
 
353
  }
354
  }
355
 
 
356
  private extractPassage(raw: string): string {
357
  if (!raw) return '';
358
  const p = raw.indexOf('Passage:');
 
362
  return slice.trim();
363
  }
364
 
 
365
  private escapeHtml(s: string): string {
366
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
367
  }
368
 
 
369
  toggleReadAloud(): void {
370
  if (!this.content || typeof window === 'undefined' || !('speechSynthesis' in window)) {
371
  this.errorMessage = 'Read Aloud is not supported in this browser.';
 
373
  return;
374
  }
375
  const synth = window.speechSynthesis;
 
376
  if (this.isReading && !synth.paused) { synth.pause(); this.isReading = false; this.ttsPaused = true; return; }
377
  if (this.ttsPaused) { synth.resume(); this.isReading = true; this.ttsPaused = false; return; }
378
+ this.stopReadAloud();
 
379
  const text = this.extractPassage(this.content) || this.content;
380
  const u = new SpeechSynthesisUtterance(text);
381
  u.rate = 1.0; u.pitch = 1.0; u.lang = 'en-US';
 
386
  this.isReading = true;
387
  }
388
 
 
389
  stopReadAloud(): void {
390
  if (typeof window !== 'undefined' && 'speechSynthesis' in window) {
391
  window.speechSynthesis.cancel();
 
395
  this.ttsUtterance = undefined;
396
  }
397
 
 
 
 
 
 
398
  showCongrats = false;
399
  scoreCorrect = 0;
 
400
  private confettiInterval: any = null;
401
+ scoreTotal: number = 3;
 
 
402
  congratsTitle: string = '';
403
  congratsMessage: string = '';
404
 
405
  scheduleCongratsIfLast(): void {
 
406
  const isLast = this.currentQuestionIndex === this.questions.length - 1;
407
  const curr = this.questions[this.currentQuestionIndex];
408
  if (!isLast || !curr?.isChecked) return;
 
 
409
  setTimeout(() => {
410
  const { correct, total } = this.computeScore();
411
  this.scoreCorrect = correct;
412
  this.scoreTotal = total;
 
 
413
  if (this.scoreCorrect === this.scoreTotal) {
414
+ this.showCongratsMessage('🎉 Congratulations 🎉', 'You have completed all questions with a perfect score!');
415
  } else if (this.scoreCorrect > 0) {
416
+ this.showCongratsMessage('Good Job!', 'You did well, but there is room for improvement.');
417
  } else {
418
+ this.showCongratsMessage('Keep Trying!', "Don't give up, try again!");
419
  }
 
420
  this.showCongrats = true;
421
+ this.triggerConfetti();
422
  }, 800);
423
  }
424
 
 
425
  showCongratsMessage(title: string, message: string) {
426
  this.congratsTitle = title;
427
  this.congratsMessage = message;
428
  }
429
+
430
  private computeScore(): { correct: number; total: number } {
431
  let correct = 0;
432
  const total = this.questions.length;
 
 
433
  for (const q of this.questions) {
434
  const chosen = this.selectedAnswers[q.question];
435
  if (chosen && chosen === q.correct_answer) correct++;
 
437
  return { correct, total };
438
  }
439
 
 
440
  private triggerConfetti(): void {
441
  if (this.confettiInterval) clearInterval(this.confettiInterval);
 
442
  this.confettiInterval = setInterval(() => {
443
  confetti({
444
  startVelocity: 30,
 
458
  if (canvas) canvas.remove();
459
  }
460
 
 
 
461
  startOver(): void {
462
  this.stopConfetti();
463
  this.showCongrats = false;
464
+ this.resetAll();
465
  }
466
 
 
467
  ngOnDestroy(): void {
 
468
  this.stopConfetti();
469
  }
470
+
471
  isValidating = false;
 
472
  validateAnswer(): void {
 
473
  this.isValidating = true;
474
  setTimeout(() => (this.isValidating = false), 300);
 
 
 
 
 
 
 
 
475
  const curr = this.questions?.[this.currentQuestionIndex];
476
  if (curr && this.selectedAnswers?.[curr.question]) {
477
+ curr.isChecked = true;
478
  }
 
 
479
  this.scheduleCongratsIfLast();
480
  }
481
 
 
 
482
  private refreshGenerateQuestionsState(): void {
483
  this.isGenerateQuestionDisabled = !(this.content && this.content.trim().length > 0);
484
  }
 
 
485
  }
src/app/vocabulary-builder/vocabulary-builder.component.css CHANGED
@@ -1 +1 @@
1
-
2
  margin-top: 1rem;
3
  padding: 1rem;
4
  background-color: #f0f4f8;
5
  border-radius: 8px;
6
  border: 1px solid #cdd5df;
7
  background: rgba(255, 255, 255, 0.9);
8
  width: 80vw;
9
  position: absolute;
10
  top: 50%;
11
  left: 50%;
12
  transform: translate(-50%, -50%);
13
  height: 66vh;
14
  display: flex;
15
  justify-content: space-between;
16
  align-items: flex-start;
17
  padding: 4vw;
18
  gap: 2vw;
19
  background-color: #fff;
20
  border: 10px solid #009688;
21
  border-radius: 1vw;
22
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
23
  margin: 2vw auto;
24
  max-width: 90%;
25
  display: flex;
26
  align-items: center;
27
  gap: 4vw;
28
  width: 50%;
29
  font-size: 2.5vw;
30
  font-weight: 800;
31
  color: #006780;
32
  margin-bottom: 20px;
33
  text-align: center;
34
  width: 100%;
35
  height: auto;
36
  border-radius: 10px;
37
  width: 66%;
38
  .description-container p {
39
  font-size: 1.4vw;
40
  margin-bottom: 30px;
41
  text-align: justify;
42
  }
43
  position: relative;
44
  border-radius: 0.5vw;
45
  font-size: 1.5vw;
46
  transition: background-color 0.3s;
47
  width: auto;
48
  font-weight: bold;
49
  background-color: #006780;
50
  border: none;
51
  color: white;
52
  padding: 15px 32px;
53
  text-align: center;
54
  text-decoration: none;
55
  display: inline-flex;
56
  align-items: center;
57
  justify-content: center;
58
  position: relative;
59
  min-width: 5vw;
60
  height: 2.9vw;
61
  margin-top: 1.5vw;
62
  .validate-button:hover {
63
  background-color: #bdc3c7;
64
  box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
65
  }
66
  .validate-button:disabled {
67
  background-color: #ccc;
68
  cursor: not-allowed;
69
  }
70
  position: absolute;
71
  border-radius: 0.5vw;
72
  font-size: 1.5vw;
73
  transition: background-color 0.3s;
74
  font-weight: bold;
75
  background-color: #006780;
76
  border: none;
77
  color: white;
78
  left: 50%;
79
  text-decoration: none;
80
  transform: translateX(-50%);
81
  padding: 0.5vw;
82
  .submit-button:hover {
83
  background-color: #bdc3c7;
84
  box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
85
  }
86
  .submit-button:disabled {
87
  background-color: #ccc;
88
  cursor: not-allowed;
89
  }
90
  background: rgba(255, 255, 255, 0.9);
91
  padding: 2vw;
92
  border-radius: 1vw;
93
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
94
  width: 80vw;
95
  position: absolute;
96
  top: 54%;
97
  left: 50%;
98
  transform: translate(-50%, -50%);
99
  height: 70vh;
100
  border: 10px solid #009688;
101
  background-color: #fff;
102
  .card2 .content-container {
103
  display: flex;
104
  justify-content: space-around;
105
  align-items: center;
106
  height: 100%;
107
  }
108
  max-width: 100%;
109
  height: auto;
110
  display: flex;
111
  flex-direction: column;
112
  align-items: center;
113
  justify-content: center;
114
  text-align: center;
115
  width: 50%;
116
  .word-container h2 {
117
  font-size: 2.5rem;
118
  font-weight: bold;
119
  color: #333;
120
  margin-top: 2.7vw;
121
  }
122
  width: 45%;
123
  text-align: center;
124
  padding: 20px;
125
  background: rgba(255, 255, 255, 0.7);
126
  border-radius: 10px;
127
  box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2);
128
  margin-right: 4vw;
129
  font-size: 1.2vw;
130
  margin-bottom: 20px;
131
  font-weight: 700;
132
  display: flex;
133
  flex-direction: column;
134
  align-items: center;
135
  background-color: #ccc;
136
  color: white;
137
  padding: 12px 24px;
138
  border-radius: 5px;
139
  cursor: pointer;
140
  font-size: 1.2rem;
141
  margin: 10px;
142
  width: 80%;
143
  transition: background-color 0.3s ease;
144
  background-color: #ffffff;
145
  color: #333;
146
  border: 2px solid #000000;
147
  font-size: 1.1vw;
148
  border-radius: 3vw;
149
  transition: all 0.3s ease;
150
  font-weight: 600;
151
  display: flex;
152
  align-items: center;
153
  justify-content: center;
154
  box-shadow: -1px 1vw 0.5vw rgb(0 0 0 / 13%);
155
  height: 100%;
156
  text-align: center;
157
  white-space: normal;
158
  word-break: break-word;
159
  width: 21vw;
160
  /* Make state classes override :hover */
161
  .option-btn.correct,
162
  .option-btn.incorrect,
163
  .option-btn.selected {
164
  position: relative; /* keeps layout stable */
165
  }
166
  /* Correct = green (state wins even on hover) */
167
  .option-btn.correct,
168
  .option-btn.correct:hover {
169
  background: #e8f7ed; /* soft green */
170
  border-color: #22c55e; /* green-500 */
171
  color: #14532d; /* dark green text */
172
  }
173
  /* Incorrect = red (state wins even on hover) */
174
  .option-btn.incorrect,
175
  .option-btn.incorrect:hover {
176
  background: #fee2e2; /* soft red */
177
  border-color: #ef4444; /* red-500 */
178
  color: #7f1d1d; /* dark red text */
179
  }
180
  /* Selected (pre-validation) = blue (state wins even on hover) */
181
  .option-btn.selected,
182
  .option-btn.selected:hover {
183
  background: #e0f2fe; /* soft blue */
184
  border-color: #3b82f6; /* blue-500 */
185
  color: #0c4a6e; /* dark blue text */
186
  }
187
  /* Default hover only when no state class is present */
188
  .option-btn:not(.selected):not(.correct):not(.incorrect):hover {
189
  background: #f3f4f6; /* light grey hover */
190
  }
191
  /* Optional: disabled look still visible */
192
  .option-btn.disabled,
193
  .option-btn:disabled {
194
  cursor: not-allowed;
195
  filter: grayscale(10%);
196
  }
197
  .option-btn.picked {
198
  outline: 2px dashed rgba(0,0,0,0.25);
199
  outline-offset: 2px;
200
  }
201
  margin-left: 8px;
202
  padding: 2px 8px;
203
  border-radius: 12px;
204
  font-size: 12px;
205
  background: rgba(0,0,0,0.06);
206
  background: rgba(255, 255, 255, 0.9);
207
  padding: 2vw;
208
  border-radius: 1vw;
209
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
210
  width: 80vw;
211
  position: absolute;
212
  top: 54%;
213
  left: 50%;
214
  transform: translate(-50%, -50%);
215
  height: 70vh;
216
  border: 10px solid #009688;
217
  background-color: #fff;
218
  list-style-type: disc;
219
  padding: 0 5vw;
220
  color: #000000;
221
  overflow-y: auto;
222
  height: 18vw;
223
  font-size: 1.5vw;
224
  margin-bottom: 30px;
225
  text-align: justify;
226
  .feedback-list1 li {
227
  margin-bottom: 10px;
228
  line-height: 1.5;
229
  display: list-item;
230
  }
231
  list-style-type: none !important;
232
  text-align: justify;
233
  height: 20vw;
234
  overflow-y: auto;
235
  padding: 0 3vw;
236
  margin-bottom: 2vw;
237
  font-size: 1.5vw;
238
  .feedback-list li {
239
  margin-bottom: 1vw;
240
  font-weight: 500;
241
  }
242
  color: green;
243
  color: red;
244
  0% {
245
  transform: translate(-50%, -50%) rotate(0deg);
246
  }
247
  100% {
248
  transform: translate(-50%, -50%) rotate(360deg);
249
  }
250
  background: rgba(255, 255, 255, 0.9);
251
  padding: 2vw;
252
  border-radius: 1vw;
253
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
254
  width: 80vw;
255
  position: absolute;
256
  top: 54%;
257
  left: 50%;
258
  transform: translate(-50%, -50%);
259
  height: 70vh;
260
  border: 10px solid #009688;
261
  background-color: #fff;
262
  resize: none
263
  font-size: 1.8vw;
264
  text-align: center;
265
  margin-bottom: 20px;
266
  color: #006780;
267
  font-weight: 600;
268
  display: flex;
269
  flex-direction: column;
270
  align-items: center;
271
  width: 100%;
272
  display: flex;
273
  justify-content: space-between;
274
  align-items: center;
275
  width: 80%;
276
  max-width: 600px;
277
  margin-bottom: 20px;
278
  .card4 .sentence-input-group label {
279
  font-weight: bold;
280
  text-align: left;
281
  width: 30%;
282
  font-size: 1.7vw;
283
  }
284
  .card4 .sentence-input-group .input-group {
285
  width: 60%;
286
  }
287
  .card4 .sentence-input-group textarea {
288
  width: 138%;
289
  height: 100%;
290
  font-size: 1.4rem;
291
  border: 2px solid #006780;
292
  font-family: Arial, sans-serif;
293
  border-radius: 8px;
294
  padding: 0.4vw;
295
  }
296
  .card4 .sentence-input-group textarea:focus {
297
  outline: none;
298
  border: 1px solid #007bff;
299
  }
300
  background: rgba(255, 255, 255, 0.9);
301
  padding: 2vw;
302
  border-radius: 1vw;
303
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
304
  width: 80vw;
305
  position: absolute;
306
  top: 54%;
307
  left: 50%;
308
  transform: translate(-50%, -50%);
309
  height: 70vh;
310
  border: 10px solid #009688;
311
  background-color: #fff;
312
  .card5 .feedback-text {
313
  font-size: 1.8vw;
314
  font-weight: 600;
315
  color: #333;
316
  margin-top: 1vw;
317
  word-wrap: break-word;
318
  max-height: 400px;
319
  overflow-y: auto;
320
  padding-left: 20px;
321
  padding-right: 20px;
322
  text-align: justify;
323
  margin-bottom: 1vw;
324
  }
325
  position: absolute;
326
  top: 1vw;
327
  left: 2vw;
328
  color: #007bff;
329
  font-size: 24px;
330
  width: 5vw;
331
  cursor: pointer;
332
  transition: transform 0.2s ease-in-out;
333
  .back-icon:hover {
334
  transform: scale(1.1);
335
  color: #0056b3;
336
  }
337
  .back-icon img {
338
  width: 30px;
339
  height: auto;
340
  cursor: pointer;
341
  }
342
  position: absolute;
343
  top: -1vw;
344
  right: -1.4vw;
345
  width: 36px;
346
  height: 36px;
347
  display: grid;
348
  place-items: center;
349
  border-radius: 50%;
350
  background: #009688;
351
  color: #0f172a;
352
  border: 4px solid #009688;
353
  font-size: 18px;
354
  font-weight: 800;
355
  cursor: pointer;
356
  box-shadow: 0 4px 10px rgba(0, 0, 0, .15);
357
  transition: transform .15s ease, box-shadow .15s ease, background .15s ease;
358
  z-index: 10;
359
  .icon-btn1:hover {
360
  transform: scale(1.06);
361
  box-shadow: 0 6px 14px rgba(0,0,0,.22);
362
  background: #f7fafc;
363
  }
364
  .icon-btn1:active {
365
  transform: scale(0.98);
366
  }
367
  .icon-btn1 {
368
  width: 50px;
369
  height: 50px;
370
  font-size: 20px;
371
  }
372
  position: fixed;
373
  top: 0;
374
  left: 0;
375
  width: 100%;
376
  height: 100%;
377
  display: flex;
378
  justify-content: center;
379
  align-items: center;
380
  z-index: 1000;
381
  font-size: 15px;
382
  width: 1.5em;
383
  height: 1.5em;
384
  border-radius: 50%;
385
  position: relative;
386
  text-indent: -9999em;
387
  animation: mulShdSpin 1.1s infinite ease;
388
  transform: translateZ(0);
389
  0%, 100% {
390
  box-shadow: 0em -3em 0em 0em #808080, 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.5), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.7);
391
  }
392
  12.5% {
393
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.7), 2.2em -2.2em 0 0em #808080, 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.5);
394
  }
395
  25% {
396
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.5), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.7), 3em 0em 0 0em #808080, 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
397
  }
398
  37.5% {
399
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.5), 3em 0em 0 0em rgba(128, 128, 128, 0.7), 2.2em 2.2em 0 0em #808080, 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
400
  }
401
  50% {
402
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.5), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.7), 0em 3em 0 0em #808080, -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
403
  }
404
  62.5% {
405
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.5), 0em 3em 0 0em rgba(128, 128, 128, 0.7), -2.2em 2.2em 0 0em #808080, -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
406
  }
407
  75% {
408
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.5), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.7), -3em 0em 0 0em #808080, -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
409
  }
410
  87.5% {
411
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.5), -3em 0em 0 0em rgba(128, 128, 128, 0.7), -2.2em -2.2em 0 0em #808080;
412
  }
 
413
  background: rgba(255, 255, 255, 0.9);
414
  width: 80vw;
415
  position: absolute;
416
  top: 50%;
417
  left: 50%;
418
  transform: translate(-50%, -50%);
419
  height: 66vh;
420
  display: flex;
421
  justify-content: space-between;
422
  align-items: flex-start;
423
  padding: 4vw;
424
  gap: 2vw;
425
  background-color: #fff;
426
  border: 10px solid var(--main-accent-color);
427
  border-radius: 1vw;
428
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
429
  margin: 2vw auto;
430
  max-width: 90%;
431
  display: flex;
432
  align-items: center;
433
  gap: 4vw;
434
  width: 50%;
435
  font-size: 2.5vw;
436
  font-weight: 800;
437
  color: #006780;
438
  margin-bottom: 20px;
439
  text-align: center;
440
  width: 100%;
441
  height: auto;
442
  border-radius: 10px;
443
  width: 66%;
444
  .description-container p {
445
  font-size: 1.4vw;
446
  margin-bottom: 30px;
447
  text-align: justify;
448
  }
449
  position: relative;
450
  border-radius: 0.5vw;
451
  font-size: 1.5vw;
452
  transition: background-color 0.3s;
453
  width: auto;
454
  font-weight: bold;
455
  background-color: #006780;
456
  border: none;
457
  color: white;
458
  padding: 15px 32px;
459
  text-align: center;
460
  text-decoration: none;
461
  display: inline-flex;
462
  align-items: center;
463
  justify-content: center;
464
  position: relative;
465
  min-width: 5vw;
466
  height: 2.9vw;
467
  margin-top: 1.5vw;
468
  .validate-button:hover {
469
  background-color: #bdc3c7;
470
  box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
471
  }
472
  .validate-button:disabled {
473
  background-color: #ccc;
474
  cursor: not-allowed;
475
  }
476
  position: absolute;
477
  left: 50%;
478
  transform: translateX(-50%);
479
  background: rgba(255, 255, 255, 0.9);
480
  padding: 2vw;
481
  border-radius: 1vw;
482
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
483
  width: 80vw;
484
  position: absolute;
485
  top: 54%;
486
  left: 50%;
487
  transform: translate(-50%, -50%);
488
  height: 70vh;
489
  border: 10px solid var(--main-accent-color);
490
  background-color: #fff;
491
  .card2 .content-container {
492
  display: flex;
493
  justify-content: space-around;
494
  align-items: center;
495
  height: 100%;
496
  }
497
  max-width: 100%;
498
  height: auto;
499
  display: flex;
500
  flex-direction: column;
501
  align-items: center;
502
  justify-content: center;
503
  text-align: center;
504
  width: 50%;
505
  .word-container h2 {
506
  font-size: 2.5rem;
507
  font-weight: bold;
508
  color: #333;
509
  margin-top: 2.7vw;
510
  }
511
  width: 45%;
512
  text-align: center;
513
  padding: 20px;
514
  background: rgba(255, 255, 255, 0.7);
515
  border-radius: 10px;
516
  box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2);
517
  margin-right: 4vw;
518
  font-size: 1.2vw;
519
  margin-bottom: 20px;
520
  font-weight: 700;
521
  display: flex;
522
  flex-direction: column;
523
  align-items: center;
524
  background-color: #ccc;
525
  color: white;
526
  padding: 12px 24px;
527
  border-radius: 5px;
528
  cursor: pointer;
529
  font-size: 1.2rem;
530
  margin: 10px;
531
  width: 80%;
532
  transition: background-color 0.3s ease;
533
  background-color: #ffffff;
534
  color: #333;
535
  border: 2px solid #000000;
536
  font-size: 1.1vw;
537
  border-radius: 3vw;
538
  transition: all 0.3s ease;
539
  font-weight: 600;
540
  display: flex;
541
  align-items: center;
542
  justify-content: center;
543
  box-shadow: -1px 1vw 0.5vw rgb(0 0 0 / 13%);
544
  height: 100%;
545
  text-align: center;
546
  white-space: normal;
547
  word-break: break-word;
548
  width: 21vw;
549
  .option-btn.correct,
550
  .option-btn.correct:hover {
551
  background: #e8f7ed;
552
  border-color: #22c55e;
553
  color: #14532d;
554
  }
555
  .option-btn.incorrect,
556
  .option-btn.incorrect:hover {
557
  background: #fee2e2;
558
  border-color: #ef4444;
559
  color: #7f1d1d;
560
  }
561
  .option-btn.selected,
562
  .option-btn.selected:hover {
563
  background: #e0f2fe;
564
  border-color: #3b82f6;
565
  color: #0c4a6e;
566
  }
567
  .option-btn:not(.selected):not(.correct):not(.incorrect):hover {
568
  background: #f3f4f6;
569
  }
570
  .option-btn.disabled,
571
  .option-btn:disabled {
572
  cursor: not-allowed;
573
  filter: grayscale(10%);
574
  }
575
  .option-btn.picked {
576
  outline: 2px dashed rgba(0,0,0,0.25);
577
  outline-offset: 2px;
578
  }
579
  margin-left: 8px;
580
  padding: 2px 8px;
581
  border-radius: 12px;
582
  font-size: 12px;
583
  background: rgba(0,0,0,0.06);
584
  background: rgba(255, 255, 255, 0.9);
585
  padding: 2vw;
586
  border-radius: 1vw;
587
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
588
  width: 80vw;
589
  position: absolute;
590
  top: 54%;
591
  left: 50%;
592
  transform: translate(-50%, -50%);
593
  height: 70vh;
594
  border: 10px solid var(--main-accent-color);
595
  background-color: #fff;
596
  list-style-type: disc;
597
  padding: 0 5vw;
598
  color: #000000;
599
  overflow-y: auto;
600
  height: 18vw;
601
  font-size: 1.5vw;
602
  margin-bottom: 30px;
603
  text-align: justify;
604
  .feedback-list1 li {
605
  margin-bottom: 10px;
606
  line-height: 1.5;
607
  display: list-item;
608
  }
609
  list-style-type: none !important;
610
  text-align: justify;
611
  height: 20vw;
612
  overflow-y: auto;
613
  padding: 0 3vw;
614
  margin-bottom: 2vw;
615
  font-size: 1.5vw;
616
  .feedback-list li {
617
  margin-bottom: 1vw;
618
  font-weight: 500;
619
  }
620
  color: green;
621
  color: red;
622
  background: rgba(255, 255, 255, 0.9);
623
  padding: 2vw;
624
  border-radius: 1vw;
625
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
626
  width: 80vw;
627
  position: absolute;
628
  top: 54%;
629
  left: 50%;
630
  transform: translate(-50%, -50%);
631
  height: 70vh;
632
  border: 10px solid var(--main-accent-color);
633
  background-color: #fff;
634
  resize: none
635
  font-size: 1.8vw;
636
  text-align: center;
637
  margin-bottom: 20px;
638
  color: #006780;
639
  font-weight: 600;
640
  display: flex;
641
  flex-direction: column;
642
  align-items: center;
643
  width: 100%;
644
  display: flex;
645
  justify-content: space-between;
646
  align-items: center;
647
  width: 80%;
648
  max-width: 600px;
649
  margin-bottom: 20px;
650
  .card4 .sentence-input-group label {
651
  font-weight: bold;
652
  text-align: left;
653
  width: 30%;
654
  font-size: 1.7vw;
655
  }
656
  .card4 .sentence-input-group .input-group {
657
  width: 60%;
658
  }
659
  .card4 .sentence-input-group textarea {
660
  width: 138%;
661
  height: 100%;
662
  font-size: 1.4rem;
663
  border: 2px solid #006780;
664
  font-family: Arial, sans-serif;
665
  border-radius: 8px;
666
  padding: 0.4vw;
667
  }
668
  .card4 .sentence-input-group textarea:focus {
669
  outline: none;
670
  border: 1px solid #007bff;
671
  }
672
  background: rgba(255, 255, 255, 0.9);
673
  padding: 2vw;
674
  border-radius: 1vw;
675
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
676
  width: 80vw;
677
  position: absolute;
678
  top: 54%;
679
  left: 50%;
680
  transform: translate(-50%, -50%);
681
  height: 70vh;
682
  border: 10px solid var(--main-accent-color);
683
  background-color: #fff;
684
  .card5 .feedback-text {
685
  font-size: 1.8vw;
686
  font-weight: 600;
687
  color: #333;
688
  margin-top: 1vw;
689
  word-wrap: break-word;
690
  max-height: 400px;
691
  overflow-y: auto;
692
  padding-left: 20px;
693
  padding-right: 20px;
694
  text-align: justify;
695
  margin-bottom: 1vw;
696
  }
697
  position: absolute;
698
  top: 1vw;
699
  left: 2vw;
700
  color: #007bff;
701
  font-size: 24px;
702
  width: 5vw;
703
  cursor: pointer;
704
  transition: transform 0.2s ease-in-out;
705
  .back-icon:hover {
706
  transform: scale(1.1);
707
  color: #0056b3;
708
  }
709
  .back-icon img {
710
  width: 30px;
711
  height: auto;
712
  cursor: pointer;
713
  }
714
  top: -1vw;
715
  right: -1.4vw;
716
  .icon-btn1 {
717
  width: 50px;
718
  height: 50px;
719
  font-size: 20px;
720
  }
721
  position: fixed;
722
  top: 0;
723
  left: 0;
724
  width: 100%;
725
  height: 100%;
726
  display: flex;
727
  justify-content: center;
728
  align-items: center;
729
  z-index: 1000;
730
  font-size: 15px;
731
  width: 1.5em;
732
  height: 1.5em;
733
  border-radius: 50%;
734
  position: relative;
735
  text-indent: -9999em;
736
  animation: mulShdSpin 1.1s infinite ease;
737
  transform: translateZ(0);
738
  0%, 100% {
739
  box-shadow: 0em -3em 0em 0em #808080, 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.5), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.7);
740
  }
741
  12.5% {
742
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.7), 2.2em -2.2em 0 0em #808080, 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.5);
743
  }
744
  25% {
745
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.5), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.7), 3em 0em 0 0em #808080, 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
746
  }
747
  37.5% {
748
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.5), 3em 0em 0 0em rgba(128, 128, 128, 0.7), 2.2em 2.2em 0 0em #808080, 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
749
  }
750
  50% {
751
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.5), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.7), 0em 3em 0 0em #808080, -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
752
  }
753
  62.5% {
754
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.5), 0em 3em 0 0em rgba(128, 128, 128, 0.7), -2.2em 2.2em 0 0em #808080, -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
755
  }
756
  75% {
757
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.5), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.7), -3em 0em 0 0em #808080, -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
758
  }
759
  87.5% {
760
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.5), -3em 0em 0 0em rgba(128, 128, 128, 0.7), -2.2em -2.2em 0 0em #808080;
761
  }
 
 
1
  margin-top: 1rem;
2
  padding: 1rem;
3
  background-color: #f0f4f8;
4
  border-radius: 8px;
5
  border: 1px solid #cdd5df;
6
  background: rgba(255, 255, 255, 0.9);
7
  width: 80vw;
8
  position: absolute;
9
  top: 50%;
10
  left: 50%;
11
  transform: translate(-50%, -50%);
12
  height: 66vh;
13
  display: flex;
14
  justify-content: space-between;
15
  align-items: flex-start;
16
  padding: 4vw;
17
  gap: 2vw;
18
  background-color: #fff;
19
  border: 10px solid #009688;
20
  border-radius: 1vw;
21
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
22
  margin: 2vw auto;
23
  max-width: 90%;
24
  display: flex;
25
  align-items: center;
26
  gap: 4vw;
27
  width: 50%;
28
  font-size: 2.5vw;
29
  font-weight: 800;
30
  color: #006780;
31
  margin-bottom: 20px;
32
  text-align: center;
33
  width: 100%;
34
  height: auto;
35
  border-radius: 10px;
36
  width: 66%;
37
  .description-container p {
38
  font-size: 1.4vw;
39
  margin-bottom: 30px;
40
  text-align: justify;
41
  }
42
  position: relative;
43
  border-radius: 0.5vw;
44
  font-size: 1.5vw;
45
  transition: background-color 0.3s;
46
  width: auto;
47
  font-weight: bold;
48
  background-color: #006780;
49
  border: none;
50
  color: white;
51
  padding: 15px 32px;
52
  text-align: center;
53
  text-decoration: none;
54
  display: inline-flex;
55
  align-items: center;
56
  justify-content: center;
57
  position: relative;
58
  min-width: 5vw;
59
  height: 2.9vw;
60
  margin-top: 1.5vw;
61
  .validate-button:hover {
62
  background-color: #bdc3c7;
63
  box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
64
  }
65
  .validate-button:disabled {
66
  background-color: #ccc;
67
  cursor: not-allowed;
68
  }
69
  position: absolute;
70
  border-radius: 0.5vw;
71
  font-size: 1.5vw;
72
  transition: background-color 0.3s;
73
  font-weight: bold;
74
  background-color: #006780;
75
  border: none;
76
  color: white;
77
  left: 50%;
78
  text-decoration: none;
79
  transform: translateX(-50%);
80
  padding: 0.5vw;
81
  .submit-button:hover {
82
  background-color: #bdc3c7;
83
  box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
84
  }
85
  .submit-button:disabled {
86
  background-color: #ccc;
87
  cursor: not-allowed;
88
  }
89
  background: rgba(255, 255, 255, 0.9);
90
  padding: 2vw;
91
  border-radius: 1vw;
92
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
93
  width: 80vw;
94
  position: absolute;
95
  top: 54%;
96
  left: 50%;
97
  transform: translate(-50%, -50%);
98
  height: 70vh;
99
  border: 10px solid #009688;
100
  background-color: #fff;
101
  .card2 .content-container {
102
  display: flex;
103
  justify-content: space-around;
104
  align-items: center;
105
  height: 100%;
106
  }
107
  max-width: 100%;
108
  height: auto;
109
  display: flex;
110
  flex-direction: column;
111
  align-items: center;
112
  justify-content: center;
113
  text-align: center;
114
  width: 50%;
115
  .word-container h2 {
116
  font-size: 2.5rem;
117
  font-weight: bold;
118
  color: #333;
119
  margin-top: 2.7vw;
120
  }
121
  width: 45%;
122
  text-align: center;
123
  padding: 20px;
124
  background: rgba(255, 255, 255, 0.7);
125
  border-radius: 10px;
126
  box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2);
127
  margin-right: 4vw;
128
  font-size: 1.2vw;
129
  margin-bottom: 20px;
130
  font-weight: 700;
131
  display: flex;
132
  flex-direction: column;
133
  align-items: center;
134
  background-color: #ccc;
135
  color: white;
136
  padding: 12px 24px;
137
  border-radius: 5px;
138
  cursor: pointer;
139
  font-size: 1.2rem;
140
  margin: 10px;
141
  width: 80%;
142
  transition: background-color 0.3s ease;
143
  background-color: #ffffff;
144
  color: #333;
145
  border: 2px solid #000000;
146
  font-size: 1.1vw;
147
  border-radius: 3vw;
148
  transition: all 0.3s ease;
149
  font-weight: 600;
150
  display: flex;
151
  align-items: center;
152
  justify-content: center;
153
  box-shadow: -1px 1vw 0.5vw rgb(0 0 0 / 13%);
154
  height: 100%;
155
  text-align: center;
156
  white-space: normal;
157
  word-break: break-word;
158
  width: 21vw;
159
  /* Make state classes override :hover */
160
  .option-btn.correct,
161
  .option-btn.incorrect,
162
  .option-btn.selected {
163
  position: relative; /* keeps layout stable */
164
  }
165
  /* Correct = green (state wins even on hover) */
166
  .option-btn.correct,
167
  .option-btn.correct:hover {
168
  background: #e8f7ed; /* soft green */
169
  border-color: #22c55e; /* green-500 */
170
  color: #14532d; /* dark green text */
171
  }
172
  /* Incorrect = red (state wins even on hover) */
173
  .option-btn.incorrect,
174
  .option-btn.incorrect:hover {
175
  background: #fee2e2; /* soft red */
176
  border-color: #ef4444; /* red-500 */
177
  color: #7f1d1d; /* dark red text */
178
  }
179
  /* Selected (pre-validation) = blue (state wins even on hover) */
180
  .option-btn.selected,
181
  .option-btn.selected:hover {
182
  background: #e0f2fe; /* soft blue */
183
  border-color: #3b82f6; /* blue-500 */
184
  color: #0c4a6e; /* dark blue text */
185
  }
186
  /* Default hover only when no state class is present */
187
  .option-btn:not(.selected):not(.correct):not(.incorrect):hover {
188
  background: #f3f4f6; /* light grey hover */
189
  }
190
  /* Optional: disabled look still visible */
191
  .option-btn.disabled,
192
  .option-btn:disabled {
193
  cursor: not-allowed;
194
  filter: grayscale(10%);
195
  }
196
  .option-btn.picked {
197
  outline: 2px dashed rgba(0,0,0,0.25);
198
  outline-offset: 2px;
199
  }
200
  margin-left: 8px;
201
  padding: 2px 8px;
202
  border-radius: 12px;
203
  font-size: 12px;
204
  background: rgba(0,0,0,0.06);
205
  background: rgba(255, 255, 255, 0.9);
206
  padding: 2vw;
207
  border-radius: 1vw;
208
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
209
  width: 80vw;
210
  position: absolute;
211
  top: 54%;
212
  left: 50%;
213
  transform: translate(-50%, -50%);
214
  height: 70vh;
215
  border: 10px solid #009688;
216
  background-color: #fff;
217
  list-style-type: disc;
218
  padding: 0 5vw;
219
  color: #000000;
220
  overflow-y: auto;
221
  height: 18vw;
222
  font-size: 1.5vw;
223
  margin-bottom: 30px;
224
  text-align: justify;
225
  .feedback-list1 li {
226
  margin-bottom: 10px;
227
  line-height: 1.5;
228
  display: list-item;
229
  }
230
  list-style-type: none !important;
231
  text-align: justify;
232
  height: 20vw;
233
  overflow-y: auto;
234
  padding: 0 3vw;
235
  margin-bottom: 2vw;
236
  font-size: 1.5vw;
237
  .feedback-list li {
238
  margin-bottom: 1vw;
239
  font-weight: 500;
240
  }
241
  color: green;
242
  color: red;
243
  0% {
244
  transform: translate(-50%, -50%) rotate(0deg);
245
  }
246
  100% {
247
  transform: translate(-50%, -50%) rotate(360deg);
248
  }
249
  background: rgba(255, 255, 255, 0.9);
250
  padding: 2vw;
251
  border-radius: 1vw;
252
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
253
  width: 80vw;
254
  position: absolute;
255
  top: 54%;
256
  left: 50%;
257
  transform: translate(-50%, -50%);
258
  height: 70vh;
259
  border: 10px solid #009688;
260
  background-color: #fff;
261
  resize: none
262
  font-size: 1.8vw;
263
  text-align: center;
264
  margin-bottom: 20px;
265
  color: #006780;
266
  font-weight: 600;
267
  display: flex;
268
  flex-direction: column;
269
  align-items: center;
270
  width: 100%;
271
  display: flex;
272
  justify-content: space-between;
273
  align-items: center;
274
  width: 80%;
275
  max-width: 600px;
276
  margin-bottom: 20px;
277
  .card4 .sentence-input-group label {
278
  font-weight: bold;
279
  text-align: left;
280
  width: 30%;
281
  font-size: 1.7vw;
282
  }
283
  .card4 .sentence-input-group .input-group {
284
  width: 60%;
285
  }
286
  .card4 .sentence-input-group textarea {
287
  width: 138%;
288
  height: 100%;
289
  font-size: 1.4rem;
290
  border: 2px solid #006780;
291
  font-family: Arial, sans-serif;
292
  border-radius: 8px;
293
  padding: 0.4vw;
294
  }
295
  .card4 .sentence-input-group textarea:focus {
296
  outline: none;
297
  border: 1px solid #007bff;
298
  }
299
  background: rgba(255, 255, 255, 0.9);
300
  padding: 2vw;
301
  border-radius: 1vw;
302
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
303
  width: 80vw;
304
  position: absolute;
305
  top: 54%;
306
  left: 50%;
307
  transform: translate(-50%, -50%);
308
  height: 70vh;
309
  border: 10px solid #009688;
310
  background-color: #fff;
311
  .card5 .feedback-text {
312
  font-size: 1.8vw;
313
  font-weight: 600;
314
  color: #333;
315
  margin-top: 1vw;
316
  word-wrap: break-word;
317
  max-height: 400px;
318
  overflow-y: auto;
319
  padding-left: 20px;
320
  padding-right: 20px;
321
  text-align: justify;
322
  margin-bottom: 1vw;
323
  }
324
  position: absolute;
325
  top: 1vw;
326
  left: 2vw;
327
  color: #007bff;
328
  font-size: 24px;
329
  width: 5vw;
330
  cursor: pointer;
331
  transition: transform 0.2s ease-in-out;
332
  .back-icon:hover {
333
  transform: scale(1.1);
334
  color: #0056b3;
335
  }
336
  .back-icon img {
337
  width: 30px;
338
  height: auto;
339
  cursor: pointer;
340
  }
341
  position: absolute;
342
  top: -1vw;
343
  right: -1.4vw;
344
  width: 36px;
345
  height: 36px;
346
  display: grid;
347
  place-items: center;
348
  border-radius: 50%;
349
  background: #009688;
350
  color: #0f172a;
351
  border: 4px solid #009688;
352
  font-size: 18px;
353
  font-weight: 800;
354
  cursor: pointer;
355
  box-shadow: 0 4px 10px rgba(0, 0, 0, .15);
356
  transition: transform .15s ease, box-shadow .15s ease, background .15s ease;
357
  z-index: 10;
358
  .icon-btn1:hover {
359
  transform: scale(1.06);
360
  box-shadow: 0 6px 14px rgba(0,0,0,.22);
361
  background: #f7fafc;
362
  }
363
  .icon-btn1:active {
364
  transform: scale(0.98);
365
  }
366
  .icon-btn1 {
367
  width: 50px;
368
  height: 50px;
369
  font-size: 20px;
370
  }
371
  position: fixed;
372
  top: 0;
373
  left: 0;
374
  width: 100%;
375
  height: 100%;
376
  display: flex;
377
  justify-content: center;
378
  align-items: center;
379
  z-index: 1000;
380
  font-size: 15px;
381
  width: 1.5em;
382
  height: 1.5em;
383
  border-radius: 50%;
384
  position: relative;
385
  text-indent: -9999em;
386
  animation: mulShdSpin 1.1s infinite ease;
387
  transform: translateZ(0);
388
  0%, 100% {
389
  box-shadow: 0em -3em 0em 0em #808080, 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.5), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.7);
390
  }
391
  12.5% {
392
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.7), 2.2em -2.2em 0 0em #808080, 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.5);
393
  }
394
  25% {
395
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.5), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.7), 3em 0em 0 0em #808080, 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
396
  }
397
  37.5% {
398
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.5), 3em 0em 0 0em rgba(128, 128, 128, 0.7), 2.2em 2.2em 0 0em #808080, 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
399
  }
400
  50% {
401
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.5), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.7), 0em 3em 0 0em #808080, -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
402
  }
403
  62.5% {
404
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.5), 0em 3em 0 0em rgba(128, 128, 128, 0.7), -2.2em 2.2em 0 0em #808080, -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
405
  }
406
  75% {
407
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.5), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.7), -3em 0em 0 0em #808080, -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
408
  }
409
  87.5% {
410
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.5), -3em 0em 0 0em rgba(128, 128, 128, 0.7), -2.2em -2.2em 0 0em #808080;
411
  }
412
+ .card1 {
413
  background: rgba(255, 255, 255, 0.9);
414
  width: 80vw;
415
  position: absolute;
416
  top: 50%;
417
  left: 50%;
418
  transform: translate(-50%, -50%);
419
  height: 66vh;
420
  display: flex;
421
  justify-content: space-between;
422
  align-items: flex-start;
423
  padding: 4vw;
424
  gap: 2vw;
425
  background-color: #fff;
426
  border: 10px solid var(--main-accent-color);
427
  border-radius: 1vw;
428
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
429
  margin: 2vw auto;
430
  max-width: 90%;
431
  display: flex;
432
  align-items: center;
433
  gap: 4vw;
434
  width: 50%;
435
  font-size: 2.5vw;
436
  font-weight: 800;
437
  color: #006780;
438
  margin-bottom: 20px;
439
  text-align: center;
440
  width: 100%;
441
  height: auto;
442
  border-radius: 10px;
443
  width: 66%;
444
  .description-container p {
445
  font-size: 1.4vw;
446
  margin-bottom: 30px;
447
  text-align: justify;
448
  }
449
  position: relative;
450
  border-radius: 0.5vw;
451
  font-size: 1.5vw;
452
  transition: background-color 0.3s;
453
  width: auto;
454
  font-weight: bold;
455
  background-color: #006780;
456
  border: none;
457
  color: white;
458
  padding: 15px 32px;
459
  text-align: center;
460
  text-decoration: none;
461
  display: inline-flex;
462
  align-items: center;
463
  justify-content: center;
464
  position: relative;
465
  min-width: 5vw;
466
  height: 2.9vw;
467
  margin-top: 1.5vw;
468
  .validate-button:hover {
469
  background-color: #bdc3c7;
470
  box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
471
  }
472
  .validate-button:disabled {
473
  background-color: #ccc;
474
  cursor: not-allowed;
475
  }
476
  position: absolute;
477
  left: 50%;
478
  transform: translateX(-50%);
479
  background: rgba(255, 255, 255, 0.9);
480
  padding: 2vw;
481
  border-radius: 1vw;
482
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
483
  width: 80vw;
484
  position: absolute;
485
  top: 54%;
486
  left: 50%;
487
  transform: translate(-50%, -50%);
488
  height: 70vh;
489
  border: 10px solid var(--main-accent-color);
490
  background-color: #fff;
491
  .card2 .content-container {
492
  display: flex;
493
  justify-content: space-around;
494
  align-items: center;
495
  height: 100%;
496
  }
497
  max-width: 100%;
498
  height: auto;
499
  display: flex;
500
  flex-direction: column;
501
  align-items: center;
502
  justify-content: center;
503
  text-align: center;
504
  width: 50%;
505
  .word-container h2 {
506
  font-size: 2.5rem;
507
  font-weight: bold;
508
  color: #333;
509
  margin-top: 2.7vw;
510
  }
511
  width: 45%;
512
  text-align: center;
513
  padding: 20px;
514
  background: rgba(255, 255, 255, 0.7);
515
  border-radius: 10px;
516
  box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2);
517
  margin-right: 4vw;
518
  font-size: 1.2vw;
519
  margin-bottom: 20px;
520
  font-weight: 700;
521
  display: flex;
522
  flex-direction: column;
523
  align-items: center;
524
  background-color: #ccc;
525
  color: white;
526
  padding: 12px 24px;
527
  border-radius: 5px;
528
  cursor: pointer;
529
  font-size: 1.2rem;
530
  margin: 10px;
531
  width: 80%;
532
  transition: background-color 0.3s ease;
533
  background-color: #ffffff;
534
  color: #333;
535
  border: 2px solid #000000;
536
  font-size: 1.1vw;
537
  border-radius: 3vw;
538
  transition: all 0.3s ease;
539
  font-weight: 600;
540
  display: flex;
541
  align-items: center;
542
  justify-content: center;
543
  box-shadow: -1px 1vw 0.5vw rgb(0 0 0 / 13%);
544
  height: 100%;
545
  text-align: center;
546
  white-space: normal;
547
  word-break: break-word;
548
  width: 21vw;
549
  .option-btn.correct,
550
  .option-btn.correct:hover {
551
  background: #e8f7ed;
552
  border-color: #22c55e;
553
  color: #14532d;
554
  }
555
  .option-btn.incorrect,
556
  .option-btn.incorrect:hover {
557
  background: #fee2e2;
558
  border-color: #ef4444;
559
  color: #7f1d1d;
560
  }
561
  .option-btn.selected,
562
  .option-btn.selected:hover {
563
  background: #e0f2fe;
564
  border-color: #3b82f6;
565
  color: #0c4a6e;
566
  }
567
  .option-btn:not(.selected):not(.correct):not(.incorrect):hover {
568
  background: #f3f4f6;
569
  }
570
  .option-btn.disabled,
571
  .option-btn:disabled {
572
  cursor: not-allowed;
573
  filter: grayscale(10%);
574
  }
575
  .option-btn.picked {
576
  outline: 2px dashed rgba(0,0,0,0.25);
577
  outline-offset: 2px;
578
  }
579
  margin-left: 8px;
580
  padding: 2px 8px;
581
  border-radius: 12px;
582
  font-size: 12px;
583
  background: rgba(0,0,0,0.06);
584
  background: rgba(255, 255, 255, 0.9);
585
  padding: 2vw;
586
  border-radius: 1vw;
587
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
588
  width: 80vw;
589
  position: absolute;
590
  top: 54%;
591
  left: 50%;
592
  transform: translate(-50%, -50%);
593
  height: 70vh;
594
  border: 10px solid var(--main-accent-color);
595
  background-color: #fff;
596
  list-style-type: disc;
597
  padding: 0 5vw;
598
  color: #000000;
599
  overflow-y: auto;
600
  height: 18vw;
601
  font-size: 1.5vw;
602
  margin-bottom: 30px;
603
  text-align: justify;
604
  .feedback-list1 li {
605
  margin-bottom: 10px;
606
  line-height: 1.5;
607
  display: list-item;
608
  }
609
  list-style-type: none !important;
610
  text-align: justify;
611
  height: 20vw;
612
  overflow-y: auto;
613
  padding: 0 3vw;
614
  margin-bottom: 2vw;
615
  font-size: 1.5vw;
616
  .feedback-list li {
617
  margin-bottom: 1vw;
618
  font-weight: 500;
619
  }
620
  color: green;
621
  color: red;
622
  background: rgba(255, 255, 255, 0.9);
623
  padding: 2vw;
624
  border-radius: 1vw;
625
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
626
  width: 80vw;
627
  position: absolute;
628
  top: 54%;
629
  left: 50%;
630
  transform: translate(-50%, -50%);
631
  height: 70vh;
632
  border: 10px solid var(--main-accent-color);
633
  background-color: #fff;
634
  resize: none
635
  font-size: 1.8vw;
636
  text-align: center;
637
  margin-bottom: 20px;
638
  color: #006780;
639
  font-weight: 600;
640
  display: flex;
641
  flex-direction: column;
642
  align-items: center;
643
  width: 100%;
644
  display: flex;
645
  justify-content: space-between;
646
  align-items: center;
647
  width: 80%;
648
  max-width: 600px;
649
  margin-bottom: 20px;
650
  .card4 .sentence-input-group label {
651
  font-weight: bold;
652
  text-align: left;
653
  width: 30%;
654
  font-size: 1.7vw;
655
  }
656
  .card4 .sentence-input-group .input-group {
657
  width: 60%;
658
  }
659
  .card4 .sentence-input-group textarea {
660
  width: 138%;
661
  height: 100%;
662
  font-size: 1.4rem;
663
  border: 2px solid #006780;
664
  font-family: Arial, sans-serif;
665
  border-radius: 8px;
666
  padding: 0.4vw;
667
  }
668
  .card4 .sentence-input-group textarea:focus {
669
  outline: none;
670
  border: 1px solid #007bff;
671
  }
672
  background: rgba(255, 255, 255, 0.9);
673
  padding: 2vw;
674
  border-radius: 1vw;
675
  box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
676
  width: 80vw;
677
  position: absolute;
678
  top: 54%;
679
  left: 50%;
680
  transform: translate(-50%, -50%);
681
  height: 70vh;
682
  border: 10px solid var(--main-accent-color);
683
  background-color: #fff;
684
  .card5 .feedback-text {
685
  font-size: 1.8vw;
686
  font-weight: 600;
687
  color: #333;
688
  margin-top: 1vw;
689
  word-wrap: break-word;
690
  max-height: 400px;
691
  overflow-y: auto;
692
  padding-left: 20px;
693
  padding-right: 20px;
694
  text-align: justify;
695
  margin-bottom: 1vw;
696
  }
697
  position: absolute;
698
  top: 1vw;
699
  left: 2vw;
700
  color: #007bff;
701
  font-size: 24px;
702
  width: 5vw;
703
  cursor: pointer;
704
  transition: transform 0.2s ease-in-out;
705
  .back-icon:hover {
706
  transform: scale(1.1);
707
  color: #0056b3;
708
  }
709
  .back-icon img {
710
  width: 30px;
711
  height: auto;
712
  cursor: pointer;
713
  }
714
  top: -1vw;
715
  right: -1.4vw;
716
  .icon-btn1 {
717
  width: 50px;
718
  height: 50px;
719
  font-size: 20px;
720
  }
721
  position: fixed;
722
  top: 0;
723
  left: 0;
724
  width: 100%;
725
  height: 100%;
726
  display: flex;
727
  justify-content: center;
728
  align-items: center;
729
  z-index: 1000;
730
  font-size: 15px;
731
  width: 1.5em;
732
  height: 1.5em;
733
  border-radius: 50%;
734
  position: relative;
735
  text-indent: -9999em;
736
  animation: mulShdSpin 1.1s infinite ease;
737
  transform: translateZ(0);
738
  0%, 100% {
739
  box-shadow: 0em -3em 0em 0em #808080, 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.5), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.7);
740
  }
741
  12.5% {
742
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.7), 2.2em -2.2em 0 0em #808080, 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.5);
743
  }
744
  25% {
745
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.5), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.7), 3em 0em 0 0em #808080, 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
746
  }
747
  37.5% {
748
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.5), 3em 0em 0 0em rgba(128, 128, 128, 0.7), 2.2em 2.2em 0 0em #808080, 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
749
  }
750
  50% {
751
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.5), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.7), 0em 3em 0 0em #808080, -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
752
  }
753
  62.5% {
754
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.5), 0em 3em 0 0em rgba(128, 128, 128, 0.7), -2.2em 2.2em 0 0em #808080, -3em 0em 0 0em rgba(128, 128, 128, 0.2), -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
755
  }
756
  75% {
757
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.5), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.7), -3em 0em 0 0em #808080, -2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2);
758
  }
759
  87.5% {
760
  box-shadow: 0em -3em 0em 0em rgba(128, 128, 128, 0.2), 2.2em -2.2em 0 0em rgba(128, 128, 128, 0.2), 3em 0em 0 0em rgba(128, 128, 128, 0.2), 2.2em 2.2em 0 0em rgba(128, 128, 128, 0.2), 0em 3em 0 0em rgba(128, 128, 128, 0.2), -2.2em 2.2em 0 0em rgba(128, 128, 128, 0.5), -3em 0em 0 0em rgba(128, 128, 128, 0.7), -2.2em -2.2em 0 0em #808080;
761
  }
src/app/vocabulary-builder/vocabulary-builder.component.html CHANGED
@@ -3,7 +3,6 @@
3
 
4
  <app-header [title]="'Vocabulary Builder'"></app-header>
5
 
6
-
7
  <!-- Background Image -->
8
  <img src="assets/images/grammar-bg.png" alt="Chat Background" class="grammar-bg" />
9
 
@@ -19,12 +18,11 @@
19
  Vocabulary Builder is an interactive learning tool designed to help users strengthen their vocabulary in an engaging and structured manner.
20
  It presents a series of word-based exercises that allow users to learn the meaning and usage of words through intelligent suggestions, related word selections, and sentence formation tasks.
21
  </p>
22
- <button class="submit-button"
23
- (click)="getWords()"
24
- [disabled]="isButtonDisabled || isLoaderVisible"
25
- [class.loading]="isLoaderVisible">
26
  <span>Let's Build</span>
27
- </button>
28
  <div *ngIf="isLoaderVisible" class="loader-overlay">
29
  <div class="loader"></div>
30
  </div>
@@ -35,8 +33,9 @@
35
  <!-- Step 2: Word Options -->
36
  <div *ngIf="step === 2" class="card2">
37
  <img src="assets/images/vocabulary/back.png" alt="Go Back" class="back-icon" (click)="goBack()" />
38
- <!-- Top-right close button (NOT on intro/page 1) -->
39
- <button class="icon-btn1" (click)="closeToMain()" aria-label="Close">✖</button>
 
40
 
41
 
42
  <div class="content-container">
@@ -56,31 +55,27 @@
56
  'correct': optionStates[option] === 'correct',
57
  'incorrect': optionStates[option] === 'incorrect',
58
  'picked': isChecked && pickedAtCheck.has(option)
59
-
60
  }"
61
  [disabled]="(selectedOptions.length === 3 && !selectedOptions.includes(option) && buttonState === 'validate') || buttonState !== 'validate'"
62
  (click)="handleOptionClick(option)">
63
  <span class="label">{{ option }}</span>
64
  <span *ngIf="isChecked && pickedAtCheck?.has(option)" class="picked-chip"></span>
65
  </button>
66
-
67
-
68
  </div>
69
 
70
- <button *ngIf="buttonState === 'validate'"
71
- class="validate-button"
72
- [disabled]="!isCheckButtonEnabled || isLoaderVisible"
73
- (click)="checkSelections()">
74
  <span>Validate</span>
75
- </button>
76
 
77
- <button *ngIf="buttonState === 'next'" class="validate-button" (click)="goNext()">
78
  <span>Next</span>
79
- </button>
80
 
81
- <button *ngIf="buttonState === 'formSentence'" class="submit-button" (click)="startSentenceFormation()">
82
  <span>Form a Sentence</span>
83
- </button>
84
 
85
  <div *ngIf="isLoaderVisible" class="loader-overlay">
86
  <div class="loader"></div>
@@ -93,7 +88,8 @@
93
  <div *ngIf="step === 3" class="card3">
94
  <img src="assets/images/vocabulary/back.png" alt="Go Back" class="back-icon" (click)="goBack()" />
95
  <!-- Top-right close button (NOT on intro/page 1) -->
96
- <button class="icon-btn1" (click)="closeToMain()" aria-label="Close">✖</button>
 
97
 
98
  <h2>Feedback</h2>
99
  <ul class="feedback-list">
@@ -101,14 +97,14 @@
101
  <li *ngIf="point.includes('❌')">{{ point }}</li>
102
  </ng-container>
103
  </ul>
104
- <button class="submit-button" (click)="startSentenceFormation()">Form a Sentence</button>
105
  </div>
106
 
107
  <!-- Step 4: Sentence Formation -->
108
  <div *ngIf="step === 4" class="card4">
109
  <img src="assets/images/vocabulary/back.png" alt="Go Back" class="back-icon" (click)="goBack()" />
110
  <!-- Top-right close button (NOT on intro/page 1) -->
111
- <button class="icon-btn1" (click)="closeToMain()" aria-label="Close">✖</button>
112
 
113
  <h2>Form a Sentence</h2>
114
  <p class="correct-answer">Using the words: <strong class="text-green-500">{{ correctOptions.join(', ') }}</strong></p>
@@ -124,11 +120,11 @@
124
  </div>
125
  </div>
126
  </div>
127
- <button class="submit-button"
128
- [disabled]="!allFieldsFilled() || isLoaderVisible"
129
- (click)="checkSentence()">
130
  <span>Check Sentence</span>
131
- </button>
132
  <div *ngIf="isLoaderVisible" class="loader-overlay">
133
  <div class="loader"></div>
134
  </div>
@@ -138,7 +134,7 @@
138
  <div *ngIf="step === 5" class="card5">
139
  <img src="assets/images/vocabulary/back.png" alt="Go Back" class="back-icon" (click)="goBack()" />
140
  <!-- Top-right close button (NOT on intro/page 1) -->
141
- <button class="icon-btn1" (click)="closeToMain()" aria-label="Close">✖</button>
142
 
143
  <h2 class="feedback-header">Sentence Feedback</h2>
144
  <ul class="feedback-list1">
@@ -149,7 +145,7 @@
149
  <span *ngIf="!point.includes('correct answer')">{{ point }}</span>
150
  </li>
151
  </ul>
152
- <button class="submit-button" (click)="resetQuiz()"> Reset </button>
153
  </div>
154
 
155
  </div>
 
3
 
4
  <app-header [title]="'Vocabulary Builder'"></app-header>
5
 
 
6
  <!-- Background Image -->
7
  <img src="assets/images/grammar-bg.png" alt="Chat Background" class="grammar-bg" />
8
 
 
18
  Vocabulary Builder is an interactive learning tool designed to help users strengthen their vocabulary in an engaging and structured manner.
19
  It presents a series of word-based exercises that allow users to learn the meaning and usage of words through intelligent suggestions, related word selections, and sentence formation tasks.
20
  </p>
21
+ <app-button [disabled]="isButtonDisabled || isLoaderVisible"
22
+ [ngClass]="{ loading: isLoaderVisible }"
23
+ (click)="getWords()">
 
24
  <span>Let's Build</span>
25
+ </app-button>
26
  <div *ngIf="isLoaderVisible" class="loader-overlay">
27
  <div class="loader"></div>
28
  </div>
 
33
  <!-- Step 2: Word Options -->
34
  <div *ngIf="step === 2" class="card2">
35
  <img src="assets/images/vocabulary/back.png" alt="Go Back" class="back-icon" (click)="goBack()" />
36
+
37
+ <button class="user-guide-close-icon" (click)="closeToMain()">×</button>
38
+
39
 
40
 
41
  <div class="content-container">
 
55
  'correct': optionStates[option] === 'correct',
56
  'incorrect': optionStates[option] === 'incorrect',
57
  'picked': isChecked && pickedAtCheck.has(option)
 
58
  }"
59
  [disabled]="(selectedOptions.length === 3 && !selectedOptions.includes(option) && buttonState === 'validate') || buttonState !== 'validate'"
60
  (click)="handleOptionClick(option)">
61
  <span class="label">{{ option }}</span>
62
  <span *ngIf="isChecked && pickedAtCheck?.has(option)" class="picked-chip"></span>
63
  </button>
 
 
64
  </div>
65
 
66
+ <app-button *ngIf="buttonState === 'validate'"
67
+ [disabled]="!isCheckButtonEnabled || isLoaderVisible"
68
+ (click)="checkSelections()">
 
69
  <span>Validate</span>
70
+ </app-button>
71
 
72
+ <app-button *ngIf="buttonState === 'next'" (click)="goNext()">
73
  <span>Next</span>
74
+ </app-button>
75
 
76
+ <app-button *ngIf="buttonState === 'formSentence'" (click)="startSentenceFormation()">
77
  <span>Form a Sentence</span>
78
+ </app-button>
79
 
80
  <div *ngIf="isLoaderVisible" class="loader-overlay">
81
  <div class="loader"></div>
 
88
  <div *ngIf="step === 3" class="card3">
89
  <img src="assets/images/vocabulary/back.png" alt="Go Back" class="back-icon" (click)="goBack()" />
90
  <!-- Top-right close button (NOT on intro/page 1) -->
91
+ <!--<button class="icon-btn1" (click)="closeToMain()" aria-label="Close">✖</button>-->
92
+ <button class="user-guide-close-icon" (click)="closeToMain()">×</button>
93
 
94
  <h2>Feedback</h2>
95
  <ul class="feedback-list">
 
97
  <li *ngIf="point.includes('❌')">{{ point }}</li>
98
  </ng-container>
99
  </ul>
100
+ <app-button class="submit-button" (click)="startSentenceFormation()">Form a Sentence</app-button>
101
  </div>
102
 
103
  <!-- Step 4: Sentence Formation -->
104
  <div *ngIf="step === 4" class="card4">
105
  <img src="assets/images/vocabulary/back.png" alt="Go Back" class="back-icon" (click)="goBack()" />
106
  <!-- Top-right close button (NOT on intro/page 1) -->
107
+ <button class="user-guide-close-icon" (click)="closeToMain()">×</button>
108
 
109
  <h2>Form a Sentence</h2>
110
  <p class="correct-answer">Using the words: <strong class="text-green-500">{{ correctOptions.join(', ') }}</strong></p>
 
120
  </div>
121
  </div>
122
  </div>
123
+ <app-button class="submit-button"
124
+ [disabled]="!allFieldsFilled() || isLoaderVisible"
125
+ (click)="checkSentence()">
126
  <span>Check Sentence</span>
127
+ </app-button>
128
  <div *ngIf="isLoaderVisible" class="loader-overlay">
129
  <div class="loader"></div>
130
  </div>
 
134
  <div *ngIf="step === 5" class="card5">
135
  <img src="assets/images/vocabulary/back.png" alt="Go Back" class="back-icon" (click)="goBack()" />
136
  <!-- Top-right close button (NOT on intro/page 1) -->
137
+ <button class="user-guide-close-icon" (click)="closeToMain()">×</button>
138
 
139
  <h2 class="feedback-header">Sentence Feedback</h2>
140
  <ul class="feedback-list1">
 
145
  <span *ngIf="!point.includes('correct answer')">{{ point }}</span>
146
  </li>
147
  </ul>
148
+ <app-button class="submit-button" (click)="resetQuiz()"> Reset </app-button>
149
  </div>
150
 
151
  </div>
src/app/vocabulary-builder/vocabulary-builder.component.ts CHANGED
@@ -1,11 +1,17 @@
1
  import { Component, OnInit, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
2
  import { VocabularyBuilderService } from './vocabulary-builder.service';
3
  import { Router } from '@angular/router';
 
 
 
 
4
 
5
  @Component({
6
  selector: 'app-vocabulary-builder',
7
  templateUrl: './vocabulary-builder.component.html',
8
- styleUrls: ['./vocabulary-builder.component.css']
 
 
9
  })
10
  export class VocabularyBuilderComponent implements OnInit, AfterViewInit {
11
  @ViewChild('step1', { static: false }) step1?: ElementRef;
@@ -87,19 +93,15 @@ export class VocabularyBuilderComponent implements OnInit, AfterViewInit {
87
 
88
  const i = this.selectedOptions.indexOf(option);
89
  if (i >= 0) {
90
- // toggle OFF
91
  this.selectedOptions.splice(i, 1);
92
  } else {
93
- // do NOT auto-deselect the first; just stop at 3
94
  if (this.selectedOptions.length >= 3) return;
95
  this.selectedOptions.push(option);
96
  }
97
 
98
- // Enable Validate only when exactly 3 selected
99
  this.isCheckButtonEnabled = this.selectedOptions.length === 3;
100
  }
101
 
102
-
103
  checkSelections(): void {
104
  if (this.buttonState === 'next') {
105
  this.step = 3;
@@ -117,7 +119,6 @@ export class VocabularyBuilderComponent implements OnInit, AfterViewInit {
117
  this.processFeedback(response.feedback);
118
  this.isChecked = true;
119
  this.correctAnswers = response.correctAnswers || [];
120
- // ⬇️ INSERT THIS LINE HERE
121
  this.pickedAtCheck = new Set(this.selectedOptions);
122
 
123
  this.optionStates = {};
@@ -305,14 +306,9 @@ export class VocabularyBuilderComponent implements OnInit, AfterViewInit {
305
  this.buttonState = 'validate';
306
  this.skipFeedback = false;
307
  this.pickedAtCheck.clear();
308
- // or: this.pickedAtCheck = new Set<string>();
309
-
310
  }
311
 
312
-
313
  closeToMain(): void {
314
- this.resetQuiz(); // your existing method that sets step = 1 and clears state
315
- // If you also want to route away, you can add: this.router.navigate(['/home']);
316
  }
317
-
318
  }
 
1
  import { Component, OnInit, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
2
  import { VocabularyBuilderService } from './vocabulary-builder.service';
3
  import { Router } from '@angular/router';
4
+ import { HeaderComponent } from '../shared/header/header.component';
5
+ import { ButtonComponent } from '../shared/button/button.component';
6
+ import { CommonModule } from '@angular/common';
7
+ import { FormsModule } from '@angular/forms';
8
 
9
  @Component({
10
  selector: 'app-vocabulary-builder',
11
  templateUrl: './vocabulary-builder.component.html',
12
+ styleUrls: ['./vocabulary-builder.component.css'],
13
+ standalone: true,
14
+ imports: [ButtonComponent, CommonModule, FormsModule, HeaderComponent]
15
  })
16
  export class VocabularyBuilderComponent implements OnInit, AfterViewInit {
17
  @ViewChild('step1', { static: false }) step1?: ElementRef;
 
93
 
94
  const i = this.selectedOptions.indexOf(option);
95
  if (i >= 0) {
 
96
  this.selectedOptions.splice(i, 1);
97
  } else {
 
98
  if (this.selectedOptions.length >= 3) return;
99
  this.selectedOptions.push(option);
100
  }
101
 
 
102
  this.isCheckButtonEnabled = this.selectedOptions.length === 3;
103
  }
104
 
 
105
  checkSelections(): void {
106
  if (this.buttonState === 'next') {
107
  this.step = 3;
 
119
  this.processFeedback(response.feedback);
120
  this.isChecked = true;
121
  this.correctAnswers = response.correctAnswers || [];
 
122
  this.pickedAtCheck = new Set(this.selectedOptions);
123
 
124
  this.optionStates = {};
 
306
  this.buttonState = 'validate';
307
  this.skipFeedback = false;
308
  this.pickedAtCheck.clear();
 
 
309
  }
310
 
 
311
  closeToMain(): void {
312
+ this.resetQuiz();
 
313
  }
 
314
  }
src/app/voice/voice.component.ts CHANGED
@@ -1,7 +1,10 @@
1
  import { Component } from '@angular/core';
 
2
 
3
  @Component({
4
  selector: 'app-voice',
 
 
5
  templateUrl: './voice.component.html',
6
  styleUrls: ['./voice.component.css']
7
  })
 
1
  import { Component } from '@angular/core';
2
+ import { HeaderComponent } from '../shared/header/header.component';
3
 
4
  @Component({
5
  selector: 'app-voice',
6
+ standalone: true,
7
+ imports: [HeaderComponent],
8
  templateUrl: './voice.component.html',
9
  styleUrls: ['./voice.component.css']
10
  })
src/app/writing/writing.component.css CHANGED
@@ -1,5 +1,3 @@
1
-
2
-
3
  body {
4
  margin: 0;
5
  font-family: 'Comic Sans MS', cursive, sans-serif;
@@ -7,12 +5,10 @@ body {
7
  overflow: hidden;
8
  }
9
 
10
- /*main container */
11
-
12
  .main-container {
13
  width: 85%;
14
  margin: 7vh auto;
15
- border: 9px solid #009688;
16
  border-radius: 1.5vw;
17
  background: #ffffff;
18
  padding: 2vw;
@@ -20,9 +16,6 @@ body {
20
  height: 73vh;
21
  }
22
 
23
- /* Section Layouts */
24
-
25
-
26
  .writing-section {
27
  display: flex;
28
  flex-direction: row;
@@ -40,7 +33,7 @@ body {
40
  }
41
 
42
  .intro-left-image {
43
- width: 35%; /* increase from default 25% */
44
  }
45
 
46
  .intro-left-image img {
@@ -48,8 +41,6 @@ body {
48
  height: auto;
49
  }
50
 
51
-
52
- /* Left Image */
53
  .left-image {
54
  width: 25%;
55
  }
@@ -58,10 +49,8 @@ body {
58
  width: 100%;
59
  }
60
 
61
- /* Right Content */
62
  .right-content {
63
  width: 66%;
64
-
65
  }
66
 
67
  .right-content h2 {
@@ -72,18 +61,14 @@ body {
72
  }
73
 
74
  .right-content p {
75
-
76
  font-size: 1.4vw;
77
- /*margin-bottom: 30px;*/
78
  text-align: justify;
79
  }
80
 
81
- /* Dropdown and Buttons */
82
  .center-controls {
83
  display: flex;
84
  flex-direction: row;
85
  align-items: center;
86
- /*justify-content: center;*/
87
  margin-top: 1vw;
88
  gap: 1.5vw;
89
  }
@@ -96,41 +81,12 @@ body {
96
 
97
  .center-controls select {
98
  font-size: 1.4vw;
99
- padding: 0.8vw;
100
  border-radius: 0.5vw;
101
  border: 2px solid #009688;
102
  background-color: #ffffff;
103
  }
104
 
105
-
106
-
107
- select,
108
- button {
109
- font-size: 1.2vw;
110
- padding: 0.6vw 1.2vw;
111
- border-radius: 0.5vw;
112
- border: 2px solid #ccc;
113
- margin-top: 1vw;
114
- margin-right: 1vw;
115
- }
116
-
117
- button {
118
- background-color: #006780;
119
- color: white;
120
- border: none;
121
- cursor: pointer;
122
- transition: background-color 0.3s;
123
- margin-top: 1.5vw;
124
- }
125
-
126
- button:disabled {
127
- background-color: #ccc !important;
128
- color: #666 !important;
129
- cursor: not-allowed !important;
130
- opacity: 0.6;
131
- }
132
-
133
- /* Textarea */
134
  textarea {
135
  width: 100%;
136
  height: 14vw;
@@ -145,10 +101,9 @@ textarea {
145
  background-attachment: local;
146
  }
147
 
148
- /* Submit Area */
149
  .submit-area {
150
  text-align: center;
151
-
152
  }
153
 
154
  .submit-area button {
@@ -157,9 +112,6 @@ textarea {
157
  border-radius: 1vw;
158
  }
159
 
160
-
161
-
162
- /* Loader Overlay */
163
  .loader-overlay {
164
  position: fixed;
165
  top: 0;
@@ -217,13 +169,9 @@ textarea {
217
  }
218
  }
219
 
220
- /*back arrow mark */
221
-
222
-
223
-
224
  .back-icon {
225
  position: absolute;
226
- top: 10vw;
227
  left: 10vw;
228
  color: #007bff;
229
  font-size: 24px;
@@ -237,14 +185,12 @@ textarea {
237
  color: #0056b3;
238
  }
239
 
240
-
241
  .back-icon img {
242
  width: 30px;
243
  height: auto;
244
  cursor: pointer;
245
  }
246
 
247
-
248
  .topic-fieldset {
249
  border: 2px solid #ffc107;
250
  padding: 1vw;
@@ -271,9 +217,6 @@ textarea {
271
  font-weight: 900;
272
  }
273
 
274
- /* feedback section*/
275
-
276
-
277
  .feedback-section {
278
  display: flex;
279
  flex-direction: column;
@@ -307,8 +250,8 @@ textarea {
307
  border: 2px dashed #ffca28;
308
  border-radius: 1vw;
309
  padding: 2vw 2vw 2vw 2vw;
310
- width: 94%;
311
- max-height: 23vw;
312
  overflow-y: scroll;
313
  }
314
 
@@ -326,12 +269,11 @@ textarea {
326
 
327
  .try-again-button {
328
  text-align: center;
 
329
  }
330
 
331
  .try-again-button button {
332
  font-size: 1.3vw;
333
- /*padding: 0.8vw 2vw;*/
334
-
335
  border: none;
336
  color: white;
337
  border-radius: 1vw;
@@ -339,7 +281,6 @@ textarea {
339
  transition: background-color 0.3s ease;
340
  }
341
 
342
-
343
  .animated {
344
  animation: bounce 1s infinite;
345
  }
@@ -354,8 +295,6 @@ textarea {
354
  }
355
  }
356
 
357
-
358
-
359
  .with-blue-tooltip {
360
  position: relative;
361
  display: inline-block;
@@ -386,3 +325,8 @@ textarea {
386
  border-style: solid;
387
  border-color: transparent #2196f3 transparent transparent;
388
  }
 
 
 
 
 
 
 
 
1
  body {
2
  margin: 0;
3
  font-family: 'Comic Sans MS', cursive, sans-serif;
 
5
  overflow: hidden;
6
  }
7
 
 
 
8
  .main-container {
9
  width: 85%;
10
  margin: 7vh auto;
11
+ border: 10px solid var(--main-accent-color);
12
  border-radius: 1.5vw;
13
  background: #ffffff;
14
  padding: 2vw;
 
16
  height: 73vh;
17
  }
18
 
 
 
 
19
  .writing-section {
20
  display: flex;
21
  flex-direction: row;
 
33
  }
34
 
35
  .intro-left-image {
36
+ width: 35%;
37
  }
38
 
39
  .intro-left-image img {
 
41
  height: auto;
42
  }
43
 
 
 
44
  .left-image {
45
  width: 25%;
46
  }
 
49
  width: 100%;
50
  }
51
 
 
52
  .right-content {
53
  width: 66%;
 
54
  }
55
 
56
  .right-content h2 {
 
61
  }
62
 
63
  .right-content p {
 
64
  font-size: 1.4vw;
 
65
  text-align: justify;
66
  }
67
 
 
68
  .center-controls {
69
  display: flex;
70
  flex-direction: row;
71
  align-items: center;
 
72
  margin-top: 1vw;
73
  gap: 1.5vw;
74
  }
 
81
 
82
  .center-controls select {
83
  font-size: 1.4vw;
84
+ padding: 0.6vw;
85
  border-radius: 0.5vw;
86
  border: 2px solid #009688;
87
  background-color: #ffffff;
88
  }
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  textarea {
91
  width: 100%;
92
  height: 14vw;
 
101
  background-attachment: local;
102
  }
103
 
 
104
  .submit-area {
105
  text-align: center;
106
+ margin-top: 1vw;
107
  }
108
 
109
  .submit-area button {
 
112
  border-radius: 1vw;
113
  }
114
 
 
 
 
115
  .loader-overlay {
116
  position: fixed;
117
  top: 0;
 
169
  }
170
  }
171
 
 
 
 
 
172
  .back-icon {
173
  position: absolute;
174
+ top: 12vw;
175
  left: 10vw;
176
  color: #007bff;
177
  font-size: 24px;
 
185
  color: #0056b3;
186
  }
187
 
 
188
  .back-icon img {
189
  width: 30px;
190
  height: auto;
191
  cursor: pointer;
192
  }
193
 
 
194
  .topic-fieldset {
195
  border: 2px solid #ffc107;
196
  padding: 1vw;
 
217
  font-weight: 900;
218
  }
219
 
 
 
 
220
  .feedback-section {
221
  display: flex;
222
  flex-direction: column;
 
250
  border: 2px dashed #ffca28;
251
  border-radius: 1vw;
252
  padding: 2vw 2vw 2vw 2vw;
253
+ width: 93%;
254
+ height: 19.5vw;
255
  overflow-y: scroll;
256
  }
257
 
 
269
 
270
  .try-again-button {
271
  text-align: center;
272
+ margin-top: 3vw;
273
  }
274
 
275
  .try-again-button button {
276
  font-size: 1.3vw;
 
 
277
  border: none;
278
  color: white;
279
  border-radius: 1vw;
 
281
  transition: background-color 0.3s ease;
282
  }
283
 
 
284
  .animated {
285
  animation: bounce 1s infinite;
286
  }
 
295
  }
296
  }
297
 
 
 
298
  .with-blue-tooltip {
299
  position: relative;
300
  display: inline-block;
 
325
  border-style: solid;
326
  border-color: transparent #2196f3 transparent transparent;
327
  }
328
+
329
+ .user-guide-close-icon {
330
+ top: 9vw;
331
+ right: 7vw;
332
+ }
src/app/writing/writing.component.html CHANGED
@@ -1,7 +1,6 @@
1
  <!-- Header container -->
2
  <app-header [title]="'Writing Exercise'"></app-header>
3
 
4
-
5
  <!-- Background Image -->
6
  <img src="assets/images/grammar-bg.png" alt="Chat Background" class="grammar-bg" />
7
 
@@ -32,15 +31,13 @@
32
  <option value="middle">🧠 Middle</option>
33
  <option value="upper">🎓 Upper</option>
34
  </select>
35
- <button class="submit-button"
36
- (click)="fetchTopic()"
37
- [disabled]="isTopicFetching"
38
- [class.loading]="isTopicFetching">
39
  <span>Get Topic</span>
40
- </button>
41
  </div>
42
 
43
-
44
  <div *ngIf="isLoaderVisible" class="loader-overlay">
45
  <div class="loader"></div>
46
  </div>
@@ -49,6 +46,7 @@
49
 
50
  <!-- Writing Section -->
51
  <div class="writing-section" *ngIf="showWriting">
 
52
  <img src="assets/images/writing/back.png" alt="Go Back" class="back-icon" (click)="goBack('writing')" />
53
 
54
  <div class="left-image">
@@ -61,21 +59,13 @@
61
  </fieldset>
62
 
63
  <textarea [(ngModel)]="userAnswer" placeholder="Write your answer here..." (input)="checkWordCount()"></textarea>
64
- <!--<div class="submit-area">
65
- <button (click)="submitAnswer()" [disabled]="isSubmitDisabled || isSubmitInProgress">
66
- {{ isSubmitInProgress ? 'Submitting...' : 'Submit Writing' }}
67
- </button>
68
- <div *ngIf="isLoaderVisible" class="loader-overlay">
69
- <div class="loader"></div>
70
- </div>
71
- </div>-->
72
 
73
  <div class="submit-area with-blue-tooltip">
74
- <button (click)="submitAnswer()"
75
- [disabled]="isSubmitDisabled || isSubmitInProgress"
76
- [ngClass]="{ 'disabled-btn': isSubmitDisabled }">
77
  {{ isSubmitInProgress ? 'Submitting...' : 'Submit Writing' }}
78
- </button>
79
  <div *ngIf="isLoaderVisible" class="loader-overlay">
80
  <div class="loader"></div>
81
  </div>
@@ -85,11 +75,9 @@
85
  </div>
86
  </div>
87
 
88
-
89
  </div>
90
  </div>
91
 
92
-
93
  <!-- Feedback Section -->
94
  <div class="feedback-section" *ngIf="showFeedback">
95
  <img src="assets/images/writing/back.png" alt="Go Back" class="back-icon" (click)="goBack('feedback')" />
@@ -106,10 +94,8 @@
106
  </div>
107
 
108
  <div class="try-again-button">
109
- <button (click)="resetForm()">🔄 Try Another</button>
110
  </div>
111
  </div>
112
 
113
-
114
-
115
  </div>
 
1
  <!-- Header container -->
2
  <app-header [title]="'Writing Exercise'"></app-header>
3
 
 
4
  <!-- Background Image -->
5
  <img src="assets/images/grammar-bg.png" alt="Chat Background" class="grammar-bg" />
6
 
 
31
  <option value="middle">🧠 Middle</option>
32
  <option value="upper">🎓 Upper</option>
33
  </select>
34
+ <app-button [disabled]="isTopicFetching"
35
+ [ngClass]="{ loading: isTopicFetching }"
36
+ (click)="fetchTopic()">
 
37
  <span>Get Topic</span>
38
+ </app-button>
39
  </div>
40
 
 
41
  <div *ngIf="isLoaderVisible" class="loader-overlay">
42
  <div class="loader"></div>
43
  </div>
 
46
 
47
  <!-- Writing Section -->
48
  <div class="writing-section" *ngIf="showWriting">
49
+ <button class="user-guide-close-icon" (click)="goBack('writing')">×</button>
50
  <img src="assets/images/writing/back.png" alt="Go Back" class="back-icon" (click)="goBack('writing')" />
51
 
52
  <div class="left-image">
 
59
  </fieldset>
60
 
61
  <textarea [(ngModel)]="userAnswer" placeholder="Write your answer here..." (input)="checkWordCount()"></textarea>
 
 
 
 
 
 
 
 
62
 
63
  <div class="submit-area with-blue-tooltip">
64
+ <app-button [disabled]="isSubmitDisabled || isSubmitInProgress"
65
+ [ngClass]="{ 'disabled-btn': isSubmitDisabled }"
66
+ (click)="submitAnswer()">
67
  {{ isSubmitInProgress ? 'Submitting...' : 'Submit Writing' }}
68
+ </app-button>
69
  <div *ngIf="isLoaderVisible" class="loader-overlay">
70
  <div class="loader"></div>
71
  </div>
 
75
  </div>
76
  </div>
77
 
 
78
  </div>
79
  </div>
80
 
 
81
  <!-- Feedback Section -->
82
  <div class="feedback-section" *ngIf="showFeedback">
83
  <img src="assets/images/writing/back.png" alt="Go Back" class="back-icon" (click)="goBack('feedback')" />
 
94
  </div>
95
 
96
  <div class="try-again-button">
97
+ <app-button (click)="resetForm()">🔄 Try Another</app-button>
98
  </div>
99
  </div>
100
 
 
 
101
  </div>
src/app/writing/writing.component.ts CHANGED
@@ -1,11 +1,17 @@
1
  import { Component } from '@angular/core';
2
  import { WritingService } from './writing.service';
3
  import { Router } from '@angular/router';
 
 
 
 
4
 
5
  @Component({
6
  selector: 'app-writing',
7
  templateUrl: './writing.component.html',
8
- styleUrls: ['./writing.component.css']
 
 
9
  })
10
  export class WritingComponent {
11
  gradeLevel = 'lower';
@@ -15,9 +21,9 @@ export class WritingComponent {
15
  feedback: string | null = null;
16
  isFeedbackVisible: boolean = false;
17
  isSubmitDisabled: boolean = true;
18
- isTopicFetching: boolean = false;
19
- isLoading: boolean = false;
20
- isSubmitInProgress: boolean = false;
21
  feedbackList: string[] = [];
22
  showStarAnimation: boolean = false;
23
  isLoaderVisible: boolean = false;
@@ -27,7 +33,7 @@ export class WritingComponent {
27
  showFeedback: boolean = false;
28
 
29
  constructor(private writingService: WritingService, private router: Router) { }
30
-
31
  fetchTopic(): void {
32
  this.isLoaderVisible = true;
33
  this.isTopicFetching = true;
@@ -37,7 +43,7 @@ export class WritingComponent {
37
  this.showWriting = false;
38
  this.showIntro = true;
39
  this.showFeedback = false;
40
- this.userAnswer = ''; // ✅ Clear previous answer
41
 
42
  this.writingService.getWritingTopic(this.gradeLevel).subscribe(
43
  (response) => {
@@ -63,7 +69,6 @@ export class WritingComponent {
63
  );
64
  }
65
 
66
-
67
  submitAnswer(): void {
68
  if (this.userAnswer.trim() === '') {
69
  this.errorMessage = 'Please write an answer before submitting.';
@@ -79,9 +84,8 @@ export class WritingComponent {
79
  this.isSubmitInProgress = true;
80
  this.isLoaderVisible = true;
81
 
82
- // Don't show feedback screen immediately – wait for response
83
  this.showIntro = false;
84
- this.showWriting = true; // stay in writing section during loader
85
  this.showFeedback = false;
86
 
87
  this.writingService.validateResponse(this.topic, this.userAnswer).subscribe({
@@ -94,7 +98,6 @@ export class WritingComponent {
94
  this.isSubmitInProgress = false;
95
  this.isLoaderVisible = false;
96
 
97
- // ✅ Now move to feedback after getting response
98
  this.showWriting = false;
99
  this.showFeedback = true;
100
  },
@@ -110,29 +113,23 @@ export class WritingComponent {
110
  });
111
  }
112
 
113
-
114
  checkWordCount(): void {
115
  setTimeout(() => {
116
  const wordCount = this.userAnswer.trim().split(/\s+/).length;
117
- this.isSubmitDisabled = wordCount < 10; // Enable button if 10+ words
118
  });
119
  }
120
 
121
-
122
-
123
-
124
-
125
  resetForm(): void {
126
- this.gradeLevel = 'lower'; // Reset grade level to default
127
- this.topic = null; // Clear the topic
128
- this.userAnswer = ''; // Clear the answer
129
- this.feedback = null; // Clear feedback
130
- this.errorMessage = null; // Clear errors
131
- this.isFeedbackVisible = false; // Hide feedback
132
- this.isSubmitDisabled = true; // Disable submit button
133
- this.isTopicFetching = false; // Re-enable topic selection
134
- this.isSubmitInProgress = false; // Enable submit button
135
- // UI flags
136
  this.showIntro = true;
137
  this.showWriting = false;
138
  this.showFeedback = false;
@@ -140,11 +137,9 @@ export class WritingComponent {
140
 
141
  goBack(from: string): void {
142
  if (from === 'writing') {
143
- // Go back to Intro
144
  this.showWriting = false;
145
  this.showIntro = true;
146
  } else if (from === 'feedback') {
147
- // Go back to Writing
148
  this.showFeedback = false;
149
  this.showWriting = true;
150
  }
@@ -157,7 +152,6 @@ export class WritingComponent {
157
  goToListening(): void {
158
  this.router.navigate(['/listening']);
159
  }
160
-
161
  }
162
 
163
 
 
1
  import { Component } from '@angular/core';
2
  import { WritingService } from './writing.service';
3
  import { Router } from '@angular/router';
4
+ import { HeaderComponent } from '../shared/header/header.component';
5
+ import { ButtonComponent } from '../shared/button/button.component';
6
+ import { CommonModule } from '@angular/common';
7
+ import { FormsModule } from '@angular/forms';
8
 
9
  @Component({
10
  selector: 'app-writing',
11
  templateUrl: './writing.component.html',
12
+ styleUrls: ['./writing.component.css'],
13
+ standalone: true,
14
+ imports: [ButtonComponent, CommonModule, FormsModule, HeaderComponent]
15
  })
16
  export class WritingComponent {
17
  gradeLevel = 'lower';
 
21
  feedback: string | null = null;
22
  isFeedbackVisible: boolean = false;
23
  isSubmitDisabled: boolean = true;
24
+ isTopicFetching: boolean = false;
25
+ isLoading: boolean = false;
26
+ isSubmitInProgress: boolean = false;
27
  feedbackList: string[] = [];
28
  showStarAnimation: boolean = false;
29
  isLoaderVisible: boolean = false;
 
33
  showFeedback: boolean = false;
34
 
35
  constructor(private writingService: WritingService, private router: Router) { }
36
+
37
  fetchTopic(): void {
38
  this.isLoaderVisible = true;
39
  this.isTopicFetching = true;
 
43
  this.showWriting = false;
44
  this.showIntro = true;
45
  this.showFeedback = false;
46
+ this.userAnswer = '';
47
 
48
  this.writingService.getWritingTopic(this.gradeLevel).subscribe(
49
  (response) => {
 
69
  );
70
  }
71
 
 
72
  submitAnswer(): void {
73
  if (this.userAnswer.trim() === '') {
74
  this.errorMessage = 'Please write an answer before submitting.';
 
84
  this.isSubmitInProgress = true;
85
  this.isLoaderVisible = true;
86
 
 
87
  this.showIntro = false;
88
+ this.showWriting = true;
89
  this.showFeedback = false;
90
 
91
  this.writingService.validateResponse(this.topic, this.userAnswer).subscribe({
 
98
  this.isSubmitInProgress = false;
99
  this.isLoaderVisible = false;
100
 
 
101
  this.showWriting = false;
102
  this.showFeedback = true;
103
  },
 
113
  });
114
  }
115
 
 
116
  checkWordCount(): void {
117
  setTimeout(() => {
118
  const wordCount = this.userAnswer.trim().split(/\s+/).length;
119
+ this.isSubmitDisabled = wordCount < 10;
120
  });
121
  }
122
 
 
 
 
 
123
  resetForm(): void {
124
+ this.gradeLevel = 'lower';
125
+ this.topic = null;
126
+ this.userAnswer = '';
127
+ this.feedback = null;
128
+ this.errorMessage = null;
129
+ this.isFeedbackVisible = false;
130
+ this.isSubmitDisabled = true;
131
+ this.isTopicFetching = false;
132
+ this.isSubmitInProgress = false;
 
133
  this.showIntro = true;
134
  this.showWriting = false;
135
  this.showFeedback = false;
 
137
 
138
  goBack(from: string): void {
139
  if (from === 'writing') {
 
140
  this.showWriting = false;
141
  this.showIntro = true;
142
  } else if (from === 'feedback') {
 
143
  this.showFeedback = false;
144
  this.showWriting = true;
145
  }
 
152
  goToListening(): void {
153
  this.router.navigate(['/listening']);
154
  }
 
155
  }
156
 
157