Utvid multiple choice: 137 spørsmål fordelt på 36 deltemaer
- Quiz utvidet fra 49 til 137 spørsmål - Hvert hovedtema (etikk/bærekraft/samfunn/verktoy) får 30-37 spørsmål - Nytt subtopic-felt grupperer spørsmål i finere kategorier (Pliktetikk (Kant), Carrolls pyramide, CSRD og dobbel vesentlighet, etc.) - Quiz-UI har nå et "deltema-hub" som lar deg velge mellom alle eller et spesifikt deltema - Resultatside viser fordeling per deltema med fargekodede progressbarer - Tastatursnarveier (1-4 svarer, Enter/Space går videre) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1453,6 +1453,113 @@ blockquote {
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sub-topic grid */
|
||||||
|
.quiz-sub-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
gap: var(--sp-3);
|
||||||
|
margin-top: var(--sp-4);
|
||||||
|
}
|
||||||
|
.quiz-sub-card {
|
||||||
|
text-align: left;
|
||||||
|
padding: var(--sp-5);
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.18s var(--ease);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.quiz-sub-card:hover {
|
||||||
|
border-color: var(--ink-2);
|
||||||
|
background: var(--surface-2);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
.quiz-sub-card--all {
|
||||||
|
background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 12%, var(--surface)), var(--surface));
|
||||||
|
border-color: color-mix(in srgb, var(--accent) 28%, var(--line));
|
||||||
|
}
|
||||||
|
.quiz-sub-card--all:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
.quiz-sub-card__count {
|
||||||
|
font-family: var(--f-display);
|
||||||
|
font-size: var(--s-6);
|
||||||
|
font-weight: 300;
|
||||||
|
font-variation-settings: "opsz" 48, "SOFT" 30;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--ink);
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
.quiz-sub-card--all .quiz-sub-card__count { color: var(--accent); }
|
||||||
|
.quiz-sub-card__label {
|
||||||
|
font-family: var(--f-body);
|
||||||
|
font-size: var(--s-2);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--ink);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.quiz-sub-card__hint {
|
||||||
|
font-family: var(--f-mono);
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Per-subtopic breakdown on quiz result */
|
||||||
|
.subtopic-breakdown {
|
||||||
|
margin: var(--sp-7) auto 0;
|
||||||
|
max-width: 620px;
|
||||||
|
padding: var(--sp-5);
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
}
|
||||||
|
.subtopic-breakdown__label {
|
||||||
|
font-family: var(--f-mono);
|
||||||
|
font-size: var(--s-0);
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-bottom: var(--sp-4);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.subtopic-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr auto;
|
||||||
|
gap: var(--sp-3);
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--sp-2) 0;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
.subtopic-row:last-child { border-bottom: 0; }
|
||||||
|
.subtopic-row__name {
|
||||||
|
font-size: var(--s-1);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
.subtopic-row__bar {
|
||||||
|
height: 6px;
|
||||||
|
background: var(--line);
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.subtopic-row__fill {
|
||||||
|
height: 100%;
|
||||||
|
transition: width 0.6s var(--ease);
|
||||||
|
}
|
||||||
|
.subtopic-row__score {
|
||||||
|
font-family: var(--f-mono);
|
||||||
|
font-size: var(--s-0);
|
||||||
|
color: var(--ink-2);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
min-width: 3em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===================================================
|
/* ===================================================
|
||||||
Eksamen page
|
Eksamen page
|
||||||
=================================================== */
|
=================================================== */
|
||||||
|
|||||||
1467
app/data/quiz.json
1467
app/data/quiz.json
File diff suppressed because it is too large
Load Diff
257
app/js/quiz.js
257
app/js/quiz.js
@@ -1,16 +1,18 @@
|
|||||||
// =====================================================
|
// =====================================================
|
||||||
// Quiz — multiple choice with explanations
|
// Quiz — multiple choice per hovedtema med sub-temaer
|
||||||
// =====================================================
|
// =====================================================
|
||||||
|
|
||||||
let quizData = null;
|
let quizData = null;
|
||||||
let quizState = {
|
let quizState = {
|
||||||
filter: 'all',
|
category: 'all',
|
||||||
|
subtopic: 'all',
|
||||||
questions: [],
|
questions: [],
|
||||||
current: 0,
|
current: 0,
|
||||||
selected: null,
|
selected: null,
|
||||||
answered: false,
|
answered: false,
|
||||||
correct: 0,
|
correct: 0,
|
||||||
wrong: 0,
|
wrong: 0,
|
||||||
|
perSubtopic: {}, // subtopic -> {correct, total}
|
||||||
finished: false
|
finished: false
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -26,12 +28,39 @@ async function quizLoad() {
|
|||||||
return quizData;
|
return quizData;
|
||||||
}
|
}
|
||||||
|
|
||||||
function quizFilterQuestions(filter) {
|
function quizCategoryLabel(cat) {
|
||||||
if (filter === 'all') return [...quizData];
|
return {
|
||||||
return quizData.filter(q => q.category === filter);
|
etikk: 'Etikk',
|
||||||
|
baerekraft: 'Bærekraft',
|
||||||
|
samfunn: 'Samfunnsansvar',
|
||||||
|
verktoy: 'Verktøy & implementering',
|
||||||
|
all: 'Alt pensum'
|
||||||
|
}[cat] || cat;
|
||||||
|
}
|
||||||
|
|
||||||
|
function categoryQuestions(cat) {
|
||||||
|
if (cat === 'all') return quizData;
|
||||||
|
return quizData.filter(q => q.category === cat);
|
||||||
|
}
|
||||||
|
|
||||||
|
function subtopicsForCategory(cat) {
|
||||||
|
const qs = categoryQuestions(cat);
|
||||||
|
const counts = new Map();
|
||||||
|
qs.forEach(q => {
|
||||||
|
const sub = q.subtopic || 'Tverrgående';
|
||||||
|
counts.set(sub, (counts.get(sub) || 0) + 1);
|
||||||
|
});
|
||||||
|
return [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function quizFilter(cat, subtopic) {
|
||||||
|
const base = categoryQuestions(cat);
|
||||||
|
if (!subtopic || subtopic === 'all') return base;
|
||||||
|
return base.filter(q => (q.subtopic || 'Tverrgående') === subtopic);
|
||||||
}
|
}
|
||||||
|
|
||||||
function quizShuffle(arr) {
|
function quizShuffle(arr) {
|
||||||
|
arr = [...arr];
|
||||||
for (let i = arr.length - 1; i > 0; i--) {
|
for (let i = arr.length - 1; i > 0; i--) {
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
[arr[i], arr[j]] = [arr[j], arr[i]];
|
[arr[i], arr[j]] = [arr[j], arr[i]];
|
||||||
@@ -39,65 +68,125 @@ function quizShuffle(arr) {
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============= Start page (4 main parts) =============
|
||||||
function quizRenderStart() {
|
function quizRenderStart() {
|
||||||
const stage = document.getElementById('quizStage');
|
const stage = document.getElementById('quizStage');
|
||||||
const counts = {
|
const counts = {
|
||||||
all: quizData.length,
|
all: quizData.length,
|
||||||
etikk: quizData.filter(q => q.category === 'etikk').length,
|
etikk: categoryQuestions('etikk').length,
|
||||||
baerekraft: quizData.filter(q => q.category === 'baerekraft').length,
|
baerekraft: categoryQuestions('baerekraft').length,
|
||||||
samfunn: quizData.filter(q => q.category === 'samfunn').length,
|
samfunn: categoryQuestions('samfunn').length,
|
||||||
verktoy: quizData.filter(q => q.category === 'verktoy').length
|
verktoy: categoryQuestions('verktoy').length
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cardFor = (cat, label, eyebrow, romanNum, themeClass) => {
|
||||||
|
const subs = subtopicsForCategory(cat);
|
||||||
|
const chips = subs.slice(0, 4).map(([name]) => `<span class="theme-card__chip">${name}</span>`).join('');
|
||||||
|
const more = subs.length > 4 ? `<span class="theme-card__chip" style="opacity:0.6">+${subs.length - 4} til</span>` : '';
|
||||||
|
return `
|
||||||
|
<button class="theme-card ${themeClass} reveal" data-cat="${cat}" style="text-align:left">
|
||||||
|
<span class="theme-card__num">${romanNum}</span>
|
||||||
|
<div class="theme-card__eyebrow">${eyebrow}</div>
|
||||||
|
<h3 class="theme-card__title">${label}</h3>
|
||||||
|
<p class="theme-card__summary">${counts[cat]} spørsmål · ${subs.length} deltemaer</p>
|
||||||
|
<div class="theme-card__list">${chips}${more}</div>
|
||||||
|
<div class="theme-card__foot"><span>Multiple choice</span><span class="theme-card__arrow">Velg →</span></div>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
stage.innerHTML = `
|
stage.innerHTML = `
|
||||||
<header class="lesson-header reveal" style="text-align:center; border:none; padding-bottom:var(--sp-5)">
|
<header class="lesson-header reveal" style="text-align:center; border:none; padding-bottom:var(--sp-5)">
|
||||||
<div class="lesson-header__num">Studiemodus</div>
|
<div class="lesson-header__num">Studiemodus</div>
|
||||||
<h1 class="lesson-header__title"><em>Selvtest</em></h1>
|
<h1 class="lesson-header__title"><em>Multiple</em> choice</h1>
|
||||||
<div class="lesson-header__meta" style="justify-content:center"><span>Velg kategori — flervalg med forklaringer</span></div>
|
<div class="lesson-header__meta" style="justify-content:center"><span>${counts.all} spørsmål · 4 hovedtemaer · filter på deltema</span></div>
|
||||||
</header>
|
</header>
|
||||||
<div class="themes__grid reveal" style="grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--sp-4); margin-top: var(--sp-7)">
|
|
||||||
<button class="theme-card" data-cat="all" style="--theme-color: var(--accent); --theme-color-bg: transparent; text-align: left">
|
<div class="themes__grid reveal" style="margin-top: var(--sp-7)">
|
||||||
<div class="theme-card__eyebrow">Mix</div>
|
${cardFor('etikk', '<em>Etikk</em>', 'Oppgave I', 'I', 'theme-card--etikk')}
|
||||||
<h3 class="theme-card__title">Alle spørsmål</h3>
|
${cardFor('baerekraft', '<em>Bærekraft</em>', 'Oppgave II', 'II', 'theme-card--baerekraft')}
|
||||||
<div class="theme-card__foot"><span>${counts.all} spørsmål</span><span class="theme-card__arrow">Start →</span></div>
|
${cardFor('samfunn', 'Samfunns<em>ansvar</em>', 'Oppgave III', 'III', 'theme-card--samfunn')}
|
||||||
</button>
|
${cardFor('verktoy', '<em>Verktøy</em> & impl.', 'Oppgave IV · Case', 'IV', 'theme-card--verktoy')}
|
||||||
<button class="theme-card theme-card--etikk" data-cat="etikk">
|
</div>
|
||||||
<div class="theme-card__eyebrow">I</div>
|
|
||||||
<h3 class="theme-card__title"><em>Etikk</em></h3>
|
<div style="margin-top: var(--sp-7); text-align:center" class="reveal">
|
||||||
<div class="theme-card__foot"><span>${counts.etikk} spørsmål</span><span class="theme-card__arrow">Start →</span></div>
|
<button class="btn btn--primary" data-cat="all" style="padding: var(--sp-4) var(--sp-7); font-size: var(--s-1)">
|
||||||
</button>
|
Test meg på <em style="font-family:var(--f-display); font-style:italic; color:inherit">alt</em> — ${counts.all} spørsmål
|
||||||
<button class="theme-card theme-card--baerekraft" data-cat="baerekraft">
|
|
||||||
<div class="theme-card__eyebrow">II</div>
|
|
||||||
<h3 class="theme-card__title"><em>Bærekraft</em></h3>
|
|
||||||
<div class="theme-card__foot"><span>${counts.baerekraft} spørsmål</span><span class="theme-card__arrow">Start →</span></div>
|
|
||||||
</button>
|
|
||||||
<button class="theme-card theme-card--samfunn" data-cat="samfunn">
|
|
||||||
<div class="theme-card__eyebrow">III</div>
|
|
||||||
<h3 class="theme-card__title">Samfunns<em>ansvar</em></h3>
|
|
||||||
<div class="theme-card__foot"><span>${counts.samfunn} spørsmål</span><span class="theme-card__arrow">Start →</span></div>
|
|
||||||
</button>
|
|
||||||
<button class="theme-card theme-card--verktoy" data-cat="verktoy">
|
|
||||||
<div class="theme-card__eyebrow">IV</div>
|
|
||||||
<h3 class="theme-card__title"><em>Verktøy</em></h3>
|
|
||||||
<div class="theme-card__foot"><span>${counts.verktoy} spørsmål</span><span class="theme-card__arrow">Start →</span></div>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
stage.querySelectorAll('[data-cat]').forEach(btn => {
|
stage.querySelectorAll('[data-cat]').forEach(btn => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
quizStart(btn.dataset.cat);
|
const cat = btn.dataset.cat;
|
||||||
|
if (cat === 'all') {
|
||||||
|
quizBegin(cat, 'all');
|
||||||
|
} else {
|
||||||
|
quizRenderCategoryHub(cat);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function quizStart(category) {
|
// ============= Sub-topic hub for a category =============
|
||||||
quizState.filter = category;
|
function quizRenderCategoryHub(cat) {
|
||||||
quizState.questions = quizShuffle(quizFilterQuestions(category));
|
const stage = document.getElementById('quizStage');
|
||||||
|
const subs = subtopicsForCategory(cat);
|
||||||
|
const total = categoryQuestions(cat).length;
|
||||||
|
const themeClass = `theme-card--${cat}`;
|
||||||
|
const themeColors = SMF.THEME_COLORS[cat];
|
||||||
|
|
||||||
|
stage.innerHTML = `
|
||||||
|
<header class="lesson-header reveal">
|
||||||
|
<div class="lesson-header__num" style="color:${themeColors.color}">Multiple choice</div>
|
||||||
|
<h1 class="lesson-header__title">${quizCategoryLabel(cat)}</h1>
|
||||||
|
<div class="lesson-header__meta">
|
||||||
|
<span>${total} spørsmål totalt</span>
|
||||||
|
<span>${subs.length} deltemaer</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<p style="margin: var(--sp-5) 0 var(--sp-3); font-family: var(--f-mono); font-size: var(--s-0); letter-spacing: 0.15em; text-transform: uppercase; color: var(--muted)">Velg deltema</p>
|
||||||
|
|
||||||
|
<div class="quiz-sub-grid reveal">
|
||||||
|
<button class="quiz-sub-card quiz-sub-card--all" data-sub="all">
|
||||||
|
<div class="quiz-sub-card__count">${total}</div>
|
||||||
|
<div class="quiz-sub-card__label">Alle deltemaer</div>
|
||||||
|
<div class="quiz-sub-card__hint">test deg bredt</div>
|
||||||
|
</button>
|
||||||
|
${subs.map(([name, count]) => `
|
||||||
|
<button class="quiz-sub-card" data-sub="${name}">
|
||||||
|
<div class="quiz-sub-card__count">${count}</div>
|
||||||
|
<div class="quiz-sub-card__label">${name}</div>
|
||||||
|
<div class="quiz-sub-card__hint">spørsmål</div>
|
||||||
|
</button>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="quiz-actions reveal" style="margin-top: var(--sp-7); justify-content: flex-start">
|
||||||
|
<button class="btn btn--ghost" id="quizBack">← Tilbake</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
stage.querySelectorAll('[data-sub]').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
quizBegin(cat, btn.dataset.sub);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
document.getElementById('quizBack').addEventListener('click', quizRenderStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============= Begin a quiz =============
|
||||||
|
function quizBegin(cat, sub) {
|
||||||
|
quizState.category = cat;
|
||||||
|
quizState.subtopic = sub;
|
||||||
|
quizState.questions = quizShuffle(quizFilter(cat, sub));
|
||||||
quizState.current = 0;
|
quizState.current = 0;
|
||||||
quizState.selected = null;
|
quizState.selected = null;
|
||||||
quizState.answered = false;
|
quizState.answered = false;
|
||||||
quizState.correct = 0;
|
quizState.correct = 0;
|
||||||
quizState.wrong = 0;
|
quizState.wrong = 0;
|
||||||
|
quizState.perSubtopic = {};
|
||||||
quizState.finished = false;
|
quizState.finished = false;
|
||||||
quizRenderCurrent();
|
quizRenderCurrent();
|
||||||
}
|
}
|
||||||
@@ -108,14 +197,18 @@ function quizRenderCurrent() {
|
|||||||
const total = quizState.questions.length;
|
const total = quizState.questions.length;
|
||||||
const q = quizState.questions[quizState.current];
|
const q = quizState.questions[quizState.current];
|
||||||
if (!q) return;
|
if (!q) return;
|
||||||
const progress = ((quizState.current) / total) * 100;
|
const progress = (quizState.current / total) * 100;
|
||||||
const letters = ['A', 'B', 'C', 'D', 'E'];
|
const letters = ['A', 'B', 'C', 'D', 'E'];
|
||||||
|
const themeColors = quizState.category === 'all'
|
||||||
|
? SMF.THEME_COLORS[q.category]
|
||||||
|
: SMF.THEME_COLORS[quizState.category];
|
||||||
|
const subLabel = q.subtopic || 'Tverrgående';
|
||||||
|
|
||||||
stage.innerHTML = `
|
stage.innerHTML = `
|
||||||
<div class="quiz-bar"><div class="quiz-bar__fill" style="width:${progress}%"></div></div>
|
<div class="quiz-bar"><div class="quiz-bar__fill" style="width:${progress}%; background:${themeColors.color}"></div></div>
|
||||||
<div class="quiz-meta">
|
<div class="quiz-meta">
|
||||||
<span><strong>${quizState.current + 1}</strong> / ${total}</span>
|
<span><strong>${quizState.current + 1}</strong> / ${total}</span>
|
||||||
<span>${categoryLabelQuiz(q.category)}</span>
|
<span style="color:${themeColors.color}">${quizCategoryLabel(q.category)} · ${subLabel}</span>
|
||||||
<span>Riktig: <strong>${quizState.correct}</strong></span>
|
<span>Riktig: <strong>${quizState.correct}</strong></span>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="quiz-question reveal">${q.question}</h2>
|
<h2 class="quiz-question reveal">${q.question}</h2>
|
||||||
@@ -137,8 +230,7 @@ function quizRenderCurrent() {
|
|||||||
stage.querySelectorAll('.quiz-option').forEach(opt => {
|
stage.querySelectorAll('.quiz-option').forEach(opt => {
|
||||||
opt.addEventListener('click', () => {
|
opt.addEventListener('click', () => {
|
||||||
if (quizState.answered) return;
|
if (quizState.answered) return;
|
||||||
const i = parseInt(opt.dataset.i, 10);
|
quizSelect(parseInt(opt.dataset.i, 10));
|
||||||
quizSelect(i);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -159,8 +251,15 @@ function quizSelect(i) {
|
|||||||
if (idx === i && i !== q.correct) opt.classList.add('quiz-option--wrong');
|
if (idx === i && i !== q.correct) opt.classList.add('quiz-option--wrong');
|
||||||
if (idx === i) opt.classList.add('quiz-option--selected');
|
if (idx === i) opt.classList.add('quiz-option--selected');
|
||||||
});
|
});
|
||||||
if (i === q.correct) quizState.correct++;
|
const sub = q.subtopic || 'Tverrgående';
|
||||||
else quizState.wrong++;
|
if (!quizState.perSubtopic[sub]) quizState.perSubtopic[sub] = { correct: 0, total: 0 };
|
||||||
|
quizState.perSubtopic[sub].total++;
|
||||||
|
if (i === q.correct) {
|
||||||
|
quizState.correct++;
|
||||||
|
quizState.perSubtopic[sub].correct++;
|
||||||
|
} else {
|
||||||
|
quizState.wrong++;
|
||||||
|
}
|
||||||
|
|
||||||
const exp = document.getElementById('quizExplanation');
|
const exp = document.getElementById('quizExplanation');
|
||||||
exp.innerHTML = `
|
exp.innerHTML = `
|
||||||
@@ -193,33 +292,75 @@ function quizRenderResult() {
|
|||||||
else if (pct >= 50) { verdict = 'Greit, men trener videre.'; vColor = 'var(--theme-samfunn)'; }
|
else if (pct >= 50) { verdict = 'Greit, men trener videre.'; vColor = 'var(--theme-samfunn)'; }
|
||||||
else { verdict = 'Repeter mer av dette temaet.'; vColor = 'var(--theme-etikk)'; }
|
else { verdict = 'Repeter mer av dette temaet.'; vColor = 'var(--theme-etikk)'; }
|
||||||
|
|
||||||
|
// Per-subtopic breakdown
|
||||||
|
const subRows = Object.entries(quizState.perSubtopic)
|
||||||
|
.sort((a, b) => b[1].total - a[1].total)
|
||||||
|
.map(([name, { correct, total }]) => {
|
||||||
|
const p = total ? Math.round((correct / total) * 100) : 0;
|
||||||
|
const barColor = p >= 70 ? 'var(--theme-baerekraft)' : p >= 50 ? 'var(--theme-samfunn)' : 'var(--theme-etikk)';
|
||||||
|
return `
|
||||||
|
<div class="subtopic-row">
|
||||||
|
<div class="subtopic-row__name">${name}</div>
|
||||||
|
<div class="subtopic-row__bar"><div class="subtopic-row__fill" style="width:${p}%; background:${barColor}"></div></div>
|
||||||
|
<div class="subtopic-row__score"><strong>${correct}</strong>/${total}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
stage.innerHTML = `
|
stage.innerHTML = `
|
||||||
<div class="quiz-result reveal">
|
<div class="quiz-result reveal">
|
||||||
<div class="quiz-result__score">${quizState.correct}<span style="color:var(--muted)">/${total}</span></div>
|
<div class="quiz-result__score">${quizState.correct}<span style="color:var(--muted)">/${total}</span></div>
|
||||||
<div class="quiz-result__label">${pct} % riktig</div>
|
<div class="quiz-result__label">${pct} % riktig</div>
|
||||||
<div class="quiz-result__verdict" style="color:${vColor}">${verdict}</div>
|
<div class="quiz-result__verdict" style="color:${vColor}">${verdict}</div>
|
||||||
<div class="quiz-actions" style="justify-content:center; margin-top: var(--sp-7)">
|
</div>
|
||||||
<button class="btn" id="quizRestart">Test på nytt</button>
|
|
||||||
|
${subRows ? `
|
||||||
|
<div class="subtopic-breakdown reveal">
|
||||||
|
<div class="subtopic-breakdown__label">Fordelt på deltema</div>
|
||||||
|
${subRows}
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
<div class="quiz-actions reveal" style="justify-content:center; margin-top: var(--sp-7)">
|
||||||
|
<button class="btn" id="quizRestart">Ta på nytt</button>
|
||||||
<button class="btn btn--primary" id="quizMore">Andre kategori</button>
|
<button class="btn btn--primary" id="quizMore">Andre kategori</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
document.getElementById('quizRestart').addEventListener('click', () => quizStart(quizState.filter));
|
document.getElementById('quizRestart').addEventListener('click', () => quizBegin(quizState.category, quizState.subtopic));
|
||||||
document.getElementById('quizMore').addEventListener('click', () => quizRenderStart());
|
document.getElementById('quizMore').addEventListener('click', () => quizRenderStart());
|
||||||
}
|
}
|
||||||
|
|
||||||
function categoryLabelQuiz(cat) {
|
function quizKeyboard(e) {
|
||||||
return {
|
if (location.hash !== '#/quiz') {
|
||||||
etikk: 'Etikk',
|
document.removeEventListener('keydown', quizKeyboard);
|
||||||
baerekraft: 'Bærekraft',
|
return;
|
||||||
samfunn: 'Samfunnsansvar',
|
}
|
||||||
verktoy: 'Verktøy & implementering'
|
const tag = document.activeElement?.tagName;
|
||||||
}[cat] || cat;
|
if (tag === 'INPUT' || tag === 'TEXTAREA') return;
|
||||||
|
|
||||||
|
// 1-4 to select option, Enter to advance, Esc to cancel
|
||||||
|
if (!quizState.finished && quizState.questions.length > 0) {
|
||||||
|
if (e.key === '1' || e.key === '2' || e.key === '3' || e.key === '4') {
|
||||||
|
const idx = parseInt(e.key, 10) - 1;
|
||||||
|
if (!quizState.answered) {
|
||||||
|
const q = quizState.questions[quizState.current];
|
||||||
|
if (q && idx < q.options.length) {
|
||||||
|
e.preventDefault();
|
||||||
|
quizSelect(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ((e.key === 'Enter' || e.key === ' ') && quizState.answered) {
|
||||||
|
e.preventDefault();
|
||||||
|
quizAdvance();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function quizInit() {
|
async function quizInit() {
|
||||||
await quizLoad();
|
await quizLoad();
|
||||||
quizRenderStart();
|
quizRenderStart();
|
||||||
|
document.removeEventListener('keydown', quizKeyboard);
|
||||||
|
document.addEventListener('keydown', quizKeyboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
SMF.quizInit = quizInit;
|
SMF.quizInit = quizInit;
|
||||||
|
|||||||
Reference in New Issue
Block a user