Anupriya commited on
Commit
50fc34c
·
1 Parent(s): c603510

Reading and writing updates

Browse files
src/app/home/home.component.css CHANGED
@@ -268,3 +268,25 @@ h1 {
268
  background-color: #007bff;
269
  color: white;
270
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  background-color: #007bff;
269
  color: white;
270
  }
271
+
272
+
273
+ .nav-link--disabled {
274
+ opacity: 0.6;
275
+ cursor: not-allowed;
276
+
277
+ }
278
+
279
+
280
+ /* Apply the same look to <a> and the disabled <span class="nav-link"> */
281
+ .nav-links li a,
282
+ .nav-links li .nav-link {
283
+ text-decoration: none;
284
+ color: #333;
285
+ font-size: 1.5vw;
286
+ font-weight: 700;
287
+ }
288
+
289
+ .nav-links li a:hover,
290
+ .nav-links li .nav-link:hover {
291
+ color: #007bff;
292
+ }
src/app/home/home.component.html CHANGED
@@ -11,7 +11,23 @@
11
  <ul class="nav-links">
12
  <li><a routerLink="/chat" routerLinkActive="active-link">Chat</a></li>
13
  <li><a routerLink="/generate-questions" routerLinkActive="active-link">Grammar</a></li>
14
- <li><a routerLink="/Voice" routerLinkActive="active-link">Voice</a></li>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  <li><a routerLink="/listen" routerLinkActive="active-link">Listening</a></li>
16
  <li><a routerLink="/reading" routerLinkActive="active-link">Reading</a></li>
17
  <li><a routerLink="/writing" routerLinkActive="active-link">Writing</a></li>
@@ -71,7 +87,7 @@
71
 
72
  <!-- Footer -->
73
  <div class="footer">
74
- <p>&#64; 2025 Pykara Technologies. All Rights Reserved.</p>
75
  <div class="social-media">
76
  <a href="https://www.youtube.com/@PykaraTechnologies/videos" target="_blank">
77
  <img src="assets/images/home/youtube-icon.png" alt="YouTube">
 
11
  <ul class="nav-links">
12
  <li><a routerLink="/chat" routerLinkActive="active-link">Chat</a></li>
13
  <li><a routerLink="/generate-questions" routerLinkActive="active-link">Grammar</a></li>
14
+ <li>
15
+ <!-- when disabled -->
16
+ <span *ngIf="isVoiceDisabled; else voiceLink"
17
+ class="nav-link nav-link--disabled"
18
+ aria-disabled="true">
19
+ Voice
20
+ </span>
21
+
22
+ <!-- when enabled -->
23
+ <ng-template #voiceLink>
24
+ <a class="nav-link"
25
+ routerLink="/Voice"
26
+ routerLinkActive="active-link">
27
+ Voice
28
+ </a>
29
+ </ng-template>
30
+ </li>
31
  <li><a routerLink="/listen" routerLinkActive="active-link">Listening</a></li>
32
  <li><a routerLink="/reading" routerLinkActive="active-link">Reading</a></li>
33
  <li><a routerLink="/writing" routerLinkActive="active-link">Writing</a></li>
 
87
 
88
  <!-- Footer -->
89
  <div class="footer">
90
+ <p 2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p>
91
  <div class="social-media">
92
  <a href="https://www.youtube.com/@PykaraTechnologies/videos" target="_blank">
93
  <img src="assets/images/home/youtube-icon.png" alt="YouTube">
src/app/home/home.component.ts CHANGED
@@ -14,6 +14,8 @@ import { Router } from '@angular/router';
14
  })
