deedrop1140 commited on
Commit
1cf7e83
·
verified ·
1 Parent(s): 6e3132a

Update templates/Test/Quiz-test.html

Browse files
Files changed (1) hide show
  1. templates/Test/Quiz-test.html +276 -276
templates/Test/Quiz-test.html CHANGED
@@ -1,277 +1,277 @@
1
- {% extends "Test-layout.html" %}
2
-
3
-
4
- {% block content %}<!DOCTYPE html>
5
- <html lang="en">
6
- <head>
7
- <meta charset="UTF-8" />
8
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
- <title>Dynamic General Knowledge Quiz</title>
10
- <script src="https://cdn.tailwindcss.com"></script>
11
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
12
- <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
13
- <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
14
- <style>
15
- body { font-family: 'Inter', sans-serif; }
16
- .quiz-option { transition: all 0.2s ease-in-out; }
17
- .quiz-option:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
18
- .quiz-option input:checked + label { background-color: #3b82f6; color: white; border-color: #2563eb; }
19
- .correct { border-left: 5px solid #22c55e; }
20
- .incorrect { border-left: 5px solid #ef4444; }
21
- .progress-bar { transition: width 0.3s ease-in-out; }
22
- #timer { transition: color 0.3s ease-in-out; }
23
- kbd { background-color: #f3f4f6; border: 1px solid #d1d5db; border-radius: 0.25rem; padding: 0.25rem 0.5rem; font-family: monospace; font-weight: 600; }
24
- </style>
25
- </head>
26
- <body class="bg-gray-100 flex items-center justify-center min-h-screen p-4">
27
-
28
- <!-- Start Screen -->
29
- <div id="start-container" class="w-full max-w-2xl bg-white p-6 sm:p-8 rounded-2xl shadow-lg text-center">
30
- <h1 class="text-3xl sm:text-4xl font-bold text-gray-800 mb-4">General Knowledge Challenge</h1>
31
- <p class="text-gray-600 mb-8">Test your knowledge with these quick-fire questions!</p>
32
- <div class="text-left bg-gray-50 p-4 rounded-lg border border-gray-200 mb-8">
33
- <h3 class="font-bold text-lg mb-3 text-gray-700">📜 Instructions</h3>
34
- <ul class="list-disc list-inside space-y-2 text-gray-600">
35
- <li>There are <strong id="instruction-q-count">0</strong> questions in total.</li>
36
- <li>You will have <strong id="instruction-time">15</strong> seconds to answer each question.</li>
37
- <li>You have a total of <strong id="instruction-attempts">3</strong> attempts to take this quiz.</li>
38
- <li class="font-semibold text-yellow-700">Once the quiz starts, do not switch tabs or windows.</li>
39
- <li class="font-semibold text-yellow-700">Emergency exit: <kbd>Esc</kbd>, <kbd>Space</kbd>, <kbd>A</kbd>, <kbd>M</kbd>.</li>
40
- </ul>
41
- </div>
42
- <button id="start-btn" class="w-full bg-blue-600 text-white font-bold py-3 px-4 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-4 focus:ring-blue-300 transition-transform transform hover:scale-105">
43
- Start Quiz
44
- </button>
45
- </div>
46
-
47
- <!-- Quiz View -->
48
- <div id="quiz-container" class="hidden w-full max-w-2xl bg-white p-6 sm:p-8 rounded-2xl shadow-lg">
49
- <div class="flex justify-between items-center mb-4">
50
- <div id="progress-container" class="text-gray-500 font-semibold">
51
- Question <span id="current-question-num">1</span> of <span id="total-question-num">0</span>
52
- </div>
53
- <div id="timer-container" class="font-bold text-lg text-gray-700">
54
- Time: <span id="timer">15</span>s
55
- </div>
56
- </div>
57
- <div class="w-full bg-gray-200 rounded-full h-2.5 mb-6">
58
- <div id="progress-bar" class="bg-blue-600 h-2.5 rounded-full progress-bar" style="width: 0%"></div>
59
- </div>
60
- <div id="question-text" class="text-lg sm:text-xl font-semibold text-gray-800 mb-6 text-center"></div>
61
- <div id="options-container" class="space-y-4"></div>
62
- <div id="feedback" class="text-red-500 text-center font-medium mt-4 h-6"></div>
63
- <button id="next-btn" class="w-full bg-blue-600 text-white font-bold py-3 px-4 rounded-lg mt-6 hover:bg-blue-700 focus:outline-none focus:ring-4 focus:ring-blue-300 transition-transform transform hover:scale-105">
64
- Next Question
65
- </button>
66
- </div>
67
-
68
- <!-- Results View -->
69
- <div id="score-container" class="hidden w-full max-w-3xl bg-white p-6 sm:p-8 rounded-2xl shadow-lg">
70
- <div id="results-content">
71
- <h2 class="text-2xl sm:text-3xl font-bold text-gray-800 mb-2 text-center">Quiz Complete!</h2>
72
- <p class="text-gray-600 mb-6 text-center">You scored <span id="final-score" class="font-bold text-blue-600 text-xl">0</span> out of <span id="total-questions" class="font-bold text-blue-600 text-xl">0</span></p>
73
- <div id="results-breakdown" class="space-y-4 text-left mt-8"></div>
74
- </div>
75
- <div class="w-full max-w-md mx-auto mt-8 flex flex-col sm:flex-row gap-4">
76
- <button id="retry-btn" class="w-full bg-gray-700 text-white font-bold py-3 px-4 rounded-lg hover:bg-gray-800 focus:outline-none focus:ring-4 focus:ring-gray-300 transition-transform transform hover:scale-105">
77
- Try Again
78
- </button>
79
- <button id="save-btn" class="w-full bg-green-600 text-white font-bold py-3 px-4 rounded-lg hover:bg-green-700 focus:outline-none focus:ring-4 focus:ring-green-300 transition-transform transform hover:scale-105">
80
- Save Results
81
- </button>
82
- </div>
83
- <p id="attempts-left-msg" class="text-center text-sm text-gray-500 mt-4"></p>
84
- </div>
85
-
86
- <!-- Limit View -->
87
- <div id="limit-container" class="hidden w-full max-w-xl bg-white p-6 sm:p-8 rounded-2xl shadow-lg text-center">
88
- <h2 class="text-2xl sm:text-3xl font-bold text-gray-800 mb-4">🚫 Attempt Limit Reached</h2>
89
- <p class="text-gray-600 text-lg">You have already taken the <span id="topic-name-limit" class="font-semibold text-blue-600"></span> quiz 3 times.</p>
90
- </div>
91
-
92
- <script>
93
- let quizData = [];
94
- let currentQuestionIndex = 0;
95
- let userAnswers = [];
96
- let shuffledQuizData = [];
97
- let quizInProgress = false;
98
- let timerInterval;
99
-
100
- // ✅ Topic-based attempt tracking
101
- const params = new URLSearchParams(window.location.search);
102
- const topic = params.get("topic") || "general";
103
- const ATTEMPT_LIMIT = 60;
104
- const ATTEMPT_KEY = `quizAttempts_${topic}`;
105
- let quizAttempts = parseInt(localStorage.getItem(ATTEMPT_KEY)) || 0;
106
-
107
- const QUESTION_TIME = 15;
108
-
109
- // DOM references
110
- const startContainer = document.getElementById('start-container');
111
- const quizContainer = document.getElementById('quiz-container');
112
- const scoreContainer = document.getElementById('score-container');
113
- const limitContainer = document.getElementById('limit-container');
114
- const startBtn = document.getElementById('start-btn');
115
- const instructionQCount = document.getElementById('instruction-q-count');
116
- const instructionTime = document.getElementById('instruction-time');
117
- const instructionAttempts = document.getElementById('instruction-attempts');
118
- const currentQNumEl = document.getElementById('current-question-num');
119
- const totalQNumEl = document.getElementById('total-question-num');
120
- const questionTextEl = document.getElementById('question-text');
121
- const optionsContainerEl = document.getElementById('options-container');
122
- const feedbackEl = document.getElementById('feedback');
123
- const nextBtn = document.getElementById('next-btn');
124
- const progressBar = document.getElementById('progress-bar');
125
- const timerEl = document.getElementById('timer');
126
- const finalScoreEl = document.getElementById('final-score');
127
- const totalQuestionsEl = document.getElementById('total-questions');
128
- const resultsBreakdownEl = document.getElementById('results-breakdown');
129
- const retryBtn = document.getElementById('retry-btn');
130
- const saveBtn = document.getElementById('save-btn');
131
-
132
- // 🧩 Emergency Exit Key Listener
133
- document.addEventListener('keydown', (e) => {
134
- const emergencyKeys = ['Escape', ' ', 'a', 'A', 'm', 'M'];
135
- if (quizInProgress && emergencyKeys.includes(e.key)) {
136
- e.preventDefault();
137
- terminateQuiz('⚠️ Emergency exit triggered!');
138
- }
139
- });
140
-
141
- // 🧩 Emergency Exit Handler
142
- function terminateQuiz(message) {
143
- quizInProgress = false;
144
- clearInterval(timerInterval);
145
-
146
- // Hide quiz, show alert, save attempt
147
- quizContainer.classList.add('hidden');
148
- startContainer.classList.remove('hidden');
149
-
150
- alert(message);
151
-
152
- // Count as one attempt
153
- quizAttempts++;
154
- localStorage.setItem(ATTEMPT_KEY, quizAttempts);
155
- initializeQuiz();
156
- }
157
-
158
- // Utility
159
- function shuffleArray(array){for(let i=array.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[array[i],array[j]]=[array[j],array[i]];}}
160
-
161
- function startTimer(){
162
- let timeLeft=QUESTION_TIME;
163
- timerEl.textContent=timeLeft;
164
- timerEl.classList.remove('text-red-500');
165
- timerInterval=setInterval(()=>{
166
- timeLeft--;
167
- timerEl.textContent=timeLeft;
168
- if(timeLeft<=5) timerEl.classList.add('text-red-500');
169
- if(timeLeft<=0){clearInterval(timerInterval);handleNextQuestion(true);}
170
- },1000);
171
- }
172
-
173
- function showQuestion(){
174
- clearInterval(timerInterval);
175
- feedbackEl.textContent='';
176
- const q=shuffledQuizData[currentQuestionIndex];
177
- currentQNumEl.textContent=currentQuestionIndex+1;
178
- totalQNumEl.textContent=shuffledQuizData.length;
179
- progressBar.style.width=`${((currentQuestionIndex+1)/shuffledQuizData.length)*100}%`;
180
- questionTextEl.textContent=q.questionText;
181
- optionsContainerEl.innerHTML='';
182
- const opts=[...q.options]; shuffleArray(opts);
183
- opts.forEach(opt=>{
184
- const id=`q${currentQuestionIndex}-${opt.replace(/\s+/g,'-')}`;
185
- const div=document.createElement('div');div.classList.add('quiz-option');
186
- const input=document.createElement('input');input.type='radio';input.name=`question${currentQuestionIndex}`;input.id=id;input.value=opt;input.classList.add('hidden');
187
- const label=document.createElement('label');label.htmlFor=id;label.textContent=opt;label.classList.add('block','w-full','p-4','border-2','border-gray-200','rounded-lg','cursor-pointer','text-gray-700','font-medium','hover:border-blue-400');
188
- div.appendChild(input);div.appendChild(label);optionsContainerEl.appendChild(div);
189
- });
190
- nextBtn.textContent=currentQuestionIndex===shuffledQuizData.length-1?'Finish Quiz':'Next Question';
191
- startTimer();
192
- }
193
-
194
- function handleNextQuestion(timedOut=false){
195
- clearInterval(timerInterval);
196
- const sel=document.querySelector(`input[name="question${currentQuestionIndex}"]:checked`);
197
- if(timedOut) userAnswers.push(null);
198
- else{
199
- if(!sel){feedbackEl.textContent='Please select an answer!';startTimer();return;}
200
- userAnswers.push(sel.value);
201
- }
202
- currentQuestionIndex++;
203
- currentQuestionIndex<shuffledQuizData.length?showQuestion():showResults();
204
- }
205
-
206
- function showResults(){
207
- quizInProgress=false;clearInterval(timerInterval);
208
- quizContainer.classList.add('hidden');scoreContainer.classList.remove('hidden');
209
- quizAttempts++;localStorage.setItem(ATTEMPT_KEY,quizAttempts);
210
- resultsBreakdownEl.innerHTML='';
211
- let score=0;
212
- shuffledQuizData.forEach((q,i)=>{
213
- const ua=userAnswers[i];const correct=ua===q.answer;if(correct)score++;
214
- const div=document.createElement('div');div.classList.add('p-4','rounded-lg','bg-gray-50',correct?'correct':'incorrect');
215
- div.innerHTML=`<p class="font-bold text-gray-800">${i+1}. ${q.question}</p>
216
- <p class="mt-2 text-sm ${correct?'text-green-700':'text-red-700'}">Your answer: <span class="font-semibold">${ua||"Time's up!"}</span></p>
217
- ${!correct?`<p class="mt-1 text-sm text-green-700">Correct: <span class="font-semibold">${q.answer}</span></p>`:''}
218
- <p class="mt-2 text-sm text-gray-600 bg-gray-100 p-2 rounded"><span class="font-semibold">Explanation:</span> ${q.explanation}</p>`;
219
- resultsBreakdownEl.appendChild(div);
220
- });
221
- finalScoreEl.textContent=score;totalQuestionsEl.textContent=shuffledQuizData.length;
222
- if(score/shuffledQuizData.length>=0.8) confetti({particleCount:150,spread:90,origin:{y:0.6}});
223
- const left=ATTEMPT_LIMIT-quizAttempts;
224
- const msg=document.getElementById('attempts-left-msg');
225
- if(left<=0){retryBtn.disabled=true;retryBtn.textContent='No Attempts Left';retryBtn.classList.add('bg-gray-400','cursor-not-allowed');msg.textContent='You have used all your attempts.';}
226
- else{msg.textContent=`You have ${left} attempt${left>1?'s':''} left.`;}
227
- }
228
-
229
- function initializeQuiz(){
230
- if(quizAttempts>=ATTEMPT_LIMIT){
231
- document.getElementById('topic-name-limit').textContent=topic.replace(/_/g,' ');
232
- startContainer.classList.add('hidden');quizContainer.classList.add('hidden');scoreContainer.classList.add('hidden');limitContainer.classList.remove('hidden');
233
- } else {
234
- startContainer.classList.remove('hidden');quizContainer.classList.add('hidden');scoreContainer.classList.add('hidden');limitContainer.classList.add('hidden');
235
- }
236
- }
237
-
238
- startBtn.addEventListener('click',()=>{
239
- if(quizData.length===0){alert("Quiz data not loaded!");return;}
240
- shuffledQuizData=[...quizData];
241
- // shuffleArray(shuffledQuizData);
242
- startContainer.classList.add('hidden');
243
- quizContainer.classList.remove('hidden');quizInProgress=true;
244
- currentQuestionIndex=0;userAnswers=[];
245
- showQuestion();
246
- });
247
- nextBtn.addEventListener('click',()=>handleNextQuestion(false));
248
- retryBtn.addEventListener('click',()=>{currentQuestionIndex=0;userAnswers=[];scoreContainer.classList.add('hidden');initializeQuiz();});
249
- saveBtn.addEventListener('click',()=>{html2pdf().from(document.getElementById('results-content')).set({margin:1,filename:`${topic}-results.pdf`,image:{type:'jpeg',quality:0.98},html2canvas:{scale:2},jsPDF:{unit:'in',format:'letter',orientation:'portrait'}}).save();});
250
-
251
- async function loadQuizData(){
252
- const count=parseInt(params.get("count"))||5;
253
- try{
254
- const res=await fetch(`/api/quiz/${topic}?count=${count}`);
255
- // const res = await fetch(`./data/${topic}.json`);
256
-
257
- if(!res.ok) throw new Error("Could not load quiz file!");
258
- const rawData = await res.json();
259
- quizData = rawData.questions.map(q => ({
260
- questionText: q.questionText,
261
- options: q.options,
262
- answer: q.options[q.correctAnswerIndex], // convert index to string
263
- explanation: q.explanation
264
- }));
265
- }catch(e){alert("Failed to load quiz data.");quizData=[];}
266
-
267
- instructionQCount.textContent=quizData.length;
268
- instructionTime.textContent=QUESTION_TIME;
269
- instructionAttempts.textContent=ATTEMPT_LIMIT-quizAttempts;
270
- initializeQuiz();
271
- }
272
-
273
- window.addEventListener('load',loadQuizData);
274
- </script>
275
- </body>
276
- </html>
277
  {% endblock %}
 
1
+ {% extends "Test-layout.html" %}
2
+
3
+
4
+ {% block content %}<!DOCTYPE html>
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="UTF-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
+ <title>Dynamic General Knowledge Quiz</title>
10
+ <script src="https://cdn.tailwindcss.com"></script>
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
12
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
14
+ <style>
15
+ body { font-family: 'Inter', sans-serif; }
16
+ .quiz-option { transition: all 0.2s ease-in-out; }
17
+ .quiz-option:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
18
+ .quiz-option input:checked + label { background-color: #3b82f6; color: white; border-color: #2563eb; }
19
+ .correct { border-left: 5px solid #22c55e; }
20
+ .incorrect { border-left: 5px solid #ef4444; }
21
+ .progress-bar { transition: width 0.3s ease-in-out; }
22
+ #timer { transition: color 0.3s ease-in-out; }
23
+ kbd { background-color: #f3f4f6; border: 1px solid #d1d5db; border-radius: 0.25rem; padding: 0.25rem 0.5rem; font-family: monospace; font-weight: 600; }
24
+ </style>
25
+ </head>
26
+ <body class="bg-gray-100 flex items-center justify-center min-h-screen p-4">
27
+
28
+ <!-- Start Screen -->
29
+ <div id="start-container" class="w-full max-w-2xl bg-white p-6 sm:p-8 rounded-2xl shadow-lg text-center">
30
+ <h1 class="text-3xl sm:text-4xl font-bold text-gray-800 mb-4">General Knowledge Challenge</h1>
31
+ <p class="text-gray-600 mb-8">Test your knowledge with these quick-fire questions!</p>
32
+ <div class="text-left bg-gray-50 p-4 rounded-lg border border-gray-200 mb-8">
33
+ <h3 class="font-bold text-lg mb-3 text-gray-700">📜 Instructions</h3>
34
+ <ul class="list-disc list-inside space-y-2 text-gray-600">
35
+ <li>There are <strong id="instruction-q-count">0</strong> questions in total.</li>
36
+ <li>You will have <strong id="instruction-time">15</strong> seconds to answer each question.</li>
37
+ <li>You have a total of <strong id="instruction-attempts">3</strong> attempts to take this quiz.</li>
38
+ <li class="font-semibold text-yellow-700">Once the quiz starts, do not switch tabs or windows.</li>
39
+ <li class="font-semibold text-yellow-700">Emergency exit: <kbd>Esc</kbd>, <kbd>Space</kbd>, <kbd>A</kbd>, <kbd>M</kbd>.</li>
40
+ </ul>
41
+ </div>
42
+ <button id="start-btn" class="w-full bg-blue-600 text-white font-bold py-3 px-4 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-4 focus:ring-blue-300 transition-transform transform hover:scale-105">
43
+ Start Quiz
44
+ </button>
45
+ </div>
46
+
47
+ <!-- Quiz View -->
48
+ <div id="quiz-container" class="hidden w-full max-w-2xl bg-white p-6 sm:p-8 rounded-2xl shadow-lg">
49
+ <div class="flex justify-between items-center mb-4">
50
+ <div id="progress-container" class="text-gray-500 font-semibold">
51
+ Question <span id="current-question-num">1</span> of <span id="total-question-num">0</span>
52
+ </div>
53
+ <div id="timer-container" class="font-bold text-lg text-gray-700">
54
+ Time: <span id="timer">15</span>s
55
+ </div>
56
+ </div>
57
+ <div class="w-full bg-gray-200 rounded-full h-2.5 mb-6">
58
+ <div id="progress-bar" class="bg-blue-600 h-2.5 rounded-full progress-bar" style="width: 0%"></div>
59
+ </div>
60
+ <div id="question-text" class="text-lg sm:text-xl font-semibold text-gray-800 mb-6 text-center"></div>
61
+ <div id="options-container" class="space-y-4"></div>
62
+ <div id="feedback" class="text-red-500 text-center font-medium mt-4 h-6"></div>
63
+ <button id="next-btn" class="w-full bg-blue-600 text-white font-bold py-3 px-4 rounded-lg mt-6 hover:bg-blue-700 focus:outline-none focus:ring-4 focus:ring-blue-300 transition-transform transform hover:scale-105">
64
+ Next Question
65
+ </button>
66
+ </div>
67
+
68
+ <!-- Results View -->
69
+ <div id="score-container" class="hidden w-full max-w-3xl bg-white p-6 sm:p-8 rounded-2xl shadow-lg">
70
+ <div id="results-content">
71
+ <h2 class="text-2xl sm:text-3xl font-bold text-gray-800 mb-2 text-center">Quiz Complete!</h2>
72
+ <p class="text-gray-600 mb-6 text-center">You scored <span id="final-score" class="font-bold text-blue-600 text-xl">0</span> out of <span id="total-questions" class="font-bold text-blue-600 text-xl">0</span></p>
73
+ <div id="results-breakdown" class="space-y-4 text-left mt-8"></div>
74
+ </div>
75
+ <div class="w-full max-w-md mx-auto mt-8 flex flex-col sm:flex-row gap-4">
76
+ <button id="retry-btn" class="w-full bg-gray-700 text-white font-bold py-3 px-4 rounded-lg hover:bg-gray-800 focus:outline-none focus:ring-4 focus:ring-gray-300 transition-transform transform hover:scale-105">
77
+ Try Again
78
+ </button>
79
+ <button id="save-btn" class="w-full bg-green-600 text-white font-bold py-3 px-4 rounded-lg hover:bg-green-700 focus:outline-none focus:ring-4 focus:ring-green-300 transition-transform transform hover:scale-105">
80
+ Save Results
81
+ </button>
82
+ </div>
83
+ <p id="attempts-left-msg" class="text-center text-sm text-gray-500 mt-4"></p>
84
+ </div>
85
+
86
+ <!-- Limit View -->
87
+ <div id="limit-container" class="hidden w-full max-w-xl bg-white p-6 sm:p-8 rounded-2xl shadow-lg text-center">
88
+ <h2 class="text-2xl sm:text-3xl font-bold text-gray-800 mb-4">🚫 Attempt Limit Reached</h2>
89
+ <p class="text-gray-600 text-lg">You have already taken the <span id="topic-name-limit" class="font-semibold text-blue-600"></span> quiz 3 times.</p>
90
+ </div>
91
+
92
+ <script>
93
+ let quizData = [];
94
+ let currentQuestionIndex = 0;
95
+ let userAnswers = [];
96
+ let shuffledQuizData = [];
97
+ let quizInProgress = false;
98
+ let timerInterval;
99
+
100
+ // ✅ Topic-based attempt tracking
101
+ const params = new URLSearchParams(window.location.search);
102
+ const topic = params.get("topic") || "general";
103
+ const ATTEMPT_LIMIT = 60;
104
+ const ATTEMPT_KEY = `quizAttempts_${topic}`;
105
+ let quizAttempts = parseInt(localStorage.getItem(ATTEMPT_KEY)) || 0;
106
+
107
+ const QUESTION_TIME = 15;
108
+
109
+ // DOM references
110
+ const startContainer = document.getElementById('start-container');
111
+ const quizContainer = document.getElementById('quiz-container');
112
+ const scoreContainer = document.getElementById('score-container');
113
+ const limitContainer = document.getElementById('limit-container');
114
+ const startBtn = document.getElementById('start-btn');
115
+ const instructionQCount = document.getElementById('instruction-q-count');
116
+ const instructionTime = document.getElementById('instruction-time');
117
+ const instructionAttempts = document.getElementById('instruction-attempts');
118
+ const currentQNumEl = document.getElementById('current-question-num');
119
+ const totalQNumEl = document.getElementById('total-question-num');
120
+ const questionTextEl = document.getElementById('question-text');
121
+ const optionsContainerEl = document.getElementById('options-container');
122
+ const feedbackEl = document.getElementById('feedback');
123
+ const nextBtn = document.getElementById('next-btn');
124
+ const progressBar = document.getElementById('progress-bar');
125
+ const timerEl = document.getElementById('timer');
126
+ const finalScoreEl = document.getElementById('final-score');
127
+ const totalQuestionsEl = document.getElementById('total-questions');
128
+ const resultsBreakdownEl = document.getElementById('results-breakdown');
129
+ const retryBtn = document.getElementById('retry-btn');
130
+ const saveBtn = document.getElementById('save-btn');
131
+
132
+ // 🧩 Emergency Exit Key Listener
133
+ document.addEventListener('keydown', (e) => {
134
+ const emergencyKeys = ['Escape', ' ', 'a', 'A', 'm', 'M'];
135
+ if (quizInProgress && emergencyKeys.includes(e.key)) {
136
+ e.preventDefault();
137
+ terminateQuiz('⚠️ Emergency exit triggered!');
138
+ }
139
+ });
140
+
141
+ // 🧩 Emergency Exit Handler
142
+ function terminateQuiz(message) {
143
+ quizInProgress = false;
144
+ clearInterval(timerInterval);
145
+
146
+ // Hide quiz, show alert, save attempt
147
+ quizContainer.classList.add('hidden');
148
+ startContainer.classList.remove('hidden');
149
+
150
+ alert(message);
151
+
152
+ // Count as one attempt
153
+ quizAttempts++;
154
+ localStorage.setItem(ATTEMPT_KEY, quizAttempts);
155
+ initializeQuiz();
156
+ }
157
+
158
+ // Utility
159
+ function shuffleArray(array){for(let i=array.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[array[i],array[j]]=[array[j],array[i]];}}
160
+
161
+ function startTimer(){
162
+ let timeLeft=QUESTION_TIME;
163
+ timerEl.textContent=timeLeft;
164
+ timerEl.classList.remove('text-red-500');
165
+ timerInterval=setInterval(()=>{
166
+ timeLeft--;
167
+ timerEl.textContent=timeLeft;
168
+ if(timeLeft<=5) timerEl.classList.add('text-red-500');
169
+ if(timeLeft<=0){clearInterval(timerInterval);handleNextQuestion(true);}
170
+ },1000);
171
+ }
172
+
173
+ function showQuestion(){
174
+ clearInterval(timerInterval);
175
+ feedbackEl.textContent='';
176
+ const q=shuffledQuizData[currentQuestionIndex];
177
+ currentQNumEl.textContent=currentQuestionIndex+1;
178
+ totalQNumEl.textContent=shuffledQuizData.length;
179
+ progressBar.style.width=`${((currentQuestionIndex+1)/shuffledQuizData.length)*100}%`;
180
+ questionTextEl.textContent=q.questionText;
181
+ optionsContainerEl.innerHTML='';
182
+ const opts=[...q.options]; shuffleArray(opts);
183
+ opts.forEach(opt=>{
184
+ const id=`q${currentQuestionIndex}-${opt.replace(/\s+/g,'-')}`;
185
+ const div=document.createElement('div');div.classList.add('quiz-option');
186
+ const input=document.createElement('input');input.type='radio';input.name=`question${currentQuestionIndex}`;input.id=id;input.value=opt;input.classList.add('hidden');
187
+ const label=document.createElement('label');label.htmlFor=id;label.textContent=opt;label.classList.add('block','w-full','p-4','border-2','border-gray-200','rounded-lg','cursor-pointer','text-gray-700','font-medium','hover:border-blue-400');
188
+ div.appendChild(input);div.appendChild(label);optionsContainerEl.appendChild(div);
189
+ });
190
+ nextBtn.textContent=currentQuestionIndex===shuffledQuizData.length-1?'Finish Quiz':'Next Question';
191
+ startTimer();
192
+ }
193
+
194
+ function handleNextQuestion(timedOut=false){
195
+ clearInterval(timerInterval);
196
+ const sel=document.querySelector(`input[name="question${currentQuestionIndex}"]:checked`);
197
+ if(timedOut) userAnswers.push(null);
198
+ else{
199
+ if(!sel){feedbackEl.textContent='Please select an answer!';startTimer();return;}
200
+ userAnswers.push(sel.value);
201
+ }
202
+ currentQuestionIndex++;
203
+ currentQuestionIndex<shuffledQuizData.length?showQuestion():showResults();
204
+ }
205
+
206
+ function showResults(){
207
+ quizInProgress=false;clearInterval(timerInterval);
208
+ quizContainer.classList.add('hidden');scoreContainer.classList.remove('hidden');
209
+ quizAttempts++;localStorage.setItem(ATTEMPT_KEY,quizAttempts);
210
+ resultsBreakdownEl.innerHTML='';
211
+ let score=0;
212
+ shuffledQuizData.forEach((q,i)=>{
213
+ const ua=userAnswers[i];const correct=ua===q.answer;if(correct)score++;
214
+ const div=document.createElement('div');div.classList.add('p-4','rounded-lg','bg-gray-50',correct?'correct':'incorrect');
215
+ div.innerHTML=`<p class="font-bold text-gray-800">${i+1}. ${q.question}</p>
216
+ <p class="mt-2 text-sm ${correct?'text-green-700':'text-red-700'}">Your answer: <span class="font-semibold">${ua||"Time's up!"}</span></p>
217
+ ${!correct?`<p class="mt-1 text-sm text-green-700">Correct: <span class="font-semibold">${q.answer}</span></p>`:''}
218
+ <p class="mt-2 text-sm text-gray-600 bg-gray-100 p-2 rounded"><span class="font-semibold">Explanation:</span> ${q.explanation}</p>`;
219
+ resultsBreakdownEl.appendChild(div);
220
+ });
221
+ finalScoreEl.textContent=score;totalQuestionsEl.textContent=shuffledQuizData.length;
222
+ if(score/shuffledQuizData.length>=0.8) confetti({particleCount:150,spread:90,origin:{y:0.6}});
223
+ const left=ATTEMPT_LIMIT-quizAttempts;
224
+ const msg=document.getElementById('attempts-left-msg');
225
+ if(left<=0){retryBtn.disabled=true;retryBtn.textContent='No Attempts Left';retryBtn.classList.add('bg-gray-400','cursor-not-allowed');msg.textContent='You have used all your attempts.';}
226
+ else{msg.textContent=`You have ${left} attempt${left>1?'s':''} left.`;}
227
+ }
228
+
229
+ function initializeQuiz(){
230
+ if(quizAttempts>=ATTEMPT_LIMIT){
231
+ document.getElementById('topic-name-limit').textContent=topic.replace(/_/g,' ');
232
+ startContainer.classList.add('hidden');quizContainer.classList.add('hidden');scoreContainer.classList.add('hidden');limitContainer.classList.remove('hidden');
233
+ } else {
234
+ startContainer.classList.remove('hidden');quizContainer.classList.add('hidden');scoreContainer.classList.add('hidden');limitContainer.classList.add('hidden');
235
+ }
236
+ }
237
+
238
+ startBtn.addEventListener('click',()=>{
239
+ if(quizData.length===0){alert("Quiz data not loaded!");return;}
240
+ shuffledQuizData=[...quizData];
241
+ // shuffleArray(shuffledQuizData);
242
+ startContainer.classList.add('hidden');
243
+ quizContainer.classList.remove('hidden');quizInProgress=true;
244
+ currentQuestionIndex=0;userAnswers=[];
245
+ showQuestion();
246
+ });
247
+ nextBtn.addEventListener('click',()=>handleNextQuestion(false));
248
+ retryBtn.addEventListener('click',()=>{currentQuestionIndex=0;userAnswers=[];scoreContainer.classList.add('hidden');initializeQuiz();});
249
+ saveBtn.addEventListener('click',()=>{html2pdf().from(document.getElementById('results-content')).set({margin:1,filename:`${topic}-results.pdf`,image:{type:'jpeg',quality:0.98},html2canvas:{scale:2},jsPDF:{unit:'in',format:'letter',orientation:'portrait'}}).save();});
250
+
251
+ async function loadQuizData(){
252
+ const count=parseInt(params.get("count"))||5;
253
+ try{
254
+ const res=await fetch(`/api/quiz/${topic}?count=${count}`);
255
+ // const res = await fetch(`./data/${topic}.json`);
256
+
257
+ if(!res.ok) throw new Error("Could not load quiz file!");
258
+ const rawData = await res.json();
259
+ quizData = rawData.questions.map(q => ({
260
+ questionText: q.questionText,
261
+ options: q.options,
262
+ answer: q.options[q.correctAnswerIndex], // convert index to string
263
+ explanation: q.explanation
264
+ }));
265
+ }catch(e){alert("Failed to load quiz data.");quizData=[];}
266
+
267
+ instructionQCount.textContent=quizData.length;
268
+ instructionTime.textContent=QUESTION_TIME;
269
+ instructionAttempts.textContent=ATTEMPT_LIMIT-quizAttempts;
270
+ initializeQuiz();
271
+ }
272
+
273
+ window.addEventListener('load',loadQuizData);
274
+ </script>
275
+ </body>
276
+ </html>
277
  {% endblock %}