diff --git a/css/style.css b/css/style.css index bd617aa..c9c8d15 100644 --- a/css/style.css +++ b/css/style.css @@ -460,6 +460,183 @@ button { #retry-btn:hover { opacity: .9; } +/* ===== Mode selector ===== */ +.mode-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.625rem; + max-width: 480px; + margin: 0 auto 2rem; +} + +.mode-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.25rem; + min-height: 64px; + padding: 0.75rem 0.75rem; + border: 2px solid var(--border); + border-radius: var(--radius); + background: var(--surface); + color: var(--text); + box-shadow: var(--shadow); + transition: border-color .15s, background .15s; + cursor: pointer; +} + +.mode-btn:hover { + border-color: var(--accent); + background: var(--accent-light); +} + +.mode-btn.selected { + border-color: var(--accent); + background: var(--accent-light); + color: var(--accent); +} + +.mode-label { + font-size: 0.9375rem; + font-weight: 600; +} + +.mode-desc { + font-size: 0.75rem; + color: var(--text-muted); + line-height: 1.3; +} + +.mode-btn.selected .mode-desc { + color: var(--accent); + opacity: .8; +} + +/* ===== Review section (exam mode) ===== */ +.review-header { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 0.75rem; + margin: 2rem 0 1rem; + padding-top: 1.5rem; + border-top: 1px solid var(--border); +} + +.review-header h3 { + font-size: 1rem; + font-weight: 700; + color: var(--text); +} + +.toggle-correct-btn { + min-height: 36px; + padding: 0.375rem 0.875rem; + border-radius: var(--radius-sm); + border: 2px solid var(--border); + background: var(--surface); + color: var(--text-muted); + font-size: 0.8125rem; + font-weight: 500; + box-shadow: var(--shadow); + transition: border-color .15s, color .15s, background .15s; +} + +.toggle-correct-btn:hover { + border-color: var(--accent); + color: var(--accent); +} + +.toggle-correct-btn.active { + border-color: var(--accent); + background: var(--accent-light); + color: var(--accent); +} + +.review-empty { + font-size: 0.875rem; + color: var(--text-muted); + text-align: center; + padding: 1rem 0; +} + +.review-card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1rem 1.125rem; + margin-bottom: 0.75rem; + box-shadow: var(--shadow); + text-align: left; +} + +.review-card--wrong { border-left: 3px solid var(--wrong-border); } +.review-card--correct { border-left: 3px solid var(--correct-border); } + +.review-card-head { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.625rem; +} + +.review-badge { + font-size: 0.6875rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: .06em; + padding: 0.2rem 0.5rem; + border-radius: 4px; +} + +.review-badge--wrong { background: var(--wrong-bg); color: var(--wrong-text); } +.review-badge--correct { background: var(--correct-bg); color: var(--correct-text); } + +.review-qref { + font-size: 0.75rem; + color: var(--text-muted); +} + +.review-question { + font-size: 0.9375rem; + font-weight: 500; + line-height: 1.5; + margin-bottom: 0.625rem; +} + +.review-chosen { + font-size: 0.8125rem; + color: var(--wrong-text); + background: var(--wrong-bg); + border-radius: var(--radius-sm); + padding: 0.375rem 0.625rem; + margin-bottom: 0.375rem; +} + +.review-correct { + font-size: 0.8125rem; + color: var(--correct-text); + background: var(--correct-bg); + border-radius: var(--radius-sm); + padding: 0.375rem 0.625rem; + margin-bottom: 0.75rem; +} + +.review-explanations { + border-top: 1px solid var(--border); + padding-top: 0.625rem; +} + +.review-exp-item { + font-size: 0.8125rem; + line-height: 1.6; + color: var(--text); + margin-bottom: 0.375rem; +} + +.review-exp-item:last-child { margin-bottom: 0; } + /* ===== Copyright footer ===== */ .copyright { text-align: center; diff --git a/index.html b/index.html index 2f79e91..1b76c68 100644 --- a/index.html +++ b/index.html @@ -25,6 +25,7 @@

PRINCE2 7 Foundation Quiz

Kies hoeveel vragen je wilt oefenen

+
@@ -70,6 +71,14 @@ + diff --git a/js/quiz.js b/js/quiz.js index 7686ada..a2e6701 100644 --- a/js/quiz.js +++ b/js/quiz.js @@ -11,8 +11,11 @@ let sessionQ = []; let currentIdx = 0; let score = 0; let wrongIds = []; +let correctIds = []; +let chosenAnswers = []; // chosenAnswers[i] = chosen option index for question i let selectedCount = 0; let answered = false; +let quizMode = 'feedback'; // 'feedback' | 'exam' let timerInterval = null; let elapsedSeconds = 0; @@ -66,7 +69,7 @@ function shuffleQuestion(q) { function initLangScreen() { if (LANG_OPTIONS.length === 1) { ALL_QUESTIONS = LANG_OPTIONS[0].data; - initCountButtons(); + initStartScreen(); showScreen('start'); return; } @@ -79,7 +82,7 @@ function initLangScreen() { btn.innerHTML = '' + lang.flag + '' + lang.label + ''; btn.onclick = () => { ALL_QUESTIONS = lang.data; - initCountButtons(); + initStartScreen(); showScreen('start'); }; grid.appendChild(btn); @@ -89,6 +92,11 @@ function initLangScreen() { } /* ===== Start screen ===== */ +function initStartScreen() { + initCountButtons(); + initModeButtons(); +} + function initCountButtons() { const counts = [5, 10, 20, 60, 9999]; const grid = document.getElementById('count-grid'); @@ -111,6 +119,26 @@ function initCountButtons() { selectedCount = 0; } +function initModeButtons() { + const modes = [ + { key: 'feedback', label: 'Directe feedback', desc: 'Antwoord zien na elke vraag' }, + { key: 'exam', label: 'Examenmodus', desc: 'Alle vragen eerst, daarna beoordeling' }, + ]; + const grid = document.getElementById('mode-grid'); + grid.innerHTML = ''; + modes.forEach(m => { + const btn = document.createElement('button'); + btn.className = 'mode-btn' + (m.key === quizMode ? ' selected' : ''); + btn.innerHTML = '' + m.label + '' + m.desc + ''; + btn.onclick = () => { + quizMode = m.key; + document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('selected')); + btn.classList.add('selected'); + }; + grid.appendChild(btn); + }); +} + function selectCount(n, btn) { selectedCount = Math.min(n, ALL_QUESTIONS.length); document.querySelectorAll('.count-btn').forEach(b => b.classList.remove('selected')); @@ -121,10 +149,11 @@ function selectCount(n, btn) { function startQuiz() { const pool = shuffle(ALL_QUESTIONS.map(shuffleQuestion)); sessionQ = pool.slice(0, selectedCount); - currentIdx = 0; score = 0; wrongIds = []; answered = false; + currentIdx = 0; score = 0; wrongIds = []; correctIds = []; chosenAnswers = []; answered = false; elapsedSeconds = 0; answeredCount = 0; updateTimerDisplay(); showScreen('quiz'); + if (quizMode === 'exam') startTimer(); renderQuestion(); } @@ -149,23 +178,52 @@ function renderQuestion() { document.getElementById('explanation').style.display = 'none'; document.getElementById('next-btn').style.display = 'none'; - startTimer(); + + if (quizMode === 'feedback') { + startTimer(); + } + // In exam mode the timer is already running (started in startQuiz via first renderQuestion) } function handleAnswer(chosen) { if (answered) return; answered = true; - stopTimer(); answeredCount++; - updateTimerDisplay(); + chosenAnswers[currentIdx] = chosen; + const q = sessionQ[currentIdx]; + const isCorrect = chosen === q.correct; + + if (isCorrect) { score++; correctIds.push(currentIdx); } + else { wrongIds.push(currentIdx); } + + if (quizMode === 'feedback') { + stopTimer(); + updateTimerDisplay(); + showAnswerFeedback(q, chosen); + const nextBtn = document.getElementById('next-btn'); + nextBtn.style.display = 'inline-flex'; + nextBtn.textContent = currentIdx === sessionQ.length - 1 ? 'Bekijk resultaat' : 'Volgende vraag →'; + } else { + // Exam mode: highlight answer, keep timer running, show minimal next + updateTimerDisplay(); + document.querySelectorAll('.option-btn').forEach((btn, i) => { + btn.disabled = true; + if (i === q.correct) btn.classList.add('correct'); + else if (i === chosen && !isCorrect) btn.classList.add('wrong'); + }); + const nextBtn = document.getElementById('next-btn'); + nextBtn.style.display = 'inline-flex'; + nextBtn.textContent = currentIdx === sessionQ.length - 1 ? 'Bekijk resultaat' : 'Volgende vraag →'; + } +} + +function showAnswerFeedback(q, chosen) { 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'; @@ -175,10 +233,6 @@ function handleAnswer(chosen) { q.explanation.map((e, i) => '

' + e.replace(/^[A-D]\.\s*/, 'ABCD'[i] + '. ') + '

' ).join(''); - - const nextBtn = document.getElementById('next-btn'); - nextBtn.style.display = 'inline-flex'; - nextBtn.textContent = currentIdx === sessionQ.length - 1 ? 'Bekijk resultaat' : 'Volgende vraag →'; } function nextQuestion() { @@ -203,6 +257,75 @@ function showResult() { document.getElementById('stat-time').textContent = answeredCount > 0 ? Math.round(elapsedSeconds / answeredCount) + 's' : '—'; document.getElementById('retry-btn').style.display = wrongIds.length > 0 ? 'inline-flex' : 'none'; + + // In exam mode: render review section + const reviewSection = document.getElementById('review-section'); + if (quizMode === 'exam') { + renderReview(); + reviewSection.style.display = 'block'; + } else { + reviewSection.style.display = 'none'; + } +} + +function renderReview() { + // Wrong questions (always shown) + const wrongContainer = document.getElementById('wrong-review'); + wrongContainer.innerHTML = wrongIds.length === 0 + ? '

Geen fout beantwoorde vragen.

' + : wrongIds.map(i => buildReviewCard(sessionQ[i], chosenAnswers[i], true)).join(''); + + // Correct questions (hidden by default) + const correctContainer = document.getElementById('correct-review'); + correctContainer.innerHTML = correctIds.length === 0 + ? '

Geen goed beantwoorde vragen.

' + : correctIds.map(i => buildReviewCard(sessionQ[i], chosenAnswers[i], false)).join(''); + correctContainer.style.display = 'none'; + + // Toggle button + const toggleBtn = document.getElementById('toggle-correct-btn'); + if (correctIds.length === 0) { + toggleBtn.style.display = 'none'; + } else { + toggleBtn.style.display = 'inline-flex'; + toggleBtn.textContent = 'Toon ook goed beantwoorde vragen (' + correctIds.length + ')'; + toggleBtn.classList.remove('active'); + } +} + +function buildReviewCard(q, chosen, isWrong) { + const letters = 'ABCD'; + const chosenLetter = letters[chosen]; + const correctLetter = letters[q.correct]; + const explanations = q.explanation.map((e, i) => + '

' + e.replace(/^[A-D]\.\s*/, letters[i] + '. ') + '

' + ).join(''); + + return '
' + + '
' + + '' + + (isWrong ? 'Fout' : 'Goed') + + '' + + 'Proefexamen ' + q.exam + ', vraag ' + q.qnum + '' + + '
' + + '
' + q.q + '
' + + (isWrong + ? '
Jouw antwoord: ' + chosenLetter + '. ' + q.opts[chosen] + '
' + : '') + + '
Juist antwoord: ' + correctLetter + '. ' + q.opts[q.correct] + '
' + + '
' + explanations + '
' + + '
'; +} + +function toggleCorrectReview() { + const container = document.getElementById('correct-review'); + const btn = document.getElementById('toggle-correct-btn'); + const visible = container.style.display !== 'none'; + container.style.display = visible ? 'none' : 'block'; + btn.textContent = visible + ? 'Toon ook goed beantwoorde vragen (' + correctIds.length + ')' + : 'Verberg goed beantwoorde vragen'; + btn.classList.toggle('active', !visible); } function goStart() { @@ -215,10 +338,11 @@ function goStart() { function retryWrong() { sessionQ = shuffle(wrongIds.map(i => shuffleQuestion(sessionQ[i]))); - currentIdx = 0; score = 0; wrongIds = []; answered = false; + currentIdx = 0; score = 0; wrongIds = []; correctIds = []; chosenAnswers = []; answered = false; elapsedSeconds = 0; answeredCount = 0; updateTimerDisplay(); showScreen('quiz'); + if (quizMode === 'exam') startTimer(); renderQuestion(); }