15
  export class HomeComponent implements AfterViewInit {
16
  @ViewChildren('wrapper') wrappers!: QueryList<ElementRef>;
 
 
17
 
18
  cards = [
19
  {
 
14
  })
15
  export class HomeComponent implements AfterViewInit {
16
  @ViewChildren('wrapper') wrappers!: QueryList<ElementRef>;
17
+ // component.ts
18
+ isVoiceDisabled = true; // set true to disable, false to enable
19
 
20
  cards = [
21
  {
src/app/reading/reading.component.css CHANGED
@@ -1,26 +1,24 @@
 
1
  .reading-container {
2
- font-family: 'Segoe UI', sans-serif;
3
- /*background-image: url(/assets/images/grammar-bg.png);*/
4
  background-size: auto;
5
  background-position: center;
6
  background-attachment: fixed;
7
  width: 100%;
8
- height: 100%;
9
- height: 100%;
10
  }
11
 
12
-
13
  .header-container {
14
  display: flex;
15
  justify-content: space-between;
16
  align-items: center;
17
- padding: 0vw 2vw;
18
  background-color: #009688;
19
- box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2); /* Soft shadow */
20
  width: 100%;
21
  position: sticky;
22
  top: 0;
23
- z-index: 1;
24
  }
25
 
26
  .logo img {
@@ -28,31 +26,29 @@
28
  height: auto;
29
  background: #fff;
30
  border-radius: 1vw;
31
- margin: 0.5vw;
32
  }
33
 
34
  .header-title {
35
  text-align: center;
36
- flex: 1;
37
  }
38
 
39
  .header-title h1 {
40
  font-size: 3vw;
41
  color: #fff;
42
- margin: 0;
43
  }
44
 
45
  .home-btn img {
46
  width: 5vw;
 
47
  }
48
 
49
  .home-btn img:hover {
50
- transform: scale(1.1);
51
  }
52
 
53
-
54
-
55
-
56
  .grammar-bg {
57
  position: absolute;
58
  top: 10%;
@@ -62,7 +58,7 @@
62
  max-height: calc(100vh - 100px);
63
  object-fit: fill;
64
  z-index: -1;
65
- opacity: 0.2;
66
  }
67
 
68
  .main-container {
@@ -70,628 +66,783 @@
70
  margin: 7vh auto;
71
  border: 9px solid #009688;
72
  border-radius: 1.5vw;
73
- background: #ffffff;
74
  padding: 2vw;
75
- box-shadow: 0 0.4vw 1vw rgba(0, 0, 0, 0.1);
76
- height: 76vh;
77
  }
78
 
 
 
 
 
 
79
 
80
- /* Intro Section Layout */
81
- .intro-section {
82
- display: flex;
83
- flex-direction: row;
84
- justify-content: center;
85
- align-items: center;
86
- gap: 3vw;
87
- height: 100%;
88
  }
89
 
90
- /* Left Image */
91
- .intro-left-image {
92
- width: 27%;
 
 
 
 
 
93
  }
94
 
95
- .intro-left-image img {
96
- width: 100%;
97
- height: auto;
98
- border-radius: 1vw;
99
- }
100
 
101
- /* Right Content */
102
- .right-content {
103
  width: 60%;
104
  }
105
 
106
- .right-content h2 {
107
- font-size: 2.5vw;
108
- font-weight: 800;
109
- color: #006780;
110
- margin-bottom: 20px;
111
- }
 
 
112
 
113
- .right-content p {
114
- font-size: 1.3vw;
115
- text-align: justify;
116
- color: #333;
117
- }
118
 
119
- /* Controls Row */
120
- .center-controls {
121
  display: flex;
122
- flex-direction: row;
123
  align-items: center;
124
- gap: 1vw;
125
- margin-top: 1.5vw;
126
- }
127
-
128
- .center-controls input {
129
- flex: 1;
130
- padding: 0.8vw;
131
- font-size: 1.2vw;
132
- border-radius: 0.5vw;
133
- border: 2px solid #009688;
134
- }
135
-
136
- .center-controls select {
137
- font-size: 1.2vw;
138
- padding: 0.8vw;
139
- border-radius: 0.5vw;
140
- border: 2px solid #009688;
141
- background-color: #fff;
142
- width: 8vw;
143
- }
144
-
145
- .center-controls button {
146
- font-size: 1.2vw;
147
- padding: 0.8vw 1.6vw;
148
- background-color: #006780;
149
- color: white;
150
- border: none;
151
- border-radius: 0.5vw;
152
- cursor: pointer;
153
- }
154
 
155
- .center-controls button:disabled {
156
- background-color: #cccccc;
157
- cursor: not-allowed;
158
- opacity: 0.6;
159
- }
 
160
 
161
- /* Suggestion Box */
162
- .suggestion-box {
163
- margin-top: 1vw;
164
- background: #ffffff;
165
- border: 1px solid #ccc;
166
- padding: 0.6vw;
167
- width: 100%;
168
- border-radius: 0.5vw;
169
- box-shadow: 0 0.2vw 0.6vw rgba(0, 0, 0, 0.1);
170
  }
171
 
172
- .suggestion-box span {
173
- display: block;
174
- padding: 0.4vw 0.6vw;
175
- cursor: pointer;
176
- font-size: 1.2vw;
 
 
 
 
177
  }
178
 
179
- .suggestion-box span:hover {
180
- background-color: #f0f0f0;
181
  }
182
 
 
 
 
 
183
 
184
- .content-header {
185
- display: flex;
186
- align-items: center;
187
- justify-content: space-between;
188
- position: relative;
189
- margin-bottom: 2vw;
190
- }
191
-
192
- .content-header .back-arrow {
193
- width: 3vw;
 
 
 
 
 
194
  }
195
 
196
- .content-header .arrow-icon {
197
- width: 100%;
198
- cursor: pointer;
199
  }
200
 
201
- .content-header .centered-title {
202
- position: absolute;
203
- left: 50%;
204
- transform: translateX(-50%);
205
- font-size: 2vw;
206
- color: #ff6f00;
207
- text-shadow: 1px 1px #ffe0b2;
208
- font-weight: bold;
209
- margin: 0;
 
 
 
 
 
 
 
 
 
210
  }
211
 
212
- .content-header .right-spacer {
213
- width: 3vw; /* same as back-arrow width to balance layout */
 
 
214
  }
215
 
 
 
 
 
 
216
 
217
-
218
- .question-header {
219
- display: flex;
220
- align-items: center;
221
- justify-content: space-between;
222
- position: relative;
223
- margin-bottom: 2vw;
 
 
 
 
 
224
  }
225
 
226
- .question-header .back-arrow {
227
- width: 3vw;
228
- }
229
-
230
- .question-header .arrow-icon {
231
- width: 100%;
232
- cursor: pointer;
233
  }
234
 
235
- .question-header .centered-title {
236
- position: absolute;
237
- left: 50%;
238
- transform: translateX(-50%);
239
- font-size: 2vw;
240
- color: #006780;
241
- font-weight: bold;
242
- margin: 0;
243
  }
244
 
245
- .question-header .right-spacer {
246
- width: 3vw;
247
- display: flex;
248
- justify-content: flex-end;
249
  }
250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
 
 
 
 
 
 
 
252
 
 
 
 
 
253
 
 
 
 
 
254
 
 
 
 
255
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
 
 
 
 
 
 
257
 
 
 
 
258
 
 
 
 
 
 
 
259
 
 
 
 
 
260
 
261
 
 
 
 
 
262
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
 
 
 
 
 
 
 
 
 
264
 
 
 
 
 
265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
 
 
 
 
267
 
 
 
 
268
 
 
 
 
 
269
 
 
 
 
270
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
 
 
 
 
 
272
 
 
 
 
273
 
 
 
 
 
274
 
 
 
 
 
 
 
 
 
 
 
 
275
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
 
 
 
 
 
277
 
 
 
 
 
 
278
 
 
 
 
 
 
279
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
 
 
 
 
 
281
 
 
 
 
 
 
282
 
 
 
 
283
 
 
 
 
 
 
 
 
 
 
 
 
 
284
 
 
 
 
285
 
 
 
 
286
 
 
 
 
 
 
 
 
 
 
287
 
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
- h2 {
307
- margin-bottom: 1vw;
308
- text-align: center;
309
- font-size: 2vw;
310
- color: #ff6f00;
311
- text-shadow: 1px 1px #ffe0b2;
312
- }
313
- .generate-content-container {
314
- background: #ffffff;
315
- padding: 2vw;
316
- border-radius: 1vw;
317
- width: 81vw;
318
- margin: 3vw auto;
319
- text-align: center;
320
- background-color: #fff;
321
- border: 10px solid #009688;
322
- border-radius: 1vw;
323
- box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.6);
324
- }
325
-
326
- .input-row {
327
  display: flex;
328
- gap: 1vw;
329
  justify-content: center;
330
- flex-wrap: wrap;
331
  }
332
 
333
- .input-wrapper {
334
- position: relative;
335
- }
336
-
337
- input[type="text"], select {
338
- font-size: 1.2vw;
339
- padding: 1vw;
340
- width: 20vw;
341
- border-radius: 0.5vw;
342
  }
343
 
344
- button {
345
- font-size: 1.2vw;
346
- padding: 0.8vw 1.5vw;
347
- border-radius: 0.5vw;
348
- background-color: #4CAF50;
349
- color: white;
350
- border: none;
351
- cursor: pointer;
352
  }
353
 
354
- button:hover {
355
- background-color: #45a049;
356
  }
357
 
358
- button:disabled {
359
- background-color: #aaa;
360
  cursor: not-allowed;
361
  }
362
 
363
- .suggestion-box {
364
- display: grid;
365
- grid-template-columns: repeat(2, 1fr);
366
- gap: 0.5vw;
367
- background: #fff;
368
- border: 1px solid #ccc;
369
- position: absolute;
370
- top: 110%;
371
- width: 20vw;
372
- padding: 1vw;
373
- z-index: 10;
374
  }
375
 
376
- .suggestion-box span {
377
- background: #e0f7fa;
378
- padding: 0.8vw;
379
- border-radius: 0.5vw;
380
- cursor: pointer;
381
- font-size: 1.1vw;
382
- text-align: center;
383
  }
384
 
385
- .suggestion-box span:hover {
386
- background-color: #b2ebf2;
387
- }
388
-
389
- .content-block {
390
- /*margin-top: 1vw;*/
391
- }
392
-
393
- .content-scroll {
394
- background-color: #fff8e1;
395
- border: 2px dashed #ffca28;
396
- border-radius: 1vw;
397
- padding: 2vw 2vw 2vw 2vw;
398
- width: 99%;
399
- max-height: 24vw;
400
- overflow-y: scroll;
401
  }
402
 
403
- .content-scroll p {
404
- font-size: 1.3vw;
405
- color: #333;
406
- line-height: 1.6;
407
- text-align: justify;
408
  }
409
 
410
- .content-block,
411
- .question-block {
412
- position: relative;
413
  }
414
 
415
- .button-container {
416
- display: flex;
417
- justify-content: center;
418
- gap: 1vw;
419
- margin-top: 2vw;
420
- }
421
- /* Common Button Styling */
422
- button,
423
- .submit-button,
424
- .reset-button {
425
- font-size: 1.2vw;
426
- padding: 0.8vw 1.5vw;
427
- border-radius: 0.5vw;
428
- border: none;
429
- color: white;
430
- background-color: #006780;
431
- cursor: pointer;
432
- transition: all 0.3s ease;
433
  }
 
434
 
435
- button:hover:enabled,
436
- .submit-button:hover:enabled,
437
- .reset-button:hover:enabled {
438
- background-color: #18788f;
439
- transform: scale(1.05);
440
- }
 
 
 
 
441
 
442
- button:disabled,
443
- .submit-button:disabled,
444
- .reset-button:disabled {
445
- background-color: #aaa;
446
- cursor: not-allowed;
447
- }
 
 
 
448
 
 
 
 
 
 
 
 
449
 
450
- /* Questions */
451
- .question-container {
452
- margin-bottom: 2vw;
453
- text-align: left;
454
  display: flex;
455
- justify-content: center;
456
  }
457
 
458
- .option-container {
 
459
  display: flex;
460
- align-items: center;
461
- margin: 0.5vw 0;
 
 
 
 
 
 
462
  }
463
 
464
- .option-container input[type="radio"] {
465
- margin-right: 1vw;
466
- transform: scale(1.5);
467
- }
468
-
469
- .option-container label {
470
- font-size: 1.3vw;
471
- }
472
 
473
- .correct-answer {
474
- color: green;
475
- font-weight: bold;
 
476
  }
477
 
478
- .wrong-answer {
479
- color: red;
480
- font-weight: bold;
481
  }
482
 
483
- .feedback-message {
484
- font-size: 1.4vw;
485
- margin-top: 2vw;
486
- color: #00796B;
 
 
 
 
487
  }
488
 
489
- /* Fixed width and height for question box */
490
- .question-box {
491
- width: 60vw;
492
- height: 20vw;
493
- background: #f9f9f9;
494
- padding: 2vw;
495
- border-radius: 1vw;
496
- overflow-y: auto;
497
- box-shadow: 0 0.5vw 1vw rgba(0, 0, 0, 0.1);
498
  }
499
 
500
- /* Popup Overlay */
501
- .popup-overlay {
502
- position: fixed;
503
- top: 0;
504
- left: 0;
505
- width: 100%;
506
- height: 100%;
507
- background-color: rgba(0, 0, 0, 0.5);
508
  display: flex;
509
- justify-content: center;
510
  align-items: center;
511
- z-index: 2;
512
- }
513
-
514
- /* Popup Content */
515
- .popup-content {
516
- background-color: #fff;
517
- padding: 2vw;
518
- border-radius: 1vw;
519
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
520
- text-align: center;
521
- width: 30vw;
522
  }
523
 
524
- .popup-content p {
525
- font-size: 1.5vw;
526
- color: #f44336;
527
- margin: 0;
528
- text-align: center;
529
  }
530
 
531
- .close-btn {
532
- background-color: #ff5722;
533
- color: white;
534
- border: none;
535
- padding: 1vw 2vw;
536
- border-radius: 0.5vw;
537
- cursor: pointer;
538
- margin-top: 2vw;
539
- font-size: 1.3vw;
540
- }
541
-
542
- .close-btn:hover {
543
- background-color: #e64a19;
544
  }
545
 
 
 
 
546
 
547
- .button-container {
548
- display: flex;
549
- justify-content: space-between;
550
- gap: 1vw;
551
- margin-top: 2vw;
552
- }
553
 
 
 
 
 
554
 
555
- /* Centered question header text */
556
- .questions-block h3 {
557
- font-size: 2vw;
558
- color: #000;
559
- text-align: center;
560
- margin-bottom: 2vw;
561
- }
562
 
563
- /* Question text styling */
564
- .question-text {
565
- font-size: 1.5vw;
566
- font-weight: bold;
567
- color: #333;
568
- margin-bottom: 1vw;
569
- word-break: break-word; /* Prevents overflow */
 
 
570
  }
571
 
572
- /* Button layout and styling */
573
- .button-container {
574
  display: flex;
 
 
 
 
575
  justify-content: center;
576
- gap: 1.5vw;
577
  margin-top: 2vw;
578
  }
579
 
 
 
 
 
 
 
580
 
581
-
 
 
 
 
582
 
 
 
 
 
 
583
 
 
 
 
584
 
585
- .question-header-with-arrow {
 
586
  display: flex;
587
- align-items: center;
588
- justify-content: space-between;
589
- margin-bottom: 2vw;
590
- position: relative;
591
  }
592
 
593
- .back-arrow .arrow-icon {
594
- width: 3vw;
 
 
 
595
  cursor: pointer;
 
 
 
596
  }
597
 
598
- .question-title {
599
- font-size: 2vw;
600
- color: #000;
601
- font-weight: bold;
602
- text-align: center;
603
- position: absolute;
604
- left: 50%;
605
- transform: translateX(-50%);
606
- }
607
-
608
- .right-spacer {
609
- width: 3vw;
610
  }
611
 
 
 
 
612
 
613
- .center-completion {
614
- display: flex;
615
- align-items: center;
616
- justify-content: center;
617
- height: 50vh;
618
- }
 
619
 
620
- .completion-message {
621
- text-align: center;
 
 
622
  }
623
 
624
- .completion-message .feedback-message {
625
- font-size: 1.8vw;
626
- color: #009688;
627
- margin-bottom: 2vw;
628
  }
629
 
630
-
631
- .input-section {
632
- display: flex;
633
- align-items: center;
634
- gap: 1vw;
635
- margin: 2vw;
636
- position: relative;
637
  }
638
 
639
- .input-wrapper {
640
- position: relative;
 
 
 
 
 
 
 
 
 
641
  display: flex;
642
- flex-direction: column;
643
- }
644
-
645
- input[type="text"] {
646
- font-size: 1.2vw;
647
- padding: 1vw;
648
- width: 25vw;
649
- border-radius: 0.5vw;
650
- border: 1px solid #4CAF50;
651
- outline: none;
652
- }
653
-
654
- .difficulty-select {
655
- font-size: 1.2vw;
656
- padding: 1vw;
657
- width: 10vw;
658
- border-radius: 0.5vw;
659
- border: 1px solid #4CAF50;
660
- outline: none;
661
  }
662
 
663
- .suggestion-box {
664
- display: flex;
665
- position: absolute;
666
- top: 110%;
667
- left: 0;
668
- background: #ffffff;
669
- border: 1px solid #ccc;
670
- border-radius: 0.5vw;
671
- padding: 0.5vw;
672
- gap: 1vw;
673
- box-shadow: 0 0.3vw 0.8vw rgba(0, 0, 0, 0.1);
674
- z-index: 10;
675
- flex-wrap: wrap;
676
  }
 
677
 
678
- .suggestion-box span {
679
- background: #e0f7fa;
680
- padding: 0.5vw 1vw;
681
- border-radius: 0.5vw;
682
- cursor: pointer;
683
- white-space: nowrap;
684
- font-size: 1.1vw;
685
- transition: background-color 0.3s ease;
686
- }
687
-
688
- .suggestion-box span:hover {
689
- background: #b2ebf2;
690
- }
691
 
692
- input[type="radio"].radio-blue:checked {
693
- accent-color: #2196F3;
694
- }
695
  /* Loader Overlay */
696
  .loader-overlay {
697
  position: fixed;
@@ -699,7 +850,6 @@ input[type="radio"].radio-blue:checked {
699
  left: 0;
700
  width: 100%;
701
  height: 100%;
702
- background-color: rgba(0, 0, 0, 0.6);
703
  display: flex;
704
  justify-content: center;
705
  align-items: center;
@@ -707,9 +857,9 @@ input[type="radio"].radio-blue:checked {
707
  }
708
 
709
  .loader {
710
- font-size: 15px;
711
- width: 1.5em;
712
- height: 1.5em;
713
  border-radius: 50%;
714
  position: relative;
715
  text-indent: -9999em;
@@ -752,165 +902,341 @@ input[type="radio"].radio-blue:checked {
752
  }
753
 
754
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
755
 
 
 
 
 
 
756
 
 
 
 
 
 
757
 
758
- /* Header row with title */
759
- .question-header-row {
 
 
 
 
 
 
 
 
 
 
 
 
 
760
  display: flex;
761
- justify-content: space-between;
762
  align-items: center;
763
- margin-bottom: 1vw;
764
  }
765
 
766
- .question-heading {
767
- font-size: 2vw;
768
- color: #006780;
769
- font-weight: bold;
770
- flex: 1;
 
771
  text-align: center;
772
- margin: 0;
 
773
  }
774
 
775
 
776
 
777
- .close-btn {
778
- background-color: #ffffff;
779
- color: #0097a7;
 
 
 
 
 
 
 
 
 
780
  border: none;
781
- padding: 0.4vw 0.9vw;
782
- border-radius: 50%;
783
  cursor: pointer;
784
- border: 1px solid black;
785
- width: 3vw;
786
- top: -66px;
787
- left: 98.5%;
788
- position: absolute;
789
- height: 3vw;
790
  }
791
 
 
 
 
792
 
793
- /* Question text */
794
- .question-text {
795
- font-size: 1.6vw;
796
- font-weight: 600;
797
- color: #333;
798
- margin-bottom: 1.5vw;
799
- text-align: left;
 
 
 
 
 
 
 
 
800
  }
801
 
802
- /* Question layout */
803
- .question-content-row {
804
- display: flex;
805
- justify-content: space-between;
806
- gap: 2vw;
807
- margin-top: 1vw;
808
- align-items: flex-start;
809
  }
810
 
811
- /* Rabbit image */
812
- .question-side-image img {
813
- width: 39vw;
814
- height: auto;
815
- max-height: 16vw;
816
- object-fit: contain;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
817
  }
818
 
819
- /* Radio options styled like buttons */
820
- .options-container {
 
 
 
 
 
 
 
 
 
 
 
821
  display: grid;
822
- grid-template-columns: repeat(2, 1fr);
823
- gap: 2vw;
824
- width: 100%;
 
825
  }
826
 
827
- .options-container label {
828
- background-color: #ffffff;
829
- border: 2px solid #cccccc;
830
- font-size: 1.3vw;
831
- border-radius: 3vw;
832
- padding: 1vw;
833
- font-weight: 600;
834
- box-shadow: 0 0.2vw 0.5vw rgba(0, 0, 0, 0.1);
835
- display: flex;
836
- align-items: center;
837
- gap: 1vw;
838
- cursor: pointer;
839
- transition: 0.3s ease;
840
  }
841
 
842
- .options-container input[type="radio"] {
843
- transform: scale(1.3);
844
  }
845
 
846
- /* Blue highlight before submission */
847
- .selected-option {
848
- background-color: #e1f5fe !important;
849
- border-color: #0277bd !important;
850
- color: #01579b !important;
851
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
852
 
853
- /* Correct/incorrect styling */
854
- .correct {
855
- background-color: #c8e6c9 !important;
856
- border-color: #388e3c !important;
857
- color: #2e7d32;
 
 
 
 
 
 
 
858
  }
859
 
860
- .incorrect {
861
- background-color: #ffcdd2 !important;
862
- border-color: #c62828 !important;
863
- color: #b71c1c;
864
  }
865
 
866
- /* Footer layout */
867
- .question-footer {
868
- display: flex;
869
- justify-content: space-between;
870
- margin-top: 2vw;
871
- align-items: center;
 
872
  }
873
 
874
- .right-buttons {
875
- display: flex;
876
- gap: 1vw;
 
 
 
 
 
877
  }
878
 
879
- /* Buttons */
880
- .prev-btn, .submit-btn, .next-btn, .reset-btn {
881
- background-color: #006780;
882
- color: white;
883
- font-size: 1.3vw;
884
- font-weight: bold;
885
- padding: 0.8vw 2vw;
886
- border: none;
887
- border-radius: 0.5vw;
888
- cursor: pointer;
889
- transition: background-color 0.3s, transform 0.3s;
 
 
 
 
 
 
 
890
  }
891
 
892
- .prev-btn:hover,
893
- .submit-btn:hover,
894
- .next-btn:hover,
895
- .reset-btn:hover {
896
- background-color: #18788f;
897
- transform: scale(1.05);
 
 
 
 
 
898
  }
899
 
900
- .prev-btn:disabled,
901
- .submit-btn:disabled,
902
- .next-btn:disabled,
903
- .reset-btn:disabled {
904
- background-color: #aaa;
905
- cursor: not-allowed;
 
 
 
 
906
  }
907
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
908
 
909
- .selected-option {
910
- background-color: #e1f5fe !important;
911
- border: 2px solid #0277bd !important;
912
- color: #01579b !important;
913
- font-weight: 600;
914
- padding: 0.8vw 1vw;
915
- border-radius: 2vw;
 
 
 
 
 
916
  }
 
 
 
 
 
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
  .header-container {
12
  display: flex;
13
  justify-content: space-between;
14
  align-items: center;
15
+ padding: 0 2vw;
16
  background-color: #009688;
17
+ box-shadow: 0 4px 8px rgba(0,0,0,.2);
18
  width: 100%;
19
  position: sticky;
20
  top: 0;
21
+ z-index: 1
22
  }
23
 
24
  .logo img {
 
26
  height: auto;
27
  background: #fff;
28
  border-radius: 1vw;
29
+ margin: .5vw
30
  }
31
 
32
  .header-title {
33
  text-align: center;
34
+ flex: 1
35
  }
36
 
37
  .header-title h1 {
38
  font-size: 3vw;
39
  color: #fff;
40
+ margin: 0
41
  }
42
 
43
  .home-btn img {
44
  width: 5vw;
45
+ cursor: pointer
46
  }
47
 
48
  .home-btn img:hover {
49
+ transform: scale(1.1)
50
  }
51
 
 
 
 
52
  .grammar-bg {
53
  position: absolute;
54
  top: 10%;
 
58
  max-height: calc(100vh - 100px);
59
  object-fit: fill;
60
  z-index: -1;
61
+ opacity: .2
62
  }
63
 
64
  .main-container {
 
66
  margin: 7vh auto;
67
  border: 9px solid #009688;
68
  border-radius: 1.5vw;
69
+ background: #fff;
70
  padding: 2vw;
71
+ box-shadow: 0 .4vw 1vw rgba(0,0,0,.1);
72
+ height: 76vh
73
  }
74
 
75
+ /* ===== Outer card tighter on sides ===== */
76
+ /*.main-container {
77
+ width: 94%;*/ /* was 85% */
78
+ /*padding: 1.5vw;*/ /* slightly tighter */
79
+ /*}*/
80
 
81
+ /* ===== Intro split (match reference, minimal side margins) ===== */
82
+ .intro-section.split {
83
+ padding-inline: 1vw; /* very small left/right padding */
84
+ display: block;
 
 
 
 
85
  }
86
 
87
+ .split-shell {
88
+ width: 100%;
89
+ max-width: none; /* use full inner width */
90
+ margin: 0 auto;
91
+ display: grid;
92
+ grid-template-columns: minmax(320px, 38%) 1fr; /* image left, content right */
93
+ align-items: center;
94
+ /*gap: 2.2vw;*/
95
  }
96
 
97
+ /* Left image card */
98
+ .split-left {
99
+ display: grid;
100
+ place-items: center;
101
+ }
102
 
103
+ .intro-illustration {
 
104
  width: 60%;
105
  }
106
 
107
+ /* Right content */
108
+ .hero-title {
109
+ font-size: 2.5vw;
110
+ font-weight: 800;
111
+ color: #006780;
112
+ margin-bottom: 20px;
113
+ font-family: raleway;
114
+ }
115
 
116
+ .hero-copy {
117
+ font-size: 1.4vw;
118
+ text-align: justify;
119
+ margin-bottom: 3vw;
120
+ }
121
 
122
+ /* Rows like the reference: label + control(s) */
123
+ .form-row {
124
  display: flex;
 
125
  align-items: center;
126
+ gap: 12px;
127
+ margin-top: 12px;
128
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
+ .row-label {
131
+ min-width: 140px;
132
+ font-weight: 700;
133
+ color: #006780;
134
+ font-size: 1.3vw;
135
+ }
136
 
137
+ /* Inputs and selects (reuse your existing styles) */
138
+ .input-wrap, .select-wrap {
139
+ position: relative;
140
+ flex: 1;
 
 
 
 
 
141
  }
142
 
143
+ .input-wrap input, .select-wrap select {
144
+ width: 100%;
145
+ padding: 12px 40px 12px 36px;
146
+ border-radius: 12px;
147
+ border: 1px solid #cfe2e0;
148
+ background: #fff;
149
+ color: #0e3e45;
150
+ outline: 0;
151
+ transition: border-color .2s, box-shadow .2s;
152
  }
153
 
154
+ .input-wrap input::placeholder {
155
+ color: #93a3b8;
156
  }
157
 
158
+ .input-wrap input:focus, .select-wrap select:focus {
159
+ border-color: #009688;
160
+ box-shadow: 0 0 0 3px rgba(0,150,136,.12);
161
+ }
162
 
163
+ /* Icons */
164
+ .icon-search, .icon-level {
165
+ position: absolute;
166
+ left: 12px;
167
+ top: 50%;
168
+ transform: translateY(-50%);
169
+ width: 18px;
170
+ height: 18px;
171
+ opacity: .8;
172
+ pointer-events: none;
173
+ }
174
+
175
+ .icon-search::before {
176
+ content: "🔎";
177
+ font-size: 16px;
178
  }
179
 
180
+ .icon-level::before {
181
+ content: "🎯";
182
+ font-size: 16px;
183
  }
184
 
185
+ /* Level badge */
186
+ .badge {
187
+ position: absolute;
188
+ right: 10px;
189
+ top: 50%;
190
+ transform: translateY(-50%);
191
+ font-size: 12px;
192
+ padding: 4px 8px;
193
+ border-radius: 999px;
194
+ border: 1px solid #cfe2e0;
195
+ background: #f7fcfb;
196
+ color: #0e3e45;
197
+ }
198
+
199
+ .badge[data-level="easy"] {
200
+ border-color: #bfe6d8;
201
+ color: #1b7f57;
202
+ background: #ecfbf4;
203
  }
204
 
205
+ .badge[data-level="medium"] {
206
+ border-color: #c7e3f4;
207
+ color: #0b74a6;
208
+ background: #eef8ff;
209
  }
210
 
211
+ .badge[data-level="hard"] {
212
+ border-color: #f9dfc8;
213
+ color: #b35a11;
214
+ background: #fff3e9;
215
+ }
216
 
217
+ /* Generate button aligned like “Get Topic” */
218
+ .btn-get {
219
+ min-width: 150px;
220
+ padding: 5px 16px;
221
+ border-radius: 12px;
222
+ border: 1px solid #009688;
223
+ background: #006780;
224
+ color: white;
225
+ font-weight: 700;
226
+ font-size: 1.5vw;
227
+ cursor: pointer;
228
+ transition: transform .08s, box-shadow .2s, opacity .2s;
229
  }
230
 
231
+ .btn-get:disabled {
232
+ /*opacity: .6;*/
233
+ background-color: #bbbbbb;
234
+ cursor: not-allowed;
 
 
 
235
  }
236
 
237
+ .btn-get:not(:disabled):hover {
238
+ box-shadow: 0 10px 24px rgba(0,150,136,.22);
 
 
 
 
 
 
239
  }
240
 
241
+ .btn-get:not(:disabled):active {
242
+ transform: translateY(1px);
 
 
243
  }
244
 
245
+ /* Suggestions (keep your existing styles; scoped to intro) */
246
+ .intro-section.split .suggestion-box {
247
+ position: absolute;
248
+ top: calc(100% + 8px);
249
+ left: 0;
250
+ z-index: 20;
251
+ width: 100%;
252
+ max-height: 200px;
253
+ overflow: auto;
254
+ padding: 6px;
255
+ border-radius: 12px;
256
+ border: 1px solid #cfe2e0;
257
+ background: #fff;
258
+ box-shadow: 0 12px 28px rgba(0,0,0,.12);
259
+ }
260
 
261
+ .intro-section.split .suggestion-box span {
262
+ display: block;
263
+ padding: 10px 12px;
264
+ border-radius: 10px;
265
+ cursor: pointer;
266
+ color: #0e3e45;
267
+ }
268
 
269
+ .intro-section.split .suggestion-box span:hover,
270
+ .intro-section.split .suggestion-box span.active {
271
+ background: #f1fbfa;
272
+ }
273
 
274
+ /* Clear (×) inside input */
275
+ .input-wrap.clearable {
276
+ position: relative;
277
+ }
278
 
279
+ .input-wrap .has-clear {
280
+ padding-right: 44px;
281
+ }
282
 
283
+ .clear-btn {
284
+ position: absolute;
285
+ right: 12px;
286
+ top: 55%;
287
+ transform: translateY(-53%);
288
+ width: 2vw;
289
+ height: 2vw;
290
+ line-height: 0vw;
291
+ text-align: center;
292
+ border: 1px solid #cfe2e0;
293
+ border-radius: 45%;
294
+ background: #006780;
295
+ color: white;
296
+ cursor: pointer;
297
+ padding: 0;
298
+ font-size: 2vw;
299
+ z-index: 30;
300
+ }
301
 
302
+ .clear-btn:hover {
303
+ background: #f1fbfa;
304
+ color: #0e3e45;
305
+ border-color: #b9d6d3;
306
+ }
307
 
308
+ .clear-btn:active {
309
+ transform: translateY(-50%) scale(.98);
310
+ }
311
 
312
+ /* Responsive */
313
+ @media (max-width: 980px) {
314
+ .split-shell {
315
+ grid-template-columns: 1fr;
316
+ gap: 16px;
317
+ }
318
 
319
+ .row-label {
320
+ min-width: 110px;
321
+ }
322
+ }
323
 
324
 
325
+ :root {
326
+ --passage-font: 21px;
327
+ }
328
+ /* used by A−/A+ */
329
 
330
+ /* ---- Reading card ---- */
331
+ /*.reading-card {
332
+ width: 100%;
333
+ border-radius: 18px;
334
+ padding: 16px 16px 14px;
335
+ background: linear-gradient(#ffffff, #ffffff) padding-box, linear-gradient(135deg, #0aa19a, #6fd0cd) border-box;*/ /* teal gradient stroke */
336
+ /*border: 1px solid transparent;
337
+ box-shadow: 0 14px 32px rgba(0,0,0,.08);
338
+ }*/
339
+
340
+ /* Header */
341
+ .reading-head {
342
+ display: grid;
343
+ grid-template-columns: 44px 1fr auto;
344
+ align-items: center;
345
+ gap: 10px;
346
+ margin-bottom: 6px;
347
+ }
348
 
349
+ .reading-title {
350
+ margin: 0;
351
+ text-align: center;
352
+ color: #006780;
353
+ font-size: clamp(20px, 2.2vw, 39px);
354
+ letter-spacing: .3px;
355
+ font-weight: 800;
356
+ }
357
 
358
+ .head-actions {
359
+ display: flex;
360
+ gap: 8px;
361
+ }
362
 
363
+ .icon-btn {
364
+ width: 45px;
365
+ height: 45px;
366
+ display: grid;
367
+ place-items: center;
368
+ /* color: #eaf1ff; */
369
+ background: rgb(32 171 107 / 42%);
370
+ border: 4px solid rgba(255, 255, 255, .15);
371
+ border-radius: 10px;
372
+ cursor: pointer;
373
+ transition: transform .05s ease, box-shadow .15s ease, background .15s ease;
374
+ font-size: x-large;
375
+ font-weight: 800;
376
+ }
377
 
378
+ .icon-btn:hover {
379
+ box-shadow: 0 8px 18px rgba(0,0,0,.08);
380
+ background: #f7fcfb;
381
+ }
382
 
383
+ .icon-btn:active {
384
+ transform: translateY(1px);
385
+ }
386
 
387
+ .icon-btn.active {
388
+ border-color: #95d0c9;
389
+ background: #eefaf8;
390
+ }
391
 
392
+ .icon-btn.danger {
393
+ color: #b1241a;
394
+ }
395
 
396
+ .icon-btn1 {
397
+ width: 8vw;
398
+ height: 5vh;
399
+ display: grid;
400
+ place-items: center;
401
+ background: rgb(0 150 136);
402
+ border: 2px solid rgba(255, 255, 255, .15);
403
+ border-radius: 22px;
404
+ cursor: pointer;
405
+ transition: transform .05s ease, box-shadow .15s ease, background .15s ease;
406
+ font-size: x-large;
407
+ font-weight: 800;
408
+ top: -4vw;
409
+ position: relative;
410
+ right: -4vw;
411
+ }
412
 
413
+ .icon-btn1:hover {
414
+ box-shadow: 0 8px 18px rgba(0,0,0,.08);
415
+ background: white;
416
+ color: black;
417
+ }
418
 
419
+ /* .icon-btn1:active {
420
+ transform: translateY(1px);
421
+ }
422
 
423
+ .icon-btn1.active {
424
+ border-color: #95d0c9;
425
+ background: #eefaf8;
426
+ }
427
 
428
+ .icon-btn1.danger {
429
+ color: #b1241a;
430
+ }
431
+ */
432
+ /* Meta chips */
433
+ .reading-meta {
434
+ display: flex;
435
+ flex-wrap: wrap;
436
+ gap: 8px;
437
+ margin: 4px 4px 12px;
438
+ }
439
 
440
+ .chip {
441
+ display: inline-flex;
442
+ align-items: center;
443
+ gap: 6px;
444
+ padding: 6px 10px;
445
+ border-radius: 999px;
446
+ font-weight: 600;
447
+ font-size: 13px;
448
+ border: 1px solid #d9ecea;
449
+ background: #f7fcfb;
450
+ color: #0e3e45;
451
+ }
452
 
453
+ .chip-level[data-level="easy"] {
454
+ border-color: #bfe6d8;
455
+ background: #ecfbf4;
456
+ color: #1b7f57;
457
+ }
458
 
459
+ .chip-level[data-level="medium"] {
460
+ border-color: #c7e3f4;
461
+ background: #eef8ff;
462
+ color: #0b74a6;
463
+ }
464
 
465
+ .chip-level[data-level="hard"] {
466
+ border-color: #f9dfc8;
467
+ background: #fff3e9;
468
+ color: #b35a11;
469
+ }
470
 
471
+ /* Passage area */
472
+ .passage-shell {
473
+ position: relative;
474
+ border: 1px solid #e7f1f0;
475
+ border-radius: 14px;
476
+ background: #f3efef;
477
+ padding: 14px 16px;
478
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.7);
479
+ max-height: 46vh;
480
+ overflow: auto;
481
+ font-size: 1.4vw;
482
+ line-height: 1.8;
483
+ }
484
 
485
+ /* Soft scrollbars */
486
+ .passage-shell::-webkit-scrollbar {
487
+ width: 12px;
488
+ }
489
 
490
+ .passage-shell::-webkit-scrollbar-thumb {
491
+ background: #cfe2e0;
492
+ border-radius: 10px;
493
+ border: 3px solid #fff;
494
+ }
495
 
496
+ .passage-shell::-webkit-scrollbar-track {
497
+ background: #f7fcfb;
498
+ }
499
 
500
+ /* Typography (A−/A+) */
501
+ .passage-text {
502
+ font-family: Georgia, "Times New Roman", serif;
503
+ font-size: var(--passage-font);
504
+ line-height: 1.5;
505
+ color: #1e3a3f;
506
+ /*letter-spacing: .2px;*/
507
+ text-align: left;
508
+ hyphens: auto;
509
+ white-space: pre-wrap;
510
+ word-wrap: break-word;
511
+ }
512
 
513
+ .passage-text span {
514
+ transition: background-color 0.3s ease;
515
+ }
516
 
517
+ .passage-text .highlight {
518
+ background-color: yellow;
519
+ }
520
 
521
+ .passage-text::first-letter {
522
+ float: left;
523
+ font-size: 2.6em;
524
+ line-height: .9;
525
+ padding-right: 6px;
526
+ padding-top: 2px;
527
+ color: #0f7f78;
528
+ font-weight: 700;
529
+ }
530
 
531
+ /* Footer buttons */
532
+ .reading-actions {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
  display: flex;
534
+ gap: 10px;
535
  justify-content: center;
536
+ margin-top: 30px;
537
  }
538
 
539
+ .btn-primary, .btn-danger {
540
+ min-width: 210px;
541
+ padding: 12px 18px;
542
+ border-radius: 12px;
543
+ font-weight: 700;
544
+ font-size: 1.3vw;
545
+ cursor: pointer;
546
+ border: 1px solid transparent;
547
+ transition: transform .06s, box-shadow .18s, opacity .18s;
548
  }
549
 
550
+ .btn-primary {
551
+ color: #fff;
552
+ background: #006780;
553
+ border-color: #006780;
 
 
 
 
554
  }
555
 
556
+ .btn-primary:hover {
557
+ box-shadow: 0 12px 24px rgba(0,103,128,.25);
558
  }
559
 
560
+ .btn-primary:disabled {
561
+ opacity: .6;
562
  cursor: not-allowed;
563
  }
564
 
565
+ .btn-danger {
566
+ color: #fff;
567
+ background: #006780;
568
+ border-color: #006780;
 
 
 
 
 
 
 
569
  }
570
 
571
+ .btn-danger:hover {
572
+ box-shadow: 0 12px 24px rgba(0,103,128,.25);
 
 
 
 
 
573
  }
574
 
575
+ /* Back button (left) reuses .icon-btn */
576
+ .back {
577
+ grid-column: 1 / 2;
 
 
 
 
 
 
 
 
 
 
 
 
 
578
  }
579
 
580
+ /* Responsive */
581
+ @media (max-width: 720px) {
582
+ .reading-title {
583
+ font-size: 22px;
 
584
  }
585
 
586
+ .btn-primary, .btn-danger {
587
+ min-width: 160px;
588
+ }
589
  }
590
 
591
+ /* Keep all MCQ UI inside the white card */
592
+ .main-container {
593
+ background: #fff;
594
+ overflow: visible;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
595
  }
596
+ /* you already have this */
597
 
598
+ /* ---- MCQ card ---- */
599
+ .mcq-card {
600
+ width: 100%;
601
+ margin: 0; /* no side gaps */
602
+ background: #fff; /* same white as container */
603
+ border: 1px solid #d9ecea;
604
+ border-radius: 16px;
605
+ padding: 16px;
606
+ box-shadow: 0 10px 24px rgba(0,0,0,.06);
607
+ }
608
 
609
+ /* Header inside the card */
610
+ .mcq-card__header {
611
+ display: grid;
612
+ grid-template-columns: 44px 1fr 44px;
613
+ align-items: center;
614
+ gap: 10px;
615
+ margin-bottom: 10px;
616
+ font-size: 2vw;
617
+ }
618
 
619
+ .mcq-card__title {
620
+ margin: 0;
621
+ text-align: center;
622
+ color: #0e3e45;
623
+ font-weight: 800;
624
+ letter-spacing: .2px;
625
+ }
626
 
627
+ .mcq-card__actions {
 
 
 
628
  display: flex;
629
+ justify-content: flex-end;
630
  }
631
 
632
+ /* Question pill */
633
+ .quiz-pill {
634
  display: flex;
635
+ align-items: flex-start;
636
+ gap: 10px;
637
+ padding: 12px 14px;
638
+ border-radius: 12px;
639
+ border: 1px solid #e6f0ef;
640
+ background: #f8fbfb;
641
+ color: #0e3e45;
642
+ font-size: 1.5vw;
643
  }
644
 
645
+ .quiz-question-pill {
646
+ margin-bottom: 3vw;
647
+ }
 
 
 
 
 
648
 
649
+ .qq-label {
650
+ font-weight: 700;
651
+ color: #0b74a6;
652
+ white-space: nowrap;
653
  }
654
 
655
+ .qq-text {
656
+ flex: 1;
657
+ line-height: 1.6;
658
  }
659
 
660
+ /* Options list */
661
+ .quiz-options {
662
+ list-style: none;
663
+ margin: 0;
664
+ padding: 0;
665
+ display: grid;
666
+ grid-template-columns: 1fr 1fr;
667
+ gap: 10px;
668
  }
669
 
670
+ @media (max-width: 720px) {
671
+ .quiz-options {
672
+ grid-template-columns: 1fr;
673
+ }
 
 
 
 
 
674
  }
675
 
676
+ /* Option pill */
677
+ .quiz-option-pill {
 
 
 
 
 
 
678
  display: flex;
 
679
  align-items: center;
680
+ gap: 10px;
681
+ padding: 12px 14px;
682
+ border-radius: 12px;
683
+ border: 1px solid #dfe9e7;
684
+ background: #ffffff;
685
+ cursor: pointer;
686
+ transition: box-shadow .15s, transform .05s, border-color .15s, background .15s;
 
 
 
 
687
  }
688
 
689
+ .quiz-option-pill:hover {
690
+ box-shadow: 0 8px 18px rgba(0,0,0,.06);
691
+ background: #f7fbfb;
 
 
692
  }
693
 
694
+ .quiz-option-pill .slot {
695
+ font-weight: 700;
696
+ color: #0b74a6;
697
+ min-width: 26px;
 
 
 
 
 
 
 
 
 
698
  }
699
 
700
+ .quiz-option-pill .opt-text {
701
+ color: #0e3e45;
702
+ }
703
 
704
+ /* States */
705
+ .quiz-option-pill.is-selected {
706
+ border-color: #a8d8d3;
707
+ background: #eefaf8;
708
+ }
 
709
 
710
+ .quiz-option-pill.is-correct {
711
+ border-color: #6cc090;
712
+ background: #ecfbf4;
713
+ }
714
 
715
+ .quiz-option-pill.is-incorrect {
716
+ border-color: #f2b0a9;
717
+ background: #fff1ef;
718
+ }
 
 
 
719
 
720
+ /* Hide the native radio */
721
+ .visually-hidden {
722
+ position: absolute !important;
723
+ inset: auto auto auto auto !important;
724
+ width: 1px;
725
+ height: 1px;
726
+ overflow: hidden;
727
+ clip: rect(1px,1px,1px,1px);
728
+ white-space: nowrap;
729
  }
730
 
731
+ /* Answer status */
732
+ .answer-status {
733
  display: flex;
734
+ flex-wrap: wrap;
735
+ align-items: normal;
736
+ gap: 8px;
737
+ margin-top: 12px;
738
  justify-content: center;
739
+ font-size: 1vw;
740
  margin-top: 2vw;
741
  }
742
 
743
+ .status-chip {
744
+ padding: 6px 10px;
745
+ border-radius: 999px;
746
+ font-weight: 700;
747
+ font-size: 12px;
748
+ }
749
 
750
+ .status-chip.ok {
751
+ background: #ecfbf4;
752
+ color: #1b7f57;
753
+ border: 1px solid #bfe6d8;
754
+ }
755
 
756
+ .status-chip.nope {
757
+ background: #ffe9e7;
758
+ color: #b1241a;
759
+ border: 1px solid #f4b2ab;
760
+ }
761
 
762
+ .status-help {
763
+ color: #3a5b60;
764
+ }
765
 
766
+ /* Footer buttons */
767
+ .mcq-card__footer {
768
  display: flex;
769
+ gap: 10px;
770
+ justify-content: end;
771
+ margin-top: 6vw;
 
772
  }
773
 
774
+ .submit-btn, .next-btn, .reset-btn {
775
+ min-width: 160px;
776
+ padding: 12px 16px;
777
+ border-radius: 12px;
778
+ font-weight: 700;
779
  cursor: pointer;
780
+ /*border: 1px solid transparent;*/
781
+ transition: transform .06s, box-shadow .18s, opacity .18s;
782
+ font-size: 1.5vw;
783
  }
784
 
785
+ .submit-btn {
786
+ background: #006780;
787
+ color: #fff;
788
+ border-color: #006780;
 
 
 
 
 
 
 
 
789
  }
790
 
791
+ .submit-btn:hover {
792
+ box-shadow: 0 12px 24px rgba(0,103,128,.25);
793
+ }
794
 
795
+ .submit-btn:disabled {
796
+ box-shadow: none;
797
+ transform: none;
798
+ background-color: #cccccc;
799
+ color: #666666;
800
+ cursor: not-allowed;
801
+ }
802
 
803
+ .next-btn {
804
+ background: #006780;
805
+ color: #fff;
806
+ border-color: #006780;
807
  }
808
 
809
+ .next-btn:hover {
810
+ box-shadow: 0 12px 24px rgba(0,103,128,.25);
 
 
811
  }
812
 
813
+ .reset-btn {
814
+ background: #e5483b;
815
+ color: #fff;
816
+ border-color: #e5483b;
 
 
 
817
  }
818
 
819
+ .reset-btn:hover {
820
+ box-shadow: 0 12px 24px rgba(229,72,59,.25);
821
+ }
822
+ /*
823
+ .popup-overlay {
824
+ position: fixed;
825
+ top: 0;
826
+ left: 0;
827
+ width: 100%;
828
+ height: 100%;
829
+ background: rgba(0,0,0,.5);
830
  display: flex;
831
+ justify-content: center;
832
+ align-items: center
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
833
  }
834
 
835
+ .popup-content {
836
+ background: #fff;
837
+ padding: 2vw;
838
+ border-radius: 1vw;
839
+ box-shadow: 0 .5vw 1.5vw rgba(0,0,0,.1);
840
+ width: 40%;
841
+ text-align: center
 
 
 
 
 
 
842
  }
843
+ */
844
 
 
 
 
 
 
 
 
 
 
 
 
 
 
845
 
 
 
 
846
  /* Loader Overlay */
847
  .loader-overlay {
848
  position: fixed;
 
850
  left: 0;
851
  width: 100%;
852
  height: 100%;
 
853
  display: flex;
854
  justify-content: center;
855
  align-items: center;
 
857
  }
858
 
859
  .loader {
860
+ font-size: 15px;
861
+ width: 1.5em;
862
+ height: 1.5em;
863
  border-radius: 50%;
864
  position: relative;
865
  text-indent: -9999em;
 
902
  }
903
 
904
 
905
+ /* reusable icon button base */
906
+ /*.icon-btn {
907
+ width: var(--icon-btn, 36px);
908
+ height: var(--icon-btn, 36px);
909
+ display: grid;
910
+ place-items: center;
911
+ border-radius: 10px;
912
+ border: 1px solid #cfe2e0;
913
+ background: #fff;
914
+ color: #006780;*/ /* text color if any */
915
+ /*cursor: pointer;
916
+ transition: box-shadow .15s, transform .05s, background .15s, border-color .15s;
917
+ }*/
918
+
919
+ /* back button specifics */
920
+ /*.icon-btn.back .icon-img {
921
+ width: var(--icon-size, 18px);
922
+ height: var(--icon-size, 18px);
923
+ object-fit: contain;
924
+ display: block;
925
+ transition: transform .12s, opacity .12s;
926
+ }*/
927
+
928
+ /* states */
929
+ /*.icon-btn.back:hover {
930
+ background: #f7fcfb;
931
+ box-shadow: 0 8px 18px rgba(0,0,0,.08);
932
+ border-color: #b9d6d3;
933
+ }
934
+
935
+ .icon-btn.back:hover .icon-img {
936
+ transform: translateX(-1px);*/ /* subtle nudge left */
937
+ /*}
938
+
939
+ .icon-btn.back:active {
940
+ transform: translateY(1px);
941
+ }
942
+
943
+ .icon-btn.back:disabled {
944
+ opacity: .5;
945
+ pointer-events: none;
946
+ }*/
947
 
948
+ /* optional: bigger/smaller controls via CSS variables */
949
+ /*:root {
950
+ --icon-btn: 36px;*/ /* button size */
951
+ /*--icon-size: 18px;*/ /* arrow size */
952
+ /*}*/
953
 
954
+ /* If you need a dark-on-teal variant inside colored headers: */
955
+ /*.header-on-teal .icon-btn.back {
956
+ background: #007e74;
957
+ border-color: #00756c;
958
+ }
959
 
960
+ .header-on-teal .icon-btn.back:hover {
961
+ background: #0a8a80;
962
+ }
963
+
964
+ .header-on-teal .icon-btn.back .icon-img {
965
+ filter: brightness(0) invert(1);*/ /* make white */
966
+ /*}*/
967
+ /* Overlay */
968
+ .popup-overlay {
969
+ position: fixed;
970
+ top: 0;
971
+ left: 0;
972
+ width: 100vw;
973
+ height: 100vh;
974
+ background-color: rgba(0, 0, 0, 0.5);
975
  display: flex;
976
+ justify-content: center;
977
  align-items: center;
978
+ z-index: 1000;
979
  }
980
 
981
+ /* Card */
982
+ .popup-content {
983
+ background-color: #fff;
984
+ padding: 2vw;
985
+ border-radius: 0.5vw;
986
+ box-shadow: 0 0.4vw 0.8vw rgba(0, 0, 0, 0.3);
987
  text-align: center;
988
+ max-width: 40vw;
989
+ width: 80%;
990
  }
991
 
992
 
993
 
994
+ .popup-content p {
995
+ font-size: 1.4vw;
996
+ color: #dc3545;
997
+ margin-bottom: 1vw;
998
+ }
999
+
1000
+ /* Close button */
1001
+ .close-btn1 {
1002
+ padding: 0.8vw 1.5vw;
1003
+ font-size: 1.2vw;
1004
+ background-color: #007bff;
1005
+ color: #fff;
1006
  border: none;
1007
+ border-radius: 0.5vw;
 
1008
  cursor: pointer;
1009
+ transition: all 0.3s ease;
 
 
 
 
 
1010
  }
1011
 
1012
+ .close-btn1:hover {
1013
+ filter: brightness(0.98);
1014
+ }
1015
 
1016
+ .close-btn1:active {
1017
+ transform: translateY(1px);
1018
+ }
1019
+
1020
+ /* small scale-in animation */
1021
+ @keyframes popupScale {
1022
+ from {
1023
+ transform: scale(.96);
1024
+ opacity: .85;
1025
+ }
1026
+
1027
+ to {
1028
+ transform: scale(1);
1029
+ opacity: 1;
1030
+ }
1031
  }
1032
 
1033
+
1034
+ /* Locked/disabled state for Topic input until a level is chosen */
1035
+ .input-wrap.locked input {
1036
+ background: #f7f9fb;
1037
+ border-color: #dfe9e7;
1038
+ color: #93a3b8;
1039
+ cursor: not-allowed;
1040
  }
1041
 
1042
+ .input-wrap.locked .icon-search {
1043
+ opacity: .5;
1044
+ }
1045
+
1046
+ /* optional: a subtle invisible scrim so nothing inside is clickable */
1047
+ .input-wrap.locked::after {
1048
+ content: "";
1049
+ position: absolute;
1050
+ inset: 0;
1051
+ border-radius: 12px;
1052
+ pointer-events: auto; /* block clicks */
1053
+ }
1054
+
1055
+ /* Helper note under the field */
1056
+ .field-hint {
1057
+ margin: 6px 2px 0;
1058
+ font-size: 12px;
1059
+ color: #738a95;
1060
+ display: inline-flex;
1061
+ align-items: center;
1062
+ gap: 6px;
1063
+ }
1064
+
1065
+ .lock-icon::before {
1066
+ content: "🔒";
1067
+ font-size: 14px;
1068
+ line-height: 1;
1069
  }
1070
 
1071
+ /* Keep space for the clear (×) button when enabled */
1072
+ .input-wrap .has-clear {
1073
+ padding-right: 44px;
1074
+ }
1075
+
1076
+
1077
+ /* === Larger Back Arrow Button === */
1078
+ .icon-btn.back {
1079
+ width: 3vw; /* bigger button */
1080
+ height: 3vw;
1081
+ border-radius: 14px;
1082
+ border: 1px solid #cfe2e0;
1083
+ background: #fff;
1084
  display: grid;
1085
+ place-items: center;
1086
+ cursor: pointer;
1087
+ box-shadow: 0 2px 8px rgba(0,0,0,.06);
1088
+ transition: box-shadow .15s, transform .05s, border-color .15s, background .15s;
1089
  }
1090
 
1091
+ .icon-btn.back:hover {
1092
+ background: #f7fcfb;
1093
+ border-color: #b9d6d3;
1094
+ box-shadow: 0 8px 18px rgba(0,0,0,.08);
 
 
 
 
 
 
 
 
 
1095
  }
1096
 
1097
+ .icon-btn.back:active {
1098
+ transform: translateY(1px);
1099
  }
1100
 
1101
+ /* visible keyboard focus */
1102
+ .icon-btn.back:focus-visible {
1103
+ outline: 3px solid rgba(0,150,136,.35);
1104
+ outline-offset: 2px;
1105
+ }
1106
+
1107
+ /* arrow image inside the button */
1108
+ .icon-btn.back .icon-img {
1109
+ width: 3vw; /* bigger arrow */
1110
+ height: 3vw;
1111
+ object-fit: contain;
1112
+ display: block;
1113
+ transition: transform .12s, opacity .12s;
1114
+ }
1115
+
1116
+ /* subtle nudge on hover */
1117
+ .icon-btn.back:hover .icon-img {
1118
+ transform: translateX(-1px);
1119
+ }
1120
 
1121
+ /* mobile: slightly smaller to fit tight headers */
1122
+ @media (max-width: 720px) {
1123
+ .icon-btn.back {
1124
+ width: 44px;
1125
+ height: 44px;
1126
+ border-radius: 12px;
1127
+ }
1128
+
1129
+ .icon-btn.back .icon-img {
1130
+ width: 22px;
1131
+ height: 22px;
1132
+ }
1133
  }
1134
 
1135
+ /* optional: if the header has a teal background */
1136
+ .header-on-teal .icon-btn.back {
1137
+ background: #007e74;
1138
+ border-color: #00756c;
1139
  }
1140
 
1141
+ .header-on-teal .icon-btn.back .icon-img {
1142
+ filter: brightness(0) invert(1); /* make arrow white */
1143
+ }
1144
+
1145
+
1146
+ .passage-text .highlight {
1147
+ background-color: yellow;
1148
  }
1149
 
1150
+
1151
+ /* ===== Congratulations modal ===== */
1152
+ .congrats-overlay {
1153
+ position: fixed;
1154
+ inset: 0;
1155
+ display: grid;
1156
+ place-items: center;
1157
+ /*background: rgba(0, 0, 0, 0.45);*/
1158
  }
1159
 
1160
+ .congrats-card {
1161
+ text-align: center;
1162
+ margin: 47px auto;
1163
+ padding: 40px;
1164
+ background: white;
1165
+ border-radius: 15px;
1166
+ width: 70vw;
1167
+ height: 70vh;
1168
+ display: flex;
1169
+ flex-direction: column;
1170
+ justify-content: center;
1171
+ align-items: center;
1172
+ font-family: 'Pacifico', cursive;
1173
+ position: relative;
1174
+ z-index: 1;
1175
+ box-shadow: 0 .4vw 1vw rgba(0, 0, 0, .1);
1176
+ border: 9px solid #009688;
1177
+ top: 3vw;
1178
  }
1179
 
1180
+ .congrats-card h2 {
1181
+ margin: 8px 0 6px;
1182
+ color: #0e3e45;
1183
+ font-weight: 800;
1184
+ color: #4ca1af;
1185
+ margin-bottom: 15px;
1186
+ font-size: 2.5vw;
1187
+ white-space: nowrap;
1188
+ overflow: hidden;
1189
+ text-overflow: ellipsis;
1190
+ text-align: center;
1191
  }
1192
 
1193
+ .congrats-card p {
1194
+ margin: 0 0 16px;
1195
+ color: #0e3e45;
1196
+ /* color: #4ca1af; */
1197
+ margin-bottom: 15px;
1198
+ font-size: 2.5vw;
1199
+ white-space: nowrap;
1200
+ overflow: hidden;
1201
+ text-overflow: ellipsis;
1202
+ text-align: center;
1203
  }
1204
 
1205
+ /* Score badge */
1206
+ .score-badge {
1207
+ display: inline-flex;
1208
+ align-items: baseline;
1209
+ gap: 6px;
1210
+ margin-bottom: 10px;
1211
+ padding: 10px 14px;
1212
+ border-radius: 999px;
1213
+ background: #eef7f6;
1214
+ color: #0e3e45;
1215
+ font-weight: 800;
1216
+ /* color: #4ca1af; */
1217
+ margin-bottom: 15px;
1218
+ font-size: 2.5vw;
1219
+ white-space: nowrap;
1220
+ overflow: hidden;
1221
+ text-overflow: ellipsis;
1222
+ text-align: center;
1223
+ }
1224
+
1225
 
1226
+
1227
+ .start-over-btn {
1228
+ min-width: 180px;
1229
+ padding: 12px 16px;
1230
+ border-radius: 12px;
1231
+ background: #006780;
1232
+ color: #fff;
1233
+ border: 1px solid #006780;
1234
+ font-weight: 800;
1235
+ cursor: pointer;
1236
+ transition: transform .06s, box-shadow .18s;
1237
+ font-size: 2vw;
1238
  }
1239
+
1240
+ .start-over-btn:hover {
1241
+ box-shadow: 0 12px 24px rgba(0,103,128,.25);
1242
+ }
src/app/reading/reading.component.html CHANGED
@@ -1,191 +1,301 @@
1
  <div class="reading-container">
 
2
  <div class="header-container">
3
  <div class="logo">
4
  <a (click)="goToHome()" routerLink="/home">
5
  <img src="assets/images/pykara-logo.png" alt="Pykara Logo" />
6
  </a>
7
  </div>
 
8
  <div class="header-title">
9
  <h1>Reading</h1>
10
  </div>
 
11
  <div class="home-btn">
12
  <a (click)="goToHome()" routerLink="/home">
13
  <img src="assets/images/home.png" alt="Home" class="home-icon" />
14
  </a>
15
  </div>
16
  </div>
17
- <img src="assets/images/grammar-bg.png" alt="Chat Background" class="grammar-bg" />
18
 
 
 
19
 
20
- <!-- Main Container -->
21
- <div class="main-container">
 
 
 
 
 
 
 
 
 
22
 
23
- <!-- Intro Section -->
24
- <div class="intro-section" *ngIf="!content && !hasStarted">
25
- <div class="intro-left-image">
26
- <img src="assets/images/reading/teacher.png" alt="Reading Illustration" />
27
- </div>
28
- <div class="right-content">
29
- <h2>Welcome to the Reading Exercise</h2>
30
- <p>
31
- The reading component is designed to enhance comprehension skills through topic-based, auto-generated passages and interactive questions.
32
- It begins with selecting a topic and difficulty level, after which a custom passage is generated.
33
- After reading the passage, related questions appear to test understanding, strengthen vocabulary, and build critical thinking.
34
- This structured method encourages active engagement with the content and supports independent learning.
35
- With clear visuals and a simple interface, the activity becomes both effective and engaging.
36
- Overall, it serves as a valuable tool for improving language proficiency, reasoning ability, and long-term retention in an enjoyable and guided manner.
37
- </p>
38
-
39
- <div class="center-controls">
40
- <input type="text"
41
- [(ngModel)]="topic"
42
- placeholder="Enter or Select a Topic"
43
- (focus)="showSuggestions = true"
44
- (input)="onTyping()"
45
- (blur)="hideSuggestionsWithDelay()" />
46
-
47
- <select [(ngModel)]="difficulty">
48
- <option value="easy" selected>Easy</option>
49
- <option value="medium">Medium</option>
50
- <option value="hard">Hard</option>
51
- </select>
52
-
53
- <button class="submit-button"
54
- (click)="generateContent()"
55
- [disabled]="!topic.trim() || !difficulty.trim() || isGenerateDisabled">
56
- <span>Generate</span>
57
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  </div>
 
 
 
 
 
 
 
 
 
 
59
 
60
- <!-- Suggestions -->
61
- <div class="suggestion-box" *ngIf="showSuggestions">
62
- <span (mousedown)="selectSuggestion('Water Cycle')">Water Cycle</span>
63
- <span (mousedown)="selectSuggestion('Photosynthesis')">Photosynthesis</span>
64
- <span (mousedown)="selectSuggestion('Global Warming')">Global Warming</span>
65
- <span (mousedown)="selectSuggestion('My School')">My School</span>
66
- <span (mousedown)="selectSuggestion('Solar System')">Solar System</span>
67
- <span (mousedown)="selectSuggestion('Save Environment')">Save Environment</span>
68
  </div>
69
  </div>
70
- </div>
 
 
 
 
 
 
 
 
71
 
72
- <!-- Paragraph Section -->
73
 
74
- <div *ngIf="content && !hasStarted" class="content-block">
75
- <div class="content-header">
76
- <div class="back-arrow" (click)="goToIntroSection()">
77
- <img src="assets/images/reading/back.png" alt="Back to Intro" class="arrow-icon" />
 
 
 
 
 
 
 
 
 
 
 
 
78
  </div>
79
- <h2 class="centered-title">Let’s Start Reading!</h2>
80
- <div class="right-spacer"></div>
81
  </div>
82
- <div class="content-scroll">
83
- <p>{{ content }}</p>
 
 
 
 
 
 
 
 
 
 
 
 
84
  </div>
85
 
86
- <div class="button-container">
87
- <button (click)="generateQuestions()"
88
- class="submit-button"
89
- [disabled]="isGenerateQuestionDisabled">
 
90
  Generate Questions
91
  </button>
92
- <button (click)="resetAll()" class="reset-button">Reset</button>
93
- </div>
94
- </div>
95
 
96
- <!-- Question Section -->
97
- <div class="question-navigation" *ngIf="hasStarted && !hasCompleted && questions?.length">
98
- <div class="question-header">
99
- <div class="back-arrow" (click)="goToContentBlock()">
100
- <img src="assets/images/reading/back.png" alt="Back to Content" class="arrow-icon" />
101
- </div>
102
- <h2 class="centered-title">Question {{ currentQuestionIndex + 1 }} of {{ questions.length }}</h2>
103
- <div class="right-spacer">
104
- <button class="close-btn" (click)="goBack()" aria-label="Close">✖</button>
105
  </div>
 
 
106
  </div>
 
 
 
 
 
 
 
 
 
 
107
 
 
 
 
108
 
109
- <!-- Question Text -->
110
- <p class="question-text">{{ questions[currentQuestionIndex].question }}</p>
111
-
112
- <!-- Options and Image -->
113
- <div class="question-content-row">
114
- <div class="options-container">
115
- <label *ngFor="let option of questions[currentQuestionIndex].options"
116
- [ngClass]="{
117
- 'selected-option': !questions[currentQuestionIndex].isChecked && selectedAnswers[questions[currentQuestionIndex].question] === option,
118
- 'correct': questions[currentQuestionIndex].isChecked && questions[currentQuestionIndex].correct_answer === option,
119
- 'incorrect': questions[currentQuestionIndex].isChecked && selectedAnswers[questions[currentQuestionIndex].question] === option && option !== questions[currentQuestionIndex].correct_answer
120
- }"
121
- style="display: flex; align-items: center; gap: 0.8vw; cursor: pointer;">
122
- <input type="radio"
123
- [name]="'question_' + currentQuestionIndex"
124
- [value]="option"
125
- [checked]="getSelectedAnswer() === option"
126
- (change)="setSelectedAnswer(option)"
127
- [disabled]="questions[currentQuestionIndex].isChecked"
128
- class="radio-blue" />
129
- {{ option }}
130
- </label>
131
  </div>
 
132
 
133
- <div class="question-side-image">
134
- <img [src]="getFeedbackImage()" alt="Rabbit Feedback Image" />
 
 
 
 
135
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  </div>
137
 
138
- <!-- Navigation Buttons -->
139
- <div class="question-footer">
140
- <button class="prev-btn"
141
- (click)="prevQuestion()"
142
- [disabled]="currentQuestionIndex === 0">
143
- ◀ Previous
 
 
 
144
  </button>
145
 
146
- <div class="right-buttons">
147
- <button *ngIf="!questions[currentQuestionIndex].isChecked"
148
- class="submit-btn"
149
- (click)="validateAnswer()"
150
- [disabled]="!selectedAnswers[questions[currentQuestionIndex].question]">
151
- Submit
152
- </button>
153
 
154
- <button *ngIf="questions[currentQuestionIndex].isChecked && currentQuestionIndex < questions.length - 1"
155
- class="next-btn"
156
- (click)="nextQuestion()">
157
- Next ▶
158
- </button>
 
159
 
160
- <button *ngIf="questions[currentQuestionIndex].isChecked && currentQuestionIndex === questions.length - 1"
161
- class="reset-btn"
162
- (click)="regenerateQuestionsOnly()">
163
- Regenerate Questions
164
- </button>
165
- </div>
166
- </div>
167
- </div>
168
- </div>
169
 
170
- <!-- Completion Message -->
171
- <div *ngIf="hasCompleted" class="generate-content-container center-completion">
172
- <div class="completion-message">
173
- <p class="feedback-message">✅ You have completed all questions!</p>
174
- <button (click)="resetAll()" class="reset-button">Reset</button>
175
  </div>
176
  </div>
177
 
178
- <!-- Loader -->
179
- <div *ngIf="loadingQuestions" class="loader-overlay">
180
- <div class="loader"></div>
181
- </div>
182
 
183
- <!-- Error Popup -->
184
- <div *ngIf="showPopup" class="popup-overlay">
185
- <div class="popup-content">
186
- <p>{{ errorMessage }}</p>
187
- <button class="close-btn" (click)="showPopup = false">Close</button>
 
 
 
 
 
 
 
 
 
 
 
 
188
  </div>
 
 
 
 
 
189
  </div>
190
 
191
  </div>
 
1
  <div class="reading-container">
2
+ <!-- Header -->
3
  <div class="header-container">
4
  <div class="logo">
5
  <a (click)="goToHome()" routerLink="/home">
6
  <img src="assets/images/pykara-logo.png" alt="Pykara Logo" />
7
  </a>
8
  </div>
9
+
10
  <div class="header-title">
11
  <h1>Reading</h1>
12
  </div>
13
+
14
  <div class="home-btn">
15
  <a (click)="goToHome()" routerLink="/home">
16
  <img src="assets/images/home.png" alt="Home" class="home-icon" />
17
  </a>
18
  </div>
19
  </div>
 
20
 
21
+ <!-- Background -->
22
+ <img src="assets/images/grammar-bg.png" alt="Background" class="grammar-bg" />
23
 
24
+ <!-- Main -->
25
+ <div class="main-container" *ngIf="!showCongrats">
26
+ <!-- INTRO (split left image / right form) -->
27
+ <section class="intro-section split" *ngIf="!content && !hasStarted">
28
+ <div class="split-shell">
29
+ <!-- Left -->
30
+ <div class="split-left">
31
+ <img class="intro-illustration"
32
+ src="assets/images/reading/teacher.png"
33
+ alt="Reading and quiz illustration" />
34
+ </div>
35
 
36
+ <!-- Right -->
37
+ <div class="split-right">
38
+ <h1 class="hero-title">Welcome to the Reading Exercise</h1>
39
+ <p class="hero-copy">
40
+ The Reading component is a simple tool that turns any meaningful topic into a short,
41
+ age-appropriate passage. It checks the topic first to avoid nonsense and unsafe inputs,
42
+ then creates clear content at the chosen level (Easy, Medium, or Hard). After reading,
43
+ it generates multiple-choice questions and gives instant feedback. Read-Aloud and A−/A+
44
+ controls support different learning needs. This helps students build comprehension and
45
+ vocabulary, and saves teachers and parents time during practice, homework, and revision.
46
+ </p>
47
+
48
+ <!-- Row: Topic -->
49
+ <div class="form-row">
50
+ <label class="row-label">Topic:</label>
51
+
52
+ <div class="input-wrap clearable" [class.locked]="!difficulty">
53
+ <span class="icon-search" aria-hidden="true"></span>
54
+
55
+ <input type="text"
56
+ class="has-clear"
57
+ [(ngModel)]="topic"
58
+ [placeholder]="difficulty ? 'Enter or select a topic' : 'Select a level first'"
59
+ [disabled]="!difficulty"
60
+ (focus)="openSuggestions()"
61
+ (input)="onTyping()"
62
+ (keydown)="onKeydown($event)"
63
+ (blur)="hideSuggestionsWithDelay()"
64
+ autocomplete="off"
65
+ aria-autocomplete="list"
66
+ [attr.aria-expanded]="showSuggestions && !!difficulty"
67
+ [attr.aria-disabled]="!difficulty" />
68
+
69
+ <!-- Clear (×) only when enabled and has text -->
70
+ <button class="clear-btn"
71
+ *ngIf="difficulty && topic.trim().length"
72
+ (mousedown)="$event.preventDefault()"
73
+ (click)="onClearTopic()">
74
+ ×
75
+ </button>
76
+
77
+ <!-- Suggestions only after a level is chosen -->
78
+ <div class="suggestion-box" *ngIf="difficulty && showSuggestions">
79
+ <ng-container *ngIf="filteredSuggestions?.length">
80
+ <span *ngFor="let s of filteredSuggestions; let i = index"
81
+ (mousedown)="selectSuggestion(s)"
82
+ [class.active]="i === activeIndex">{{ s }}</span>
83
+ </ng-container>
84
+ <!--
85
+ <ng-template #noMatches>
86
+ <span (mousedown)="selectSuggestion(topic)">Use “{{ topic }}”</span>
87
+ </ng-template>
88
+ -->
89
+ </div>
90
+ </div>
91
+
92
+ <!-- Tiny helper note shown only when level not chosen -->
93
+ <div class="field-hint" *ngIf="!difficulty">
94
+ <span class="lock-icon" aria-hidden="true"></span>
95
+ Please select a level to enable topic suggestions.
96
+ </div>
97
+ </div>
98
+
99
+ <!-- Row: Level + Generate -->
100
+ <div class="form-row">
101
+ <label class="row-label">Select Level:</label>
102
+
103
+ <div class="select-wrap">
104
+ <span class="icon-level" aria-hidden="true"></span>
105
+ <select [(ngModel)]="difficulty" (ngModelChange)="onDifficultyChange($event)" required>
106
+ <option [ngValue]="'easy'">Easy</option>
107
+ <option [ngValue]="'medium'">Medium</option>
108
+ <option [ngValue]="'hard'">Hard</option>
109
+ </select>
110
+ <span class="badge" *ngIf="difficulty" [attr.data-level]="difficulty">
111
+ {{ difficulty | titlecase }}
112
+ </span>
113
+ </div>
114
+
115
+ <button class="btn-get"
116
+ (click)="generateContent()
117
+ "
118
+ [disabled]="!topic.trim() || !difficulty.trim() || isGenerateDisabled">
119
+ Generate Passage
120
+ </button>
121
+ </div>
122
  </div>
123
+ </div>
124
+
125
+ <!-- Centered loader (uses your .loader-overlay/.loader CSS) -->
126
+ <div class="loader-overlay" *ngIf="isGeneratingContent" role="status" aria-live="polite">
127
+ <div class="loader">Loading</div>
128
+ </div>
129
+
130
+ <!-- Error popup (uses existing CSS .popup-overlay/.popup-content) -->
131
+ <div class="popup-overlay" *ngIf="showPopup">
132
+ <div class="popup-content">
133
 
134
+ <p>
135
+ {{ errorMessage || 'We could not create the passage right now. Please try again.' }}
136
+ </p>
137
+ <button class="close-btn1" (click)="closeErrorPopup()">Close</button>
 
 
 
 
138
  </div>
139
  </div>
140
+ </section>
141
+
142
+ <!-- READING CARD -->
143
+ <div *ngIf="content && !hasStarted" class="reading-card">
144
+ <!-- Header -->
145
+ <div class="reading-head">
146
+ <button class="icon-btn back" (click)="goToIntroSection()" aria-label="Back">
147
+ <img src="assets/images/reading/back.png" alt="" class="icon-img" />
148
+ </button>
149
 
150
+ <h2 class="reading-title">Let’s Start Reading!</h2>
151
 
152
+ <!-- Actions: A− A+ Read/Pause Stop -->
153
+ <div class="head-actions">
154
+ <button class="icon-btn" (click)="decreaseFont()" aria-label="Decrease font">A−</button>
155
+ <button class="icon-btn" (click)="increaseFont()" aria-label="Increase font">A+</button>
156
+
157
+ <button class="icon-btn"
158
+ [class.active]="isReading || ttsPaused"
159
+ (click)="toggleReadAloud()"
160
+ [attr.aria-pressed]="isReading || ttsPaused"
161
+ aria-label="Read aloud">
162
+ {{ isReading ? '⏸' : '🔊' }}
163
+ </button>
164
+
165
+ <!--
166
+ <button class="icon-btn danger" (click)="stopReadAloud()" aria-label="Stop reading">■</button>
167
+ -->
168
  </div>
 
 
169
  </div>
170
+
171
+ <!-- Meta -->
172
+ <div class="reading-meta">
173
+ <span class="chip chip-topic" *ngIf="normalizedTopic || topic">📚 {{ normalizedTopic || topic }}</span>
174
+ <span class="chip chip-level" [attr.data-level]="difficulty">
175
+ {{
176
+ difficulty | titlecase
177
+ }}
178
+ </span>
179
+ </div>
180
+
181
+ <!-- Passage -->
182
+ <div class="passage-shell">
183
+ <div class="passage-text" [innerHTML]="transformContent(content)"></div>
184
  </div>
185
 
186
+ <!-- Footer -->
187
+ <div class="reading-actions">
188
+ <button class="btn-primary"
189
+ (click)="stopReadAloud(); generateQuestions()"
190
+ [disabled]="isGenerateQuestionDisabled">
191
  Generate Questions
192
  </button>
 
 
 
193
 
194
+ <!-- Centered loader while generating questions -->
195
+ <div class="loader-overlay" *ngIf="loadingQuestions" aria-live="polite" aria-busy="true">
196
+ <span class="loader">Loading</span>
 
 
 
 
 
 
197
  </div>
198
+
199
+ <button class="btn-danger" (click)="stopReadAloud(); resetAll()">Reset</button>
200
  </div>
201
+ </div>
202
+
203
+ <!-- QUESTIONS -->
204
+ <!-- MCQ CARD -->
205
+ <div class="mcq-card" *ngIf="hasStarted && questions?.length">
206
+ <!-- Header inside the card -->
207
+ <div class="mcq-card__header">
208
+ <button class="icon-btn back" (click)="goBack()" aria-label="Back">
209
+ <img src="assets/images/reading/back.png" alt="" class="icon-img" />
210
+ </button>
211
 
212
+ <h3 class="mcq-card__title">
213
+ Question {{ currentQuestionIndex + 1 }} of {{ questions.length }}
214
+ </h3>
215
 
216
+ <div class="mcq-card__actions">
217
+ <button class="icon-btn1" (click)="startOver()" aria-label="Close">✖</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  </div>
219
+ </div>
220
 
221
+ <!-- Body -->
222
+ <div class="mcq-card__body">
223
+ <!-- Question pill -->
224
+ <div class="quiz-pill quiz-question-pill">
225
+ <span class="qq-label">Question:</span>
226
+ <span class="qq-text">{{ questions[currentQuestionIndex].question }}</span>
227
  </div>
228
+
229
+ <!-- Options -->
230
+ <ul class="quiz-options">
231
+ <li *ngFor="let option of (questions[currentQuestionIndex]?.options | slice:0:4); let i = index">
232
+ <label class="quiz-pill quiz-option-pill"
233
+ [ngClass]="{
234
+ 'is-selected': !questions[currentQuestionIndex].isChecked && getSelectedAnswer() === option,
235
+ 'is-correct': questions[currentQuestionIndex].isChecked && questions[currentQuestionIndex].correct_answer === option,
236
+ 'is-incorrect':questions[currentQuestionIndex].isChecked && getSelectedAnswer() === option && option !== questions[currentQuestionIndex].correct_answer
237
+ }">
238
+ <input type="radio"
239
+ class="visually-hidden"
240
+ [name]="'q_'+currentQuestionIndex"
241
+ [value]="option"
242
+ [checked]="getSelectedAnswer() === option"
243
+ (change)="setSelectedAnswer(option)"
244
+ [disabled]="questions[currentQuestionIndex].isChecked" />
245
+ <span class="slot">{{ ['A','B','C','D'][i] }}:</span>
246
+ <span class="opt-text">{{ option }}</span>
247
+ </label>
248
+ </li>
249
+ </ul>
250
  </div>
251
 
252
+ <!-- Footer inside the card -->
253
+ <div class="mcq-card__footer">
254
+ <!-- Submit (visible until answered) -->
255
+
256
+ <button *ngIf="!questions[currentQuestionIndex].isChecked"
257
+ class="submit-btn"
258
+ (click)="validateAnswer(); scheduleCongratsIfLast()"
259
+ [disabled]="!selectedAnswers[questions[currentQuestionIndex].question]">
260
+ Validate
261
  </button>
262
 
 
 
 
 
 
 
 
263
 
264
+ <!-- Next (for Q1 & Q2 after submit) -->
265
+ <button *ngIf="questions[currentQuestionIndex].isChecked && currentQuestionIndex < questions.length - 1"
266
+ class="next-btn"
267
+ (click)="nextQuestion()">
268
+ Next ▶
269
+ </button>
270
 
 
 
 
 
 
 
 
 
 
271
 
 
 
 
 
 
272
  </div>
273
  </div>
274
 
 
 
 
 
275
 
276
+
277
+ </div>
278
+
279
+
280
+
281
+ <!-- Congratulations overlay must be OUTSIDE the main-container -->
282
+ <div class="congrats-overlay"
283
+ *ngIf="showCongrats"
284
+ aria-live="polite"
285
+ aria-modal="true"
286
+ role="dialog">
287
+ <div class="congrats-card">
288
+ <div class="score-badge" aria-label="Your score">
289
+ Your Score:
290
+ <span class="score">{{ scoreCorrect }}</span>
291
+ <span class="sep">/</span>
292
+ <span class="total">{{ scoreTotal }}</span>
293
  </div>
294
+
295
+ <h2>🎉 Congratulations 🎉</h2>
296
+ <p>You have completed all questions.</p>
297
+
298
+ <button class="start-over-btn" (click)="startOver()">Start Over</button>
299
  </div>
300
 
301
  </div>
src/app/reading/reading.component.ts CHANGED
@@ -1,6 +1,8 @@
1
- import { Component } from '@angular/core';
2
  import { ReadingService } from './reading.service';
3
  import { Router } from '@angular/router';
 
 
4
 
5
  @Component({
6
  selector: 'app-reading',
@@ -9,70 +11,140 @@ import { Router } from '@angular/router';
9
  })
10
 
11
  export class ReadingComponent {
12
- loadingQuestions: boolean = false;
 
 
 
 
 
 
 
 
 
13
  hasStarted = false;
14
- hasCompleted = false;
 
 
 
 
15
  topic: string = '';
16
- difficulty: string = 'easy';
17
  content: string = '';
18
- questions: { question: string; options: string[]; correct_answer: string; isChecked?: boolean }[] = [];
19
- selectedAnswers: { [key: string]: string } = {};
20
  errorMessage: string = '';
21
- feedback: string = '';
22
- allAnswered: boolean = false;
23
- isSubmitted: boolean = false;
24
- isGeneratingContent: boolean = false;
25
- isGenerateDisabled: boolean = false;
26
- isGenerateQuestionDisabled: boolean = false;
27
- showPopup: boolean = false;
28
- showSuggestions = false;
29
- isValidateDisabled: boolean = false;
30
- waiting: boolean = false;
31
- currentQuestionIndex: number = 0;
32
- isLoaderVisible: boolean = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
 
 
34
 
 
 
 
35
 
 
 
 
 
 
36
 
37
- constructor(private readingService: ReadingService, private router: Router) { }
 
 
38
 
39
 
40
 
41
  generateContent(): void {
42
- this.errorMessage = '';
43
  this.showPopup = false;
 
44
 
45
- // Validation: Topic and level must be chosen
46
  if (!this.topic.trim() || !this.difficulty.trim()) {
47
  this.errorMessage = 'Please enter a topic and select a difficulty level.';
48
  this.showPopup = true;
49
  return;
50
  }
51
 
52
- // Disable the button and show loader
53
  this.isGenerateDisabled = true;
54
  this.isGeneratingContent = true;
55
 
56
  this.readingService.generateContent(this.topic, this.difficulty).subscribe(
57
  (response) => {
58
- const generated = response.content?.toLowerCase();
59
 
60
- // Check if the content contains the topic (basic validity filter)
61
- if (!generated.includes(this.topic.toLowerCase())) {
62
- this.errorMessage = 'Please enter a valid or meaningful topic.';
63
  this.showPopup = true;
64
  this.isGeneratingContent = false;
65
  this.isGenerateDisabled = false;
66
  this.content = '';
 
67
  return;
68
  }
69
 
70
- this.content = response.content;
 
 
 
 
71
  this.isGeneratingContent = false;
 
 
 
 
72
  },
73
  (error) => {
74
  console.error(error);
75
- this.errorMessage = 'An error occurred while generating content.';
 
 
76
  this.showPopup = true;
77
  this.isGeneratingContent = false;
78
  this.isGenerateDisabled = false;
@@ -81,241 +153,427 @@ export class ReadingComponent {
81
  }
82
 
83
 
84
- getSelectedAnswer(): string {
85
- const current = this.questions?.[this.currentQuestionIndex];
86
- return current && this.selectedAnswers[current.question] || '';
87
- }
88
-
89
- setSelectedAnswer(value: string): void {
90
- const current = this.questions?.[this.currentQuestionIndex];
91
- if (current) {
92
- this.selectedAnswers[current.question] = value;
93
- }
94
- }
95
-
96
-
97
- getFeedbackImage(): string {
98
- const current = this.questions[this.currentQuestionIndex];
99
- const selected = this.selectedAnswers[current?.question];
100
-
101
- if (!current?.isChecked) {
102
- return 'assets/images/listen/thinking.png'; // default before validation
103
- }
104
-
105
- return selected === current.correct_answer
106
- ? 'assets/images/listen/happy.png'
107
- : 'assets/images/listen/sad.png';
108
- }
109
-
110
-
111
-
112
  generateQuestions(): void {
113
  if (!this.content.trim()) return;
114
-
115
  this.loadingQuestions = true;
116
  this.isGenerateQuestionDisabled = true;
117
 
118
  this.readingService.generateQuestions(this.content, this.difficulty).subscribe(
119
  (response) => {
120
  this.questions = this.parseQuestions(response.questions);
121
-
122
- // ✅ Reset UI state
123
  this.selectedAnswers = {};
124
  this.currentQuestionIndex = 0;
125
- this.hasCompleted = false;
126
- this.questions.forEach(q => q.isChecked = false);
127
  this.hasStarted = true;
128
  this.loadingQuestions = false;
129
  },
130
  (error) => {
131
  console.error(error);
132
  this.loadingQuestions = false;
 
 
133
  }
134
  );
135
  }
136
 
137
-
138
- parseQuestions(rawQuestions: string) {
139
- const questions: any[] = [];
140
- const questionBlocks = rawQuestions.split("\n\n");
141
- questionBlocks.forEach(block => {
142
- const questionObj: any = {};
143
- const lines = block.split("\n");
144
- questionObj.question = lines[0].replace("Question: ", "").trim();
145
- let optionsText = lines[1].replace("Options: [", "").replace("]", "").trim();
146
- questionObj.options = optionsText.split(", ").map(option => option.trim());
147
- questionObj.correct_answer = lines[2].replace("Correct Answer: ", "").trim();
148
- questionObj.isChecked = false;
149
- questions.push(questionObj);
150
- });
151
- return questions;
152
- }
153
-
154
 
155
-
156
- checkIfAllAnswered() {
157
- this.allAnswered = this.questions.every(question => this.selectedAnswers[question.question] !== undefined);
158
- }
159
 
 
 
 
160
 
 
161
 
162
- validateAllAnswers() {
163
- this.questions.forEach(question => {
164
- question.isChecked = true;
165
- });
166
 
167
- this.isSubmitted = true;
168
- }
169
 
 
 
 
 
 
170
 
 
 
171
 
172
- isOptionSelected(option: string): boolean {
173
- const currentQuestion = this.questions[this.currentQuestionIndex];
174
- return currentQuestion &&
175
- this.selectedAnswers[currentQuestion.question] === option;
176
  }
177
 
 
 
 
 
 
178
 
179
- closePopup() {
180
- this.showPopup = false;
 
 
181
  }
182
-
183
 
 
 
 
 
184
 
185
- handleNext() {
 
 
 
 
 
 
186
 
187
- const current = this.questions[this.currentQuestionIndex];
188
- current.isChecked = true;
189
- this.waiting = true;
190
 
191
- setTimeout(() => {
192
- this.waiting = false;
193
 
194
- if (this.currentQuestionIndex < this.questions.length - 1) {
195
- this.currentQuestionIndex++;
196
- } else {
197
- this.hasCompleted = true;
198
- }
199
- }, 3000);
200
  }
201
 
 
 
 
 
202
 
203
- goBack(): void {
204
- this.hasStarted = false;
205
- this.hasCompleted = false;
 
 
 
 
 
206
  this.currentQuestionIndex = 0;
207
  this.selectedAnswers = {};
208
- this.questions = [];
209
- this.content = '';
210
- this.topic = '';
211
- this.isGenerateDisabled = false;
212
- this.isGenerateQuestionDisabled = false;
 
 
 
 
213
  this.loadingQuestions = false;
214
- }
 
215
 
 
 
 
 
216
 
 
 
 
 
 
217
 
218
- hideSuggestionsWithDelay(): void {
219
- setTimeout(() => {
220
- this.showSuggestions = false;
221
- }, 200);
 
222
  }
223
 
 
 
 
 
 
 
224
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
226
- selectSuggestion(topic: string): void {
227
- this.topic = topic;
 
228
  this.showSuggestions = false;
229
- /* this.generateContent();*/
230
  }
231
 
 
 
232
 
 
 
 
 
 
 
 
233
 
234
- isLastQuestion(): boolean {
235
- return this.currentQuestionIndex === this.questions.length - 1;
 
 
 
 
 
236
  }
237
-
238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
- validateAnswer(): void {
241
- if (!this.questions[this.currentQuestionIndex]) return;
242
- const current = this.questions[this.currentQuestionIndex];
 
 
243
 
244
- if (this.selectedAnswers[current.question]) {
245
- current.isChecked = true;
246
- this.isValidateDisabled = true;
247
 
248
- setTimeout(() => {
249
- this.isValidateDisabled = false;
250
 
251
- // Removed this line to prevent auto-move
252
- // this.currentQuestionIndex++;
253
- }, 1000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  }
255
  }
256
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
- goToHome(): void {
260
- this.router.navigate(['/home']);
 
 
 
 
 
 
261
  }
262
 
263
 
264
 
265
- prevQuestion() {
266
- if (this.currentQuestionIndex > 0) {
267
- this.currentQuestionIndex--;
268
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  }
270
- nextQuestion(): void {
271
- if (this.currentQuestionIndex < this.questions.length - 1) {
272
- this.currentQuestionIndex++;
 
 
 
 
 
 
 
273
  }
 
274
  }
275
 
276
 
277
- resetAll(): void {
278
- this.topic = '';
279
- this.difficulty = 'easy'; // or your default
280
- this.content = '';
281
- this.hasStarted = false;
282
- this.hasCompleted = false;
283
- this.questions = [];
284
- this.currentQuestionIndex = 0;
285
- this.selectedAnswers = {};
286
- this.errorMessage = '';
287
- this.showPopup = false;
288
- this.isGenerateDisabled = false;
289
- this.isGenerateQuestionDisabled = false;
290
- }
291
 
 
 
 
 
 
 
 
 
 
292
 
293
- regenerateQuestionsOnly() {
294
- // Just regenerate questions from content
295
- this.hasStarted = false;
296
- this.hasCompleted = false;
297
- this.selectedAnswers = {};
298
- this.currentQuestionIndex = 0;
299
- this.questions = [];
300
- this.generateQuestions(); // Assuming this will create a fresh set
301
  }
302
 
303
- isFormValid(): boolean {
304
- return this.topic.trim().length > 0 && this.difficulty.trim().length > 0;
 
 
 
 
305
  }
306
 
307
- onTyping(): void {
308
- this.showSuggestions = true;
 
 
309
  }
310
- goToIntroSection(): void {
311
- this.content = '';
312
- this.hasStarted = false;
313
- this.isGenerateDisabled = false; // Enable Generate button
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  }
315
 
316
- goToContentBlock(): void {
317
- this.hasStarted = false;
318
- this.isGenerateQuestionDisabled = false; // Enable Generate Questions button
 
319
  }
320
 
 
321
  }
 
1
+ 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',
 
11
  })
12
 
13
  export class ReadingComponent {
14
+ // basic state
15
+ loadingQuestions = false;
16
+ isGeneratingContent = false;
17
+ isGenerateDisabled = false;
18
+ isGenerateQuestionDisabled = false;
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',
50
+ 'Good Manners', 'Healthy Eating', 'Personal Hygiene', 'Seasons of the Year',
51
+ 'Weather Today', 'Animals on the Farm', 'Wild Animals', 'Pets and Care',
52
+ 'Parts of a Plant', 'Uses of Water', 'Saving Water', 'The Sun and the Moon',
53
+ 'Our Five Senses', 'Road Safety', 'Recycling Basics', 'Teamwork in Class'
54
+ ],
55
+ medium: [
56
+ 'Water Cycle', 'Photosynthesis', 'Solar System', 'States of Matter',
57
+ 'Simple Machines', 'Electricity Basics', 'Magnetism Basics',
58
+ 'Human Digestive System', 'Circulatory System Basics',
59
+ 'Food Chain and Web', 'Ecosystems', 'Renewable Energy',
60
+ 'Weather and Climate', 'Volcanoes and Earthquakes',
61
+ 'Map Skills and Symbols', 'Ancient Egypt', 'Indus Valley Civilization',
62
+ 'Indian Constitution (Basics)', 'Cyber Safety for Kids',
63
+ 'Time Management for Students'
64
+ ],
65
+ hard: [
66
+ 'Global Warming and Climate Change', 'Greenhouse Effect', 'Plate Tectonics',
67
+ 'Genetic Inheritance (Basics)', 'Natural Selection (Basics)',
68
+ 'Nervous System Overview', 'Robotics in Daily Life',
69
+ 'Artificial Intelligence (Basics)', 'Computer Networks (Basics)',
70
+ 'Data Privacy and Security', 'Internet and Web Architecture (Basics)',
71
+ 'World War II (Overview)', 'Industrial Revolution',
72
+ 'Democracy and Rights (India)', 'Financial Literacy: Budgeting',
73
+ 'Statistics in Daily Life (Mean, Median)',
74
+ 'Renewable vs Non-renewable Energy', 'Entrepreneurship Basics',
75
+ 'Ethics in Technology', 'Career Planning and Growth Mindset'
76
+ ]
77
+ };
78
+
79
+ constructor(
80
+ private readingService: ReadingService,
81
+ private router: Router,
82
+ private el: ElementRef
83
+ ) { }
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;
125
  this.isGeneratingContent = false;
126
  this.isGenerateDisabled = false;
127
  this.content = '';
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;
149
  this.isGeneratingContent = false;
150
  this.isGenerateDisabled = false;
 
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);
 
 
165
  this.selectedAnswers = {};
166
  this.currentQuestionIndex = 0;
167
+ this.questions.forEach(q => (q.isChecked = false));
 
168
  this.hasStarted = true;
169
  this.loadingQuestions = false;
170
  },
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) => ({
188
+ question: String(q?.question || '').trim(),
189
+ options: Array.isArray(q?.options) ? q.options.map((o: any) => String(o)) : [],
190
+ correct_answer: String(q?.correct_answer || '').trim(),
191
+ isChecked: false
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]) {
247
+ curr.isChecked = true;
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') {
321
+ event.preventDefault();
322
+ this.activeIndex = Math.min(this.activeIndex + 1, this.filteredSuggestions.length - 1);
323
+ } else if (event.key === 'ArrowUp') {
324
+ event.preventDefault();
325
+ this.activeIndex = Math.max(this.activeIndex - 1, 0);
326
+ } else if (event.key === 'Enter') {
327
+ event.preventDefault();
328
+ const pick = this.filteredSuggestions[this.activeIndex] || this.topic;
329
+ this.selectSuggestion(pick);
330
+ } else if (event.key === 'Escape') {
331
+ this.showSuggestions = false;
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'] || [];
349
+ this.filteredSuggestions = q ? pool.filter(t => t.toLowerCase().includes(q)).slice(0, 12)
350
+ : pool.slice(0, 12);
351
+ }
352
 
353
+ /** Clear topic and reopen suggestions. */
354
+ onClearTopic(): void {
355
+ this.topic = '';
356
+ this.errorMessage = '';
357
+ this.activeIndex = -1;
358
+ this.showSuggestions = true;
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) {
366
+ this.filteredSuggestions = [];
367
+ this.showSuggestions = false;
368
+ this.activeIndex = -1;
369
+ return;
370
+ }
371
+ this.filterSuggestions();
372
+ this.showSuggestions = true;
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;
392
+ this.isGenerateDisabled = false;
393
+ this.isGenerateQuestionDisabled = false;
394
+ this.showPopup = false;
395
+ this.errorMessage = '';
396
+ this.topic = '';
397
+ this.normalizedTopic = '';
398
+ this.difficulty = 'easy';
399
+ this.showSuggestions = false;
400
+ this.activeIndex = -1;
401
+ this.filteredSuggestions = [];
402
+ this.content = '';
403
+ this.questions = [];
404
+ this.currentQuestionIndex = 0;
405
+ this.selectedAnswers = {};
406
+ this.fontPx = parseInt(localStorage.getItem('passageFontPx') || '18', 10);
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);
417
+ const safe = this.escapeHtml(passage).replace(/\n+/g, ' ').trim();
418
+ return `<p>${safe}</p>`;
419
+ } catch {
420
+ return this.escapeHtml(raw || '');
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:');
428
+ if (p === -1) return raw;
429
+ const s = raw.indexOf('Summary:', p + 8);
430
+ const slice = s === -1 ? raw.slice(p + 8) : raw.slice(p + 8, s);
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.';
443
+ this.showPopup = true;
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';
455
+ u.onend = () => { this.isReading = false; this.ttsPaused = false; this.ttsUtterance = undefined; };
456
+ u.onerror = () => { this.isReading = false; this.ttsPaused = false; this.ttsUtterance = undefined; };
457
+ this.ttsUtterance = u;
458
+ synth.speak(u);
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();
466
+ }
467
+ this.isReading = false;
468
+ this.ttsPaused = false;
469
+ this.ttsUtterance = undefined;
470
  }
