p2/js/quiz.js
chriseo 2210ef4623 Fix explanation letter prefix after answer shuffle
After shuffleQuestion() reorders answers, the hardcoded A–D prefix in
each explanation text no longer matched the displayed option letter.
Replace the leading letter dynamically based on the shuffled position.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:16:57 +02:00

226 lines
7.1 KiB
JavaScript

/* ===== Language detection ===== */
const LANG_OPTIONS = [
{ key: 'nl', label: 'Nederlands', flag: '🇳🇱', data: window.QUESTIONS_NL },
{ key: 'en', label: 'English', flag: '🇬🇧', data: window.QUESTIONS_EN },
].filter(l => l.data && l.data.length > 0);
let ALL_QUESTIONS = [];
/* ===== Quiz state ===== */
let sessionQ = [];
let currentIdx = 0;
let score = 0;
let wrongIds = [];
let selectedCount = 0;
let answered = false;
let timerInterval = null;
let elapsedSeconds = 0;
let answeredCount = 0;
/* ===== Helpers ===== */
function shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
function fmtTime(s) {
return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0');
}
function startTimer() {
stopTimer();
timerInterval = setInterval(() => { elapsedSeconds++; updateTimerDisplay(); }, 1000);
}
function stopTimer() {
if (timerInterval) { clearInterval(timerInterval); timerInterval = null; }
}
function updateTimerDisplay() {
const avg = answeredCount > 0 ? Math.round(elapsedSeconds / answeredCount) + 's' : '—';
document.getElementById('timer-display').textContent = fmtTime(elapsedSeconds) + ' | gem. ' + avg;
}
function showScreen(id) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(id).classList.add('active');
}
function shuffleQuestion(q) {
const idx = [0, 1, 2, 3];
const si = shuffle(idx);
return {
exam: q.exam, qnum: q.qnum, q: q.q,
opts: si.map(i => q.opts[i]),
correct: si.indexOf(q.correct),
explanation: si.map(i => q.explanation[i]),
};
}
/* ===== Language screen ===== */
function initLangScreen() {
if (LANG_OPTIONS.length === 1) {
ALL_QUESTIONS = LANG_OPTIONS[0].data;
initCountButtons();
showScreen('start');
return;
}
const grid = document.getElementById('lang-grid');
grid.innerHTML = '';
LANG_OPTIONS.forEach(lang => {
const btn = document.createElement('button');
btn.className = 'lang-btn';
btn.innerHTML = '<span>' + lang.flag + '</span><span>' + lang.label + '</span>';
btn.onclick = () => {
ALL_QUESTIONS = lang.data;
initCountButtons();
showScreen('start');
};
grid.appendChild(btn);
});
showScreen('lang');
}
/* ===== Start screen ===== */
function initCountButtons() {
const counts = [5, 10, 20, 60, 9999];
const grid = document.getElementById('count-grid');
grid.innerHTML = '';
const total = ALL_QUESTIONS.length;
counts.forEach(n => {
const btn = document.createElement('button');
btn.className = 'count-btn';
const label = n === 9999 ? 'Alle ' + total : String(n);
btn.textContent = label;
btn.dataset.count = n;
const isDisabled = n !== 9999 && total < n;
btn.disabled = isDisabled;
btn.onclick = () => selectCount(n, btn);
grid.appendChild(btn);
});
document.getElementById('start-btn').disabled = true;
selectedCount = 0;
}
function selectCount(n, btn) {
selectedCount = Math.min(n, ALL_QUESTIONS.length);
document.querySelectorAll('.count-btn').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
document.getElementById('start-btn').disabled = false;
}
function startQuiz() {
const pool = shuffle(ALL_QUESTIONS.map(shuffleQuestion));
sessionQ = pool.slice(0, selectedCount);
currentIdx = 0; score = 0; wrongIds = []; answered = false;
elapsedSeconds = 0; answeredCount = 0;
updateTimerDisplay();
showScreen('quiz');
renderQuestion();
}
/* ===== Quiz screen ===== */
function renderQuestion() {
answered = false;
const q = sessionQ[currentIdx];
const total = sessionQ.length;
document.getElementById('progress-fill').style.width = ((currentIdx / total) * 100) + '%';
document.getElementById('q-meta').textContent = 'Vraag ' + (currentIdx + 1) + ' van ' + total;
document.getElementById('q-text').textContent = q.q;
const optDiv = document.getElementById('options');
optDiv.innerHTML = '';
['A', 'B', 'C', 'D'].forEach((ltr, i) => {
const btn = document.createElement('button');
btn.className = 'option-btn';
btn.innerHTML = '<span class="opt-letter">' + ltr + '.</span><span>' + q.opts[i] + '</span>';
btn.onclick = () => handleAnswer(i);
optDiv.appendChild(btn);
});
document.getElementById('explanation').style.display = 'none';
document.getElementById('next-btn').style.display = 'none';
startTimer();
}
function handleAnswer(chosen) {
if (answered) return;
answered = true;
stopTimer();
answeredCount++;
updateTimerDisplay();
const q = sessionQ[currentIdx];
document.querySelectorAll('.option-btn').forEach((btn, i) => {
btn.disabled = true;
if (i === q.correct) btn.classList.add('correct');
else if (i === chosen && chosen !== q.correct) btn.classList.add('wrong');
});
if (chosen === q.correct) score++;
else wrongIds.push(currentIdx);
const expDiv = document.getElementById('explanation');
expDiv.style.display = 'block';
expDiv.innerHTML =
'<div class="explanation-source">Proefexamen ' + q.exam + ', vraag ' + q.qnum + '</div>' +
'<div class="explanation-label">Toelichting</div>' +
q.explanation.map((e, i) =>
'<p>' + e.replace(/^[A-D]\.\s*/, 'ABCD'[i] + '. ') + '</p>'
).join('');
const nextBtn = document.getElementById('next-btn');
nextBtn.style.display = 'inline-flex';
nextBtn.textContent = currentIdx === sessionQ.length - 1 ? 'Bekijk resultaat' : 'Volgende vraag →';
}
function nextQuestion() {
currentIdx++;
if (currentIdx >= sessionQ.length) showResult();
else renderQuestion();
}
/* ===== Result screen ===== */
function showResult() {
stopTimer();
showScreen('result');
const total = sessionQ.length;
const pct = Math.round((score / total) * 100);
const circle = document.getElementById('score-circle');
circle.innerHTML = pct + '%<span>' + (pct >= 60 ? 'Geslaagd' : 'Niet geslaagd') + '</span>';
circle.className = 'score-circle ' + (pct >= 60 ? 'score-pass' : 'score-fail');
document.getElementById('result-label').textContent = pct >= 60 ? 'Geslaagd' : 'Niet geslaagd';
document.getElementById('result-sub').textContent = score + ' van de ' + total + ' vragen goed';
document.getElementById('stat-correct').textContent = score;
document.getElementById('stat-wrong').textContent = total - score;
document.getElementById('stat-time').textContent =
answeredCount > 0 ? Math.round(elapsedSeconds / answeredCount) + 's' : '—';
document.getElementById('retry-btn').style.display = wrongIds.length > 0 ? 'inline-flex' : 'none';
}
function goStart() {
stopTimer();
document.querySelectorAll('.count-btn').forEach(b => b.classList.remove('selected'));
document.getElementById('start-btn').disabled = true;
selectedCount = 0;
showScreen('start');
}
function retryWrong() {
sessionQ = shuffle(wrongIds.map(i => shuffleQuestion(sessionQ[i])));
currentIdx = 0; score = 0; wrongIds = []; answered = false;
elapsedSeconds = 0; answeredCount = 0;
updateTimerDisplay();
showScreen('quiz');
renderQuestion();
}
/* ===== Boot ===== */
document.addEventListener('DOMContentLoaded', initLangScreen);