Rust learning: lessons, notes, and exercise crates
This commit is contained in:
+158
@@ -0,0 +1,158 @@
|
||||
// Shared quiz engine for lessons. Two question types, both graded client-side, no server.
|
||||
//
|
||||
// Recall (free-recall self-check — best for storage strength):
|
||||
// <div class="q" data-type="recall" data-topic="Ownership">
|
||||
// <p class="topic">Ownership</p>
|
||||
// <p class="prompt">...question...</p>
|
||||
// <button class="reveal-btn">Show answer</button>
|
||||
// <div class="answer hidden">...answer text...</div>
|
||||
// <div class="grade hidden">
|
||||
// <button data-grade="hit">Got it</button>
|
||||
// <button data-grade="miss">Missed it</button>
|
||||
// </div>
|
||||
// </div>
|
||||
//
|
||||
// Multiple choice (instant feedback):
|
||||
// <div class="q" data-type="mcq" data-topic="Enums">
|
||||
// <p class="topic">Enums</p>
|
||||
// <p class="prompt">...question...</p>
|
||||
// <div class="options">
|
||||
// <button class="opt" data-correct="true">...</button>
|
||||
// <button class="opt" data-correct="false">...</button>
|
||||
// </div>
|
||||
// <div class="explain hidden">...explanation...</div>
|
||||
// </div>
|
||||
//
|
||||
// Bottom of page needs:
|
||||
// <div id="summary"><div id="summary-body"></div><p id="summary-total"></p>
|
||||
// <button id="report-btn">Copy report</button><pre id="report-output" class="hidden"></pre></div>
|
||||
|
||||
(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 () {});
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,224 @@
|
||||
/* Shared stylesheet for all lessons and reference docs. Quiet, humanist sans, generous size.
|
||||
Dark by default; the print block below swaps back to ink-on-paper. */
|
||||
|
||||
:root {
|
||||
--ink: #e4e1d9;
|
||||
--paper: #15171b;
|
||||
--muted: #9b978d;
|
||||
--rule: #2f333a;
|
||||
--panel: #1d2025;
|
||||
--panel-hover: #24282e;
|
||||
--accent: #e3a172;
|
||||
--good: #74c48a;
|
||||
--good-bg: #172a1d;
|
||||
--bad: #e8796e;
|
||||
--bad-bg: #2b1715;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
max-width: 40em;
|
||||
margin: 3rem auto 6rem;
|
||||
padding: 0 1.5rem;
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
font-family: "Inter", "Ubuntu Sans", Ubuntu, system-ui, "Segoe UI", -apple-system,
|
||||
"Noto Sans", "DejaVu Sans", sans-serif;
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.68;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
h1 { font-size: 2.1rem; margin-bottom: 0.2rem; letter-spacing: -0.01em; }
|
||||
h1 + .subtitle { color: var(--muted); font-size: 1.02rem; margin-top: 0; margin-bottom: 2rem; }
|
||||
|
||||
h2 { font-size: 1.45rem; margin-top: 2.5rem; border-top: 1px solid var(--rule); padding-top: 1.5rem; }
|
||||
|
||||
a { color: var(--accent); text-decoration: underline dotted; text-underline-offset: 2px; }
|
||||
a:hover { text-decoration-style: solid; }
|
||||
|
||||
code, pre {
|
||||
font-family: "JetBrains Mono", "Cascadia Mono", "DejaVu Sans Mono",
|
||||
"Ubuntu Sans Mono", Consolas, Menlo, monospace;
|
||||
font-size: 0.87em;
|
||||
font-variant-ligatures: none;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--panel);
|
||||
/* Smaller than inline code, so long rustc output overflows by as little as possible.
|
||||
Wrapping is not an option: the `^^^^` underlines must stay column-aligned or they
|
||||
point at the wrong token. Blocks that still exceed the column scroll sideways. */
|
||||
font-size: 0.73em;
|
||||
line-height: 1.5;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 4px;
|
||||
padding: 0.9em 1em;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
code { background: var(--panel); padding: 0.1em 0.3em; border-radius: 3px; }
|
||||
pre code { background: none; padding: 0; font-size: 1em; } /* no compounding */
|
||||
|
||||
.cite {
|
||||
font-size: 0.85em;
|
||||
color: var(--muted);
|
||||
border-left: 2px solid var(--rule);
|
||||
padding-left: 0.7em;
|
||||
margin: 0.5em 0 0;
|
||||
}
|
||||
|
||||
.callout {
|
||||
border: 1px solid var(--rule);
|
||||
background: var(--panel);
|
||||
border-radius: 6px;
|
||||
padding: 1em 1.2em;
|
||||
margin: 1.5rem 0;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
/* Quoted primary sources — verbatim text from the book, std docs, or guidelines */
|
||||
blockquote {
|
||||
margin: 1.5rem 0 0.4rem;
|
||||
padding: 0.2em 0 0.2em 1.1em;
|
||||
border-left: 3px solid var(--accent);
|
||||
color: var(--ink);
|
||||
font-size: 0.97em;
|
||||
}
|
||||
blockquote p { margin: 0.4em 0; }
|
||||
blockquote + .cite { margin-top: 0; }
|
||||
|
||||
/* Coverage tables: a cell that names a real gap */
|
||||
.gap { color: var(--bad); font-weight: 600; }
|
||||
|
||||
/* Quiz widget */
|
||||
.q {
|
||||
border-top: 1px solid var(--rule);
|
||||
padding: 1.4rem 0;
|
||||
}
|
||||
|
||||
.q .topic {
|
||||
display: inline-block;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
|
||||
.q .prompt { margin: 0 0 0.8em; }
|
||||
|
||||
.q button {
|
||||
font-family: inherit;
|
||||
color: inherit;
|
||||
font-size: 0.92rem;
|
||||
background: transparent;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 4px;
|
||||
padding: 0.35em 0.8em;
|
||||
cursor: pointer;
|
||||
margin: 0.2em 0.4em 0.2em 0;
|
||||
}
|
||||
.q button:hover:not(:disabled) { background: var(--panel-hover); }
|
||||
.q button:disabled { cursor: default; }
|
||||
|
||||
.options { display: flex; flex-direction: column; align-items: flex-start; }
|
||||
.opt { text-align: left; width: 100%; }
|
||||
.opt.correct, .grade button.opt-correct { border-color: var(--good); background: var(--good-bg); color: var(--good); }
|
||||
.opt.incorrect, .grade button.opt-incorrect { border-color: var(--bad); background: var(--bad-bg); color: var(--bad); }
|
||||
.q button:disabled { opacity: 1; }
|
||||
.grade button:disabled:not(.opt-correct):not(.opt-incorrect) { opacity: 0.45; }
|
||||
|
||||
.answer, .explain {
|
||||
background: var(--panel);
|
||||
border-left: 3px solid var(--accent);
|
||||
padding: 0.7em 1em;
|
||||
margin-top: 0.6em;
|
||||
}
|
||||
|
||||
.grade button[data-grade="hit"]:hover { border-color: var(--good); }
|
||||
.grade button[data-grade="miss"]:hover { border-color: var(--bad); }
|
||||
|
||||
.hidden { display: none !important; }
|
||||
|
||||
#summary {
|
||||
margin-top: 2.5rem;
|
||||
border-top: 2px solid var(--ink);
|
||||
padding-top: 1rem;
|
||||
}
|
||||
#summary-body { white-space: pre-line; }
|
||||
#report-output {
|
||||
white-space: pre-line;
|
||||
background: var(--panel);
|
||||
padding: 0.8em 1em;
|
||||
border-radius: 4px;
|
||||
margin-top: 0.8em;
|
||||
}
|
||||
|
||||
/* Collapsible hints */
|
||||
details {
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 6px;
|
||||
padding: 0.6em 1em;
|
||||
margin: 0.7rem 0;
|
||||
background: var(--panel);
|
||||
}
|
||||
details[open] { background: var(--panel-hover); }
|
||||
summary {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
}
|
||||
details > *:last-child { margin-bottom: 0; }
|
||||
|
||||
/* Spec tables */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
th, td {
|
||||
border-bottom: 1px solid var(--rule);
|
||||
padding: 0.4em 0.6em;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
th { border-bottom: 2px solid var(--ink); }
|
||||
|
||||
footer {
|
||||
margin-top: 3rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--rule);
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media print {
|
||||
:root {
|
||||
--ink: #1a1a1a;
|
||||
--paper: #fff;
|
||||
--muted: #555;
|
||||
--rule: #ccc;
|
||||
--panel: #f4f2ec;
|
||||
--panel-hover: #eeece5;
|
||||
--accent: #8a3324;
|
||||
--good: #2a7a3b;
|
||||
--good-bg: #eaf5ec;
|
||||
--bad: #a3312a;
|
||||
--bad-bg: #fbeceb;
|
||||
color-scheme: light;
|
||||
}
|
||||
body { margin: 0.5rem auto; }
|
||||
.hidden { display: block !important; }
|
||||
.q button, #report-btn { display: none !important; }
|
||||
pre { font-size: 0.62em; } /* paper is narrower than a screen, and cannot scroll */
|
||||
pre, blockquote, table, .q { break-inside: avoid; }
|
||||
}
|
||||
Reference in New Issue
Block a user