471
 
472
 
473
 
474
+
475
+
476
+ // add inside the component class
477
+ showCongrats = false;
478
+ scoreCorrect = 0;
479
+ scoreTotal = 0;
480
+ private confettiInterval: any = null;
481
+
482
+ /** After validate is clicked, if this was the last question, show score + confetti after a short hold. */
483
+ scheduleCongratsIfLast(): void {
484
+ // must be last question AND already marked as checked
485
+ const isLast = this.currentQuestionIndex === this.questions.length - 1;
486
+ const curr = this.questions[this.currentQuestionIndex];
487
+ if (!isLast || !curr?.isChecked) return;
488
+
489
+ // small delay so learners can see the final green/red states first
490
+ setTimeout(() => {
491
+ const { correct, total } = this.computeScore();
492
+ this.scoreCorrect = correct;
493
+ this.scoreTotal = total;
494
+ this.showCongrats = true;
495
+ this.triggerConfetti();
496
+ }, 800);
497
  }
498
+
499
+ /** Compute score without altering validation code. */
500
+ private computeScore(): { correct: number; total: number } {
501
+ let correct = 0;
502
+ const total = this.questions.length;
503
+
504
+ // Uses your existing structures: selectedAnswers[...] and question.correct_answer
505
+ for (const q of this.questions) {
506
+ const chosen = this.selectedAnswers[q.question];
507
+ if (chosen && chosen === q.correct_answer) correct++;
508
  }
509
+ return { correct, total };
510
  }
