|
|
<div class="reading-container"> |
|
|
|
|
|
<div class="header-container"> |
|
|
<div class="logo"> |
|
|
<a (click)="goToHome()" routerLink="/home" class="brand-link"> |
|
|
<img src="assets/images/pykara-logo.png" alt="Pykara Logo" /> |
|
|
</a> |
|
|
<span class="product-name">Py-Learn</span> |
|
|
</div> |
|
|
|
|
|
<div class="header-title"> |
|
|
<h1>Reading</h1> |
|
|
</div> |
|
|
|
|
|
<div class="home-btn"> |
|
|
<a (click)="goToHome()" routerLink="/home"> |
|
|
<img src="assets/images/home.png" alt="Home" class="home-icon" /> |
|
|
</a> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
<button class="btn-get" |
|
|
(click)="generateContent() |
|
|
" |
|
|
[disabled]="!topic.trim() || !difficulty.trim() || isGenerateDisabled"> |
|
|
Generate Passage |
|
|
</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> |
|
|
<button class="close-btn1" (click)="closeErrorPopup()">Close</button> |
|
|
</div> |
|
|
</div> |
|
|
</section> |
|
|
|
|
|
|
|
|
<div *ngIf="content && !hasStarted" class="reading-card"> |
|
|
|
|
|
<div class="reading-head"> |
|
|
<button class="icon-btn back" (click)="goToIntroSection()" aria-label="Back"> |
|
|
<img src="assets/images/reading/back.png" alt="" class="icon-img" /> |
|
|
</button> |
|
|
|
|
|
<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"> |
|
|
<button class="btn-primary" |
|
|
(click)="stopReadAloud(); generateQuestions()" |
|
|
[disabled]="isGenerateQuestionDisabled"> |
|
|
Generate Questions |
|
|
</button> |
|
|
|
|
|
|
|
|
<div class="loader-overlay" *ngIf="loadingQuestions" aria-live="polite" aria-busy="true"> |
|
|
<span class="loader">Loading</span> |
|
|
</div> |
|
|
|
|
|
<button class="btn-danger" (click)="stopReadAloud(); resetAll()">Reset</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="mcq-card" *ngIf="hasStarted && questions?.length"> |
|
|
|
|
|
<div class="mcq-card__header"> |
|
|
<button class="icon-btn back" (click)="goBack()" aria-label="Back"> |
|
|
<img src="assets/images/reading/back.png" alt="" class="icon-img" /> |
|
|
</button> |
|
|
|
|
|
<h3 class="mcq-card__title"> |
|
|
Question {{ currentQuestionIndex + 1 }} of {{ questions.length }} |
|
|
</h3> |
|
|
|
|
|
<div class="mcq-card__actions"> |
|
|
<button class="icon-btn1" (click)="startOver()" aria-label="Close">✖</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"> |
|
|
|
|
|
|
|
|
<button *ngIf="!questions[currentQuestionIndex].isChecked" |
|
|
class="submit-btn" |
|
|
(click)="validateAnswer(); scheduleCongratsIfLast()" |
|
|
[disabled]="!selectedAnswers[questions[currentQuestionIndex].question]"> |
|
|
Validate |
|
|
</button> |
|
|
|
|
|
|
|
|
|
|
|
<button *ngIf="questions[currentQuestionIndex].isChecked && currentQuestionIndex < questions.length - 1" |
|
|
class="next-btn" |
|
|
(click)="nextQuestion()"> |
|
|
Next ▶ |
|
|
</button> |
|
|
|
|
|
|
|
|
</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> |
|
|
|
|
|
<button class="start-over-btn" (click)="startOver()">Start Over</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|