| <div class="reading-container"> | |
| <app-header [title]="'Reading'"></app-header> | |
| <img src="assets/images/grammar-bg.png" alt="Background" class="grammar-bg" /> | |
| <div class="main-container" *ngIf="!showCongrats"> | |
| <section class="intro-section split" *ngIf="!content && !hasStarted"> | |
| <div class="split-shell"> | |
| <div class="split-left"> | |
| <img class="intro-illustration" src="assets/images/reading/teacher.png" alt="Reading and quiz illustration" /> | |
| </div> | |
| <div class="split-right"> | |
| <h1 class="hero-title">Welcome to the Reading Exercise</h1> | |
| <p class="hero-copy"> | |
| The Reading component is a simple tool that turns any meaningful topic into a short, age-appropriate passage. It checks the topic first to avoid nonsense and unsafe inputs, then creates clear content at the chosen level (Easy, Medium, or Hard). After | |
| reading, it generates multiple-choice questions and gives instant feedback. Read-Aloud and A−/A+ controls support different learning needs. This helps students build comprehension and vocabulary, and saves teachers and parents | |
| time during practice, homework, and revision. | |
| </p> | |
| <div class="form-row"> | |
| <label class="row-label">Topic:</label> | |
| <div class="input-wrap clearable" [class.locked]="!difficulty"> | |
| <span class="icon-search" aria-hidden="true"></span> | |
| <input type="text" class="has-clear" [(ngModel)]="topic" [placeholder]="difficulty ? 'Enter or select a topic' : 'Select a level first'" [disabled]="!difficulty" (focus)="openSuggestions()" (input)="onTyping()" (keydown)="onKeydown($event)" (blur)="hideSuggestionsWithDelay()" | |
| autocomplete="off" aria-autocomplete="list" [attr.aria-expanded]="showSuggestions && !!difficulty" [attr.aria-disabled]="!difficulty" /> | |
| <button class="clear-btn" *ngIf="difficulty && topic.trim().length" (mousedown)="$event.preventDefault()" (click)="onClearTopic()">×</button> | |
| <div class="suggestion-box" *ngIf="difficulty && showSuggestions"> | |
| <ng-container *ngIf="filteredSuggestions?.length"> | |
| <span *ngFor="let s of filteredSuggestions; let i = index" (mousedown)="selectSuggestion(s)" [class.active]="i === activeIndex">{{ s }}</span> | |
| </ng-container> | |
| </div> | |
| </div> | |
| <div class="field-hint" *ngIf="!difficulty"> | |
| <span class="lock-icon" aria-hidden="true"></span> Please select a level to enable topic suggestions. | |
| </div> | |
| </div> | |
| <div class="form-row"> | |
| <label class="row-label">Select Level:</label> | |
| <div class="select-wrap"> | |
| <span class="icon-level" aria-hidden="true"></span> | |
| <select [(ngModel)]="difficulty" (ngModelChange)="onDifficultyChange($event)" required> | |
| <option [ngValue]="'easy'">Easy</option> | |
| <option [ngValue]="'medium'">Medium</option> | |
| <option [ngValue]="'hard'">Hard</option> | |
| </select> | |
| <span class="badge" *ngIf="difficulty" [attr.data-level]="difficulty">{{ difficulty | titlecase }}</span> | |
| </div> | |
| <app-button (click)="generateContent()" [disabled]="!topic.trim() || !difficulty.trim() || isGenerateDisabled">Generate Passage</app-button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="loader-overlay" *ngIf="isGeneratingContent" role="status" aria-live="polite"> | |
| <div class="loader">Loading</div> | |
| </div> | |
| <div class="popup-overlay" *ngIf="showPopup"> | |
| <div class="popup-content"> | |
| <p>{{ errorMessage || 'We could not create the passage right now. Please try again.' }}</p> | |
| <app-button (click)="closeErrorPopup()">Close</app-button> | |
| </div> | |
| </div> | |
| </section> | |
| <div *ngIf="content && !hasStarted" class="reading-card"> | |
| <div class="reading-head"> | |
| <img src="assets/images/reading/back.png" (click)="goToIntroSection()" alt="" class="icon-img" /> | |
| <h2 class="reading-title">Let’s Start Reading!</h2> | |
| <div class="head-actions"> | |
| <button class="icon-btn" (click)="decreaseFont()" aria-label="Decrease font">A−</button> | |
| <button class="icon-btn" (click)="increaseFont()" aria-label="Increase font">A+</button> | |
| <button class="icon-btn" [class.active]="isReading || ttsPaused" (click)="toggleReadAloud()" [attr.aria-pressed]="isReading || ttsPaused" aria-label="Read aloud">{{ isReading ? '⏸' : '🔊' }}</button> | |
| </div> | |
| </div> | |
| <div class="reading-meta"> | |
| <span class="chip chip-topic" *ngIf="normalizedTopic || topic">📚 {{ normalizedTopic || topic }}</span> | |
| <span class="chip chip-level" [attr.data-level]="difficulty">{{ difficulty | titlecase }}</span> | |
| </div> | |
| <div class="passage-shell"> | |
| <div class="passage-text" [innerHTML]="transformContent(content)"></div> | |
| </div> | |
| <div class="reading-actions"> | |
| <app-button (click)="stopReadAloud(); generateQuestions()" [disabled]="isGenerateQuestionDisabled">Generate Questions</app-button> | |
| <div class="loader-overlay" *ngIf="loadingQuestions" aria-live="polite" aria-busy="true"> | |
| <span class="loader">Loading</span> | |
| </div> | |
| <app-button (click)="stopReadAloud(); resetAll()">Reset</app-button> | |
| </div> | |
| </div> | |
| <div class="mcq-card" *ngIf="hasStarted && questions?.length"> | |
| <div class="mcq-card__header"> | |
| <img src="assets/images/reading/back.png" | |
| alt="Back" | |
| class="icon-img" | |
| (click)="goToReadingPassage()" | |
| tabindex="0" | |
| (keydown.enter)="goToReadingPassage()" /> | |
| <h3 class="mcq-card__title">Question {{ currentQuestionIndex + 1 }} of {{ questions.length }}</h3> | |
| <div class="mcq-card__actions"> | |
| <button class="user-guide-close-icon" (click)="startOver()">×</button> | |
| </div> | |
| </div> | |
| <div class="mcq-card__body"> | |
| <div class="quiz-pill quiz-question-pill"> | |
| <span class="qq-label">Question:</span> | |
| <span class="qq-text">{{ questions[currentQuestionIndex].question }}</span> | |
| </div> | |
| <ul class="quiz-options"> | |
| <li *ngFor="let option of (questions[currentQuestionIndex]?.options | slice:0:4); let i = index"> | |
| <label class="quiz-pill quiz-option-pill" [ngClass]="{ | |
| 'is-selected': !questions[currentQuestionIndex].isChecked && getSelectedAnswer() === option, | |
| 'is-correct': questions[currentQuestionIndex].isChecked && questions[currentQuestionIndex].correct_answer === option, | |
| 'is-incorrect':questions[currentQuestionIndex].isChecked && getSelectedAnswer() === option && option !== questions[currentQuestionIndex].correct_answer | |
| }"> | |
| <input type="radio" class="visually-hidden" [name]="'q_'+currentQuestionIndex" [value]="option" [checked]="getSelectedAnswer() === option" (change)="setSelectedAnswer(option)" [disabled]="questions[currentQuestionIndex].isChecked" /> | |
| <span class="slot">{{ ['A','B','C','D'][i] }}:</span> | |
| <span class="opt-text">{{ option }}</span> | |
| </label> | |
| </li> | |
| </ul> | |
| </div> | |
| <div class="mcq-card__footer"> | |
| <app-button *ngIf="currentQuestionIndex > 0" (click)="previousQuestion()">◀ Previous</app-button> | |
| <div style="display: flex; gap: 10px; justify-content: flex-end; flex: 1;"> | |
| <app-button *ngIf="!questions[currentQuestionIndex].isChecked" (click)="validateAnswer(); scheduleCongratsIfLast()" [disabled]="!selectedAnswers[questions[currentQuestionIndex].question]">Validate</app-button> | |
| <app-button *ngIf="questions[currentQuestionIndex].isChecked && currentQuestionIndex < questions.length - 1" (click)="nextQuestion()">Next ▶</app-button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="congrats-overlay" *ngIf="showCongrats" aria-live="polite" aria-modal="true" role="dialog"> | |
| <div class="congrats-card"> | |
| <div class="score-badge" aria-label="Your score"> | |
| Your Score: <span class="score">{{ scoreCorrect }}</span> / <span class="total">{{ scoreTotal }}</span> | |
| </div> | |
| <h2>{{ congratsTitle }}</h2> | |
| <p>{{ congratsMessage }}</p> | |
| <app-button (click)="startOver()">Start Over</app-button> | |
| </div> | |
| </div> | |
| </div> | |