511
 
512
 
513
+ private triggerConfetti(): void {
514
+ if (this.confettiInterval) clearInterval(this.confettiInterval);
 
 
 
 
 
 
 
 
 
 
 
 
515
 
516
+ this.confettiInterval = setInterval(() => {
517
+ confetti({
518
+ startVelocity: 30,
519
+ spread: 360,
520
+ ticks: 60,
521
+ origin: { x: Math.random(), y: Math.random() - 0.2 },
522
+ });
523
+ }, 250);
524
+ }
525
 
526
+ private stopConfetti(): void {
527
+ if (this.confettiInterval) {
528
+ clearInterval(this.confettiInterval);
529
+ this.confettiInterval = null;
530
+ }
531
+ const canvas = document.querySelector('canvas.confetti-canvas');
532
+ if (canvas) canvas.remove();
 
533
  }
534
 
535
+
536
+
537
+ startOver(): void {
538
+ this.stopConfetti();
539
+ this.showCongrats = false;
540
+ this.resetAll(); // use your current reset method
541
  }
542
 
543
+
544
+ ngOnDestroy(): void {
545
+
546
+ this.stopConfetti();
547
  }
548
+ // optional: if you want a short “Validating…” state on the button
549
+ isValidating = false;
550
+ /** Called by the template. Keeps existing validation, then schedules the final modal if last. */
551
+ validateAnswer(): void {
552
+ // OPTIONAL: short “validating” hold on the button (remove if not needed)
553
+ this.isValidating = true;
554
+ setTimeout(() => (this.isValidating = false), 300);
555
+
556
+ // 👉 IMPORTANT:
557
+ // If you already have a function that validates/marks the current question, call it here.
558
+ // Example:
559
+ // this.checkAnswer(); // or
560
+ // this.markCurrentAsChecked(); // or your own method
561
+ // If you do not have one, the fallback below will simply mark the current question as checked.
562
+
563
+ const curr = this.questions?.[this.currentQuestionIndex];
564
+ if (curr && this.selectedAnswers?.[curr.question]) {
565
+ curr.isChecked = true; // fallback mark; harmless if your own method did it already
566
+ }
567
+
568
+ // After the question has been marked, if it is the last, open the modal
569
+ this.scheduleCongratsIfLast();
570
  }
571
 
572
+
573
+ /** Enable/disable "Generate Questions" based on whether a passage exists */
574
+ private refreshGenerateQuestionsState(): void {
575
+ this.isGenerateQuestionDisabled = !(this.content && this.content.trim().length > 0);
576
  }
577
 
578
+
579
  }
src/app/writing/writing.component.html CHANGED
@@ -114,7 +114,7 @@
114
 
115
  <div class="feedback-box">
116
  <ul>
117
- <li *ngFor="let suggestion of feedbackList">✅ {{ suggestion }}</li>
118
  </ul>
119
  </div>
120
 
 
114
 
115
  <div class="feedback-box">
116
  <ul>
117
+ <li *ngFor="let suggestion of feedbackList"> {{ suggestion }}</li>
118
  </ul>
119
  </div>
120