// Shared quiz engine for lessons. Two question types, both graded client-side, no server. // // Recall (free-recall self-check — best for storage strength): //
//

Ownership

//

...question...

// // // //
// // Multiple choice (instant feedback): //
//

Enums

//

...question...

//
// // //
// //
// // Bottom of page needs: //

//
(function () { function all(sel, ctx) { return Array.from((ctx || document).querySelectorAll(sel)); } function initRecall(q) { var revealBtn = q.querySelector(".reveal-btn"); var answer = q.querySelector(".answer"); var grade = q.querySelector(".grade"); if (!revealBtn || !answer || !grade) return; revealBtn.addEventListener("click", function () { answer.classList.remove("hidden"); grade.classList.remove("hidden"); revealBtn.classList.add("hidden"); }); all("button", grade).forEach(function (btn) { btn.addEventListener("click", function () { q.dataset.result = btn.dataset.grade === "hit" ? "hit" : "miss"; q.classList.add("graded"); all("button", grade).forEach(function (b) { b.disabled = true; }); btn.classList.add(btn.dataset.grade === "hit" ? "opt-correct" : "opt-incorrect"); updateSummary(); }); }); } function initMcq(q) { var opts = all(".opt", q); var explain = q.querySelector(".explain"); opts.forEach(function (opt) { opt.addEventListener("click", function () { if (q.classList.contains("graded")) return; var correct = opt.dataset.correct === "true"; q.classList.add("graded"); q.dataset.result = correct ? "hit" : "miss"; opts.forEach(function (o) { o.disabled = true; if (o.dataset.correct === "true") o.classList.add("correct"); }); if (!correct) opt.classList.add("incorrect"); if (explain) explain.classList.remove("hidden"); updateSummary(); }); }); } function byTopic() { var map = {}; all(".q").forEach(function (q) { var topic = q.dataset.topic || "General"; map[topic] = map[topic] || { hit: 0, graded: 0, total: 0 }; map[topic].total++; if (q.dataset.result) { map[topic].graded++; if (q.dataset.result === "hit") map[topic].hit++; } }); return map; } function updateSummary() { var body = document.getElementById("summary-body"); var total = document.getElementById("summary-total"); var reportBtn = document.getElementById("report-btn"); if (!body) return; var map = byTopic(); var lines = []; var sumHit = 0, sumGraded = 0, sumAll = 0; Object.keys(map).forEach(function (topic) { var t = map[topic]; sumHit += t.hit; sumGraded += t.graded; sumAll += t.total; var pct = t.graded ? Math.round((100 * t.hit) / t.graded) : null; lines.push( topic + ": " + t.hit + "/" + t.graded + (t.graded < t.total ? " (of " + t.total + ")" : "") + (pct === null ? "" : " — " + pct + "%") ); }); body.textContent = lines.join("\n"); if (total) total.textContent = sumGraded + " of " + sumAll + " answered, " + sumHit + " correct."; if (reportBtn) reportBtn.disabled = sumGraded === 0; } function buildReport() { var map = byTopic(); var weak = []; var solid = []; Object.keys(map).forEach(function (topic) { var t = map[topic]; var entry = topic + " (" + t.hit + "/" + t.total + ")"; if (t.hit < t.total) weak.push(entry); else solid.push(entry); }); var lines = ["Rust diagnostic result:"]; lines.push(weak.length ? "Weak: " + weak.join(", ") : "Weak: none"); lines.push(solid.length ? "Solid: " + solid.join(", ") : "Solid: none"); return lines.join("\n"); } document.addEventListener("DOMContentLoaded", function () { all(".q").forEach(function (q) { if (q.dataset.type === "recall") initRecall(q); if (q.dataset.type === "mcq") initMcq(q); }); updateSummary(); var reportBtn = document.getElementById("report-btn"); if (reportBtn) { reportBtn.addEventListener("click", function () { var report = buildReport(); var out = document.getElementById("report-output"); if (out) { out.textContent = report; out.classList.remove("hidden"); } if (navigator.clipboard) navigator.clipboard.writeText(report).catch(function () {}); }); } }); })();