Refactor MQTT course and bound backend message processing
Move lesson source into the teaching structure, align the course path with the learner's Go and DevOps experience, and add the interactive lesson guides. Add advanced DevOps lifecycle study as a separate section. Bound MQTT message work and drain accepted jobs during shutdown; align the backend implementation with the revised course through Section 6.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--ink: #202a35;
|
||||
--muted: #5a6874;
|
||||
--paper: #fbfaf6;
|
||||
--panel: #fff;
|
||||
--line: #d8dedc;
|
||||
--accent: #146b65;
|
||||
--accent-soft: #e4f2ef;
|
||||
--warm: #b65a32;
|
||||
--good: #17643d;
|
||||
--bad: #9b342d;
|
||||
--shadow: 0 18px 55px rgb(28 52 50 / 8%);
|
||||
font: 16px/1.65 Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; color: var(--ink); background: var(--paper); }
|
||||
a { color: var(--accent); text-underline-offset: .18em; }
|
||||
a:hover { color: var(--warm); }
|
||||
.page { width: min(920px, calc(100% - 2rem)); margin: 0 auto; padding: 3rem 0 5rem; }
|
||||
.eyebrow { color: var(--accent); font-size: .78rem; font-weight: 800; letter-spacing: .13em; text-transform: uppercase; }
|
||||
h1, h2, h3 { font-family: Georgia, "Times New Roman", serif; line-height: 1.18; }
|
||||
h1 { max-width: 16ch; margin: .45rem 0 1rem; font-size: clamp(2.45rem, 7vw, 4.6rem); letter-spacing: -.045em; }
|
||||
h2 { margin-top: 3rem; font-size: clamp(1.65rem, 4vw, 2.2rem); }
|
||||
h3 { margin-bottom: .35rem; font-size: 1.25rem; }
|
||||
.dek { max-width: 66ch; color: var(--muted); font-size: 1.18rem; }
|
||||
.meta { display: flex; flex-wrap: wrap; gap: .6rem; margin: 1.4rem 0 2rem; }
|
||||
.pill { padding: .28rem .68rem; border: 1px solid var(--line); border-radius: 999px; color: var(--muted); font-size: .82rem; }
|
||||
.card { margin: 1.2rem 0; padding: 1.35rem 1.5rem; border: 1px solid var(--line); border-radius: 1rem; background: var(--panel); box-shadow: var(--shadow); }
|
||||
.callout { border-left: 4px solid var(--accent); background: var(--accent-soft); }
|
||||
.question { color: var(--accent); font-weight: 800; }
|
||||
.flow { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: .55rem; margin: 1.6rem 0; }
|
||||
.step { min-width: 145px; padding: .85rem; border: 1px solid var(--line); border-radius: .75rem; background: var(--panel); text-align: center; }
|
||||
.arrow { color: var(--warm); font-size: 1.3rem; font-weight: 800; }
|
||||
code, pre { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
||||
code { padding: .08rem .28rem; border-radius: .25rem; background: #edf0ee; font-size: .92em; }
|
||||
pre { overflow-x: auto; padding: 1rem 1.15rem; border-radius: .75rem; background: #18252d; color: #f2f5f3; line-height: 1.55; }
|
||||
pre code { padding: 0; background: transparent; color: inherit; }
|
||||
.quiz { margin-top: 1rem; }
|
||||
.question-block { padding: 1rem 0; border-top: 1px solid var(--line); }
|
||||
.option { display: block; margin: .55rem 0; padding: .7rem .85rem; border: 1px solid var(--line); border-radius: .6rem; cursor: pointer; }
|
||||
.option:hover { border-color: var(--accent); background: var(--accent-soft); }
|
||||
.option input { margin-right: .55rem; accent-color: var(--accent); }
|
||||
button { padding: .7rem 1rem; border: 0; border-radius: .6rem; color: #fff; background: var(--accent); font: inherit; font-weight: 750; cursor: pointer; }
|
||||
button:hover { background: #0d514d; }
|
||||
.feedback { min-height: 1.8em; margin: .75rem 0 0; font-weight: 700; }
|
||||
.feedback[data-state="correct"] { color: var(--good); }
|
||||
.feedback[data-state="incorrect"] { color: var(--bad); }
|
||||
.small { color: var(--muted); font-size: .92rem; }
|
||||
.two-col { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: .72rem; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
|
||||
th { color: var(--accent); }
|
||||
fieldset { min-width: 0; margin: 0; padding: 0; border: 0; }
|
||||
legend { margin-bottom: .65rem; }
|
||||
footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--line); color: var(--muted); font-size: .9rem; }
|
||||
@media (max-width: 650px) { .two-col { grid-template-columns: 1fr; } .page { padding-top: 2rem; } }
|
||||
@media print {
|
||||
:root { --paper: #fff; --panel: #fff; --shadow: none; }
|
||||
.page { width: 100%; padding: 0; }
|
||||
.card { break-inside: avoid; box-shadow: none; }
|
||||
button, .quiz { display: none; }
|
||||
a { color: inherit; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
document.querySelectorAll("form[data-quiz]").forEach((form) => {
|
||||
form.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
const selected = form.querySelector('input[type="radio"]:checked');
|
||||
const feedback = form.querySelector("[data-feedback]");
|
||||
if (!selected) {
|
||||
feedback.textContent = "Choose an answer first, then check your reasoning.";
|
||||
feedback.dataset.state = "incorrect";
|
||||
return;
|
||||
}
|
||||
const correct = selected.value === form.dataset.correct;
|
||||
feedback.textContent = correct
|
||||
? form.dataset.explanation
|
||||
: form.dataset.retry || "Not quite. Trace which component produced this evidence, then try again.";
|
||||
feedback.dataset.state = correct ? "correct" : "incorrect";
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user