DIY — Guide Step Navigator
Vertical guide step navigator styled like a workshop repair manual: six numbered nodes on a dashed rail walk through a laptop fan replacement, each with a blueprint thumbnail, mono part badges, and a Mark-as-done toggle. Done steps flip to green check nodes and dim, completion feeds a live count-and-percent meter in the blueprint header, and progress persists to localStorage. Prev/Next buttons and arrow keys move an auto-scrolling active highlight, mirrored by a compact horizontal dot variant driven by the same state.
MCP
Code
:root {
--blueprint: #0f2a4a;
--blueprint-d: #0a1e36;
--blueprint-l: #16385f;
--grid: rgba(140, 180, 220, 0.14);
--grid-major: rgba(140, 180, 220, 0.26);
--orange: #ff6b35;
--orange-d: #e0521f;
--orange-soft: #ffe3d6;
--kraft: #d9c7a7;
--kraft-l: #efe6d4;
--ink: #1c2733;
--ink-2: #3d4a57;
--muted: #6b7885;
--bg: #f4f1ea;
--white: #ffffff;
--line: rgba(28, 39, 51, 0.12);
--line-2: rgba(28, 39, 51, 0.2);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--r-sm: 6px;
--r-md: 12px;
--r-lg: 18px;
--shadow-sm: 0 1px 3px rgba(28, 39, 51, 0.08);
--shadow-md: 0 8px 24px rgba(28, 39, 51, 0.1);
--rail-w: 56px;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.mono-inline {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 0.86em;
font-weight: 600;
}
/* ---------- shell ---------- */
.stealth,
.stepper-shell {
max-width: 760px;
margin: 0 auto;
padding: 28px 20px 64px;
}
/* ---------- header (blueprint) ---------- */
.guide-header {
background-color: var(--blueprint);
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px),
linear-gradient(var(--grid-major) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-major) 1px, transparent 1px);
background-size: 8px 8px, 8px 8px, 80px 80px, 80px 80px;
border-radius: var(--r-lg);
padding: 28px 28px 24px;
color: var(--kraft-l);
box-shadow: var(--shadow-md);
}
.guide-id-block {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 14px;
}
.mono-badge {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.08em;
padding: 4px 9px;
border-radius: var(--r-sm);
background: rgba(255, 255, 255, 0.1);
color: var(--kraft-l);
white-space: nowrap;
}
.badge-orange {
background: var(--orange);
color: #fff;
}
.badge-outline {
background: transparent;
border: 1px dashed rgba(239, 230, 212, 0.5);
}
.guide-title {
margin: 0 0 6px;
font-size: clamp(1.5rem, 4vw, 2.1rem);
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.045em;
color: var(--white);
}
.guide-sub {
margin: 0 0 22px;
font-size: 0.92rem;
color: rgba(239, 230, 212, 0.82);
max-width: 56ch;
}
.guide-sub .mono-inline {
color: var(--orange-soft);
}
/* completion meter */
.meter-row {
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
}
.meter-count {
display: flex;
align-items: baseline;
gap: 7px;
}
.meter-count-num {
font-size: 1.15rem;
font-weight: 700;
color: var(--white);
}
.meter-count-label {
font-size: 0.78rem;
color: rgba(239, 230, 212, 0.7);
}
.meter-track {
flex: 1 1 160px;
height: 10px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
overflow: hidden;
min-width: 120px;
}
.meter-fill {
height: 100%;
width: 0%;
border-radius: 999px;
background: linear-gradient(90deg, var(--orange-d), var(--orange));
transition: width 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}
.meter-fill.is-complete {
background: linear-gradient(90deg, #24825b, var(--ok));
}
.meter-pct {
font-size: 0.82rem;
color: var(--orange-soft);
min-width: 3.2ch;
text-align: right;
}
.reset-link {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.05em;
background: none;
border: none;
color: rgba(239, 230, 212, 0.65);
text-decoration: underline;
text-underline-offset: 3px;
cursor: pointer;
padding: 4px 2px;
transition: color 0.15s ease;
}
.reset-link:hover,
.reset-link:focus-visible {
color: var(--white);
}
.reset-link:focus-visible {
outline: 2px solid var(--orange);
outline-offset: 2px;
border-radius: 4px;
}
/* ---------- vertical steps ---------- */
.steps-panel {
margin-top: 28px;
}
.steps-list {
list-style: none;
margin: 0;
padding: 0;
position: relative;
}
/* connecting rail */
.steps-list::before {
content: "";
position: absolute;
left: calc(var(--rail-w) / 2 - 1px);
top: 24px;
bottom: 24px;
width: 2px;
background: repeating-linear-gradient(
to bottom,
var(--line-2) 0 6px,
transparent 6px 12px
);
}
.step {
display: flex;
gap: 4px;
position: relative;
padding-bottom: 18px;
}
.step:last-child {
padding-bottom: 0;
}
.step-rail {
flex: 0 0 var(--rail-w);
display: flex;
justify-content: center;
padding-top: 18px;
}
.step-node {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid var(--line-2);
background: var(--white);
color: var(--ink-2);
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 0.92rem;
font-weight: 700;
display: grid;
place-items: center;
cursor: pointer;
position: relative;
z-index: 1;
transition:
border-color 0.2s ease,
background 0.2s ease,
color 0.2s ease,
transform 0.2s ease,
box-shadow 0.2s ease;
}
.step-node:hover {
transform: scale(1.08);
border-color: var(--orange);
}
.step-node:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 3px;
}
.node-check {
display: none;
width: 18px;
height: 18px;
}
/* active step node */
.step.is-active .step-node {
border-color: var(--orange);
background: var(--orange);
color: #fff;
box-shadow: 0 0 0 5px rgba(255, 107, 53, 0.22);
}
/* done step node */
.step.is-done .step-node {
border-color: var(--ok);
background: var(--ok);
color: #fff;
}
.step.is-done .node-num {
display: none;
}
.step.is-done .node-check {
display: block;
}
.step.is-done.is-active .step-node {
box-shadow: 0 0 0 5px rgba(47, 158, 111, 0.25);
}
/* ---------- step card ---------- */
.step-card {
flex: 1;
display: flex;
gap: 16px;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 16px;
box-shadow: var(--shadow-sm);
transition:
border-color 0.2s ease,
box-shadow 0.2s ease,
opacity 0.25s ease,
transform 0.2s ease;
}
.step-card:hover {
border-color: var(--line-2);
box-shadow: var(--shadow-md);
}
.step.is-active .step-card {
border-color: var(--orange);
box-shadow: 0 0 0 1px var(--orange), var(--shadow-md);
}
.step.is-done .step-card {
opacity: 0.62;
background: var(--kraft-l);
}
.step.is-done .step-title {
text-decoration: line-through;
text-decoration-color: rgba(28, 39, 51, 0.4);
text-decoration-thickness: 2px;
}
/* thumbnail */
.step-thumb {
flex: 0 0 96px;
height: 76px;
border-radius: var(--r-sm);
background-color: var(--blueprint);
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px);
background-size: 8px 8px;
display: grid;
place-items: center;
color: var(--orange-soft);
align-self: flex-start;
}
.step-thumb svg {
width: 56px;
height: 36px;
}
.step-body {
flex: 1;
min-width: 0;
}
.step-meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 7px;
margin-bottom: 6px;
}
.badge-small {
font-size: 0.62rem;
padding: 3px 7px;
background: rgba(15, 42, 74, 0.08);
color: var(--ink-2);
}
.badge-warn {
background: rgba(217, 138, 43, 0.16);
color: #8a5514;
}
.badge-danger {
background: rgba(212, 80, 62, 0.14);
color: #a1352a;
}
.badge-ok {
background: rgba(47, 158, 111, 0.15);
color: #1d6b49;
}
.badge-part {
background: var(--orange-soft);
color: var(--orange-d);
}
.step-time {
margin-left: auto;
font-size: 0.72rem;
color: var(--muted);
}
.step-title {
margin: 0 0 4px;
font-size: 1.02rem;
font-weight: 700;
letter-spacing: 0.01em;
}
.step-text {
margin: 0 0 12px;
font-size: 0.88rem;
color: var(--ink-2);
}
.step-text .mono-inline {
background: rgba(15, 42, 74, 0.07);
padding: 1px 5px;
border-radius: 4px;
color: var(--blueprint);
}
/* ---------- done toggle ---------- */
.done-toggle {
display: inline-flex;
align-items: center;
gap: 9px;
cursor: pointer;
user-select: none;
padding: 6px 12px 6px 8px;
border: 1px dashed var(--line-2);
border-radius: var(--r-sm);
transition: border-color 0.2s ease, background 0.2s ease;
}
.done-toggle:hover {
border-color: var(--ok);
background: rgba(47, 158, 111, 0.06);
}
.done-check {
position: absolute;
opacity: 0;
width: 1px;
height: 1px;
}
.done-box {
width: 20px;
height: 20px;
border-radius: 5px;
border: 2px solid var(--line-2);
background: var(--white);
display: grid;
place-items: center;
color: #fff;
transition: background 0.18s ease, border-color 0.18s ease;
}
.done-box svg {
width: 12px;
height: 12px;
opacity: 0;
transform: scale(0.5);
transition: opacity 0.18s ease, transform 0.18s ease;
}
.done-check:checked + .done-box {
background: var(--ok);
border-color: var(--ok);
}
.done-check:checked + .done-box svg {
opacity: 1;
transform: scale(1);
}
.done-check:focus-visible + .done-box {
outline: 3px solid var(--orange);
outline-offset: 2px;
}
.done-label {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ink-2);
}
.done-check:checked ~ .done-label {
color: var(--ok);
}
/* ---------- prev / next nav ---------- */
.step-nav {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 22px;
padding: 14px 16px;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
box-shadow: var(--shadow-sm);
}
.nav-btn {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: "Inter", system-ui, sans-serif;
font-size: 0.84rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 10px 18px;
border-radius: var(--r-sm);
border: 2px solid var(--line-2);
background: var(--white);
color: var(--ink);
cursor: pointer;
transition:
background 0.15s ease,
border-color 0.15s ease,
transform 0.12s ease,
box-shadow 0.15s ease;
}
.nav-btn svg {
width: 14px;
height: 14px;
}
.nav-btn:hover:not(:disabled) {
border-color: var(--ink);
transform: translateY(-1px);
}
.nav-btn:active:not(:disabled) {
transform: translateY(0);
}
.nav-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.nav-btn:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 2px;
}
.nav-btn-primary {
background: var(--orange);
border-color: var(--orange);
color: #fff;
}
.nav-btn-primary:hover:not(:disabled) {
background: var(--orange-d);
border-color: var(--orange-d);
box-shadow: 0 4px 12px rgba(224, 82, 31, 0.35);
}
.nav-hint {
font-size: 0.7rem;
color: var(--muted);
}
/* ---------- compact horizontal variant ---------- */
.mini-panel {
margin-top: 32px;
background: var(--kraft-l);
background-image:
linear-gradient(rgba(28, 39, 51, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(28, 39, 51, 0.05) 1px, transparent 1px);
background-size: 16px 16px;
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px 20px 22px;
}
.mini-head {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 18px;
}
.mini-title {
margin: 0;
font-size: 0.82rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.09em;
color: var(--ink-2);
}
.mini-head .badge-outline {
border-color: var(--line-2);
color: var(--ink-2);
}
.mini-track {
display: flex;
align-items: flex-start;
justify-content: space-between;
position: relative;
}
.mini-track::before {
content: "";
position: absolute;
top: 13px;
left: 24px;
right: 24px;
height: 2px;
background: repeating-linear-gradient(
to right,
var(--line-2) 0 6px,
transparent 6px 12px
);
}
.mini-step {
display: flex;
flex-direction: column;
align-items: center;
gap: 7px;
background: none;
border: none;
padding: 0;
cursor: pointer;
position: relative;
z-index: 1;
flex: 0 0 auto;
width: 64px;
}
.mini-dot {
width: 26px;
height: 26px;
border-radius: 50%;
border: 2px solid var(--line-2);
background: var(--white);
color: var(--ink-2);
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 0.66rem;
font-weight: 700;
display: grid;
place-items: center;
transition:
background 0.2s ease,
border-color 0.2s ease,
color 0.2s ease,
transform 0.2s ease,
box-shadow 0.2s ease;
}
.mini-step:hover .mini-dot {
transform: scale(1.12);
border-color: var(--orange);
}
.mini-step:focus-visible {
outline: none;
}
.mini-step:focus-visible .mini-dot {
outline: 3px solid var(--orange);
outline-offset: 2px;
}
.mini-step.is-active .mini-dot {
background: var(--orange);
border-color: var(--orange);
color: #fff;
box-shadow: 0 0 0 4px rgba(255, 107, 53, 0.22);
}
.mini-step.is-done .mini-dot {
background: var(--ok);
border-color: var(--ok);
color: #fff;
}
.mini-label {
font-size: 0.62rem;
font-weight: 600;
color: var(--muted);
text-align: center;
line-height: 1.25;
}
.mini-step.is-active .mini-label {
color: var(--orange-d);
font-weight: 700;
}
.mini-step.is-done .mini-label {
color: var(--ok);
}
/* ---------- toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 24px;
transform: translate(-50%, 16px);
background: var(--blueprint-d);
color: var(--kraft-l);
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 0.78rem;
font-weight: 600;
letter-spacing: 0.03em;
padding: 10px 18px;
border-radius: var(--r-sm);
border-left: 4px solid var(--orange);
box-shadow: var(--shadow-md);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 50;
max-width: min(90vw, 420px);
text-align: center;
}
.toast.is-visible {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- responsive ---------- */
@media (max-width: 640px) {
.step-card {
flex-direction: column;
gap: 12px;
}
.step-thumb {
flex-basis: auto;
width: 100%;
height: 64px;
}
}
@media (max-width: 520px) {
:root {
--rail-w: 44px;
}
.stepper-shell {
padding: 16px 12px 48px;
}
.guide-header {
padding: 20px 18px;
border-radius: var(--r-md);
}
.step-node {
width: 34px;
height: 34px;
font-size: 0.8rem;
}
.step-card {
padding: 13px;
}
.step-time {
margin-left: 0;
width: 100%;
}
.nav-hint {
display: none;
}
.nav-btn {
flex: 1;
justify-content: center;
padding: 10px 12px;
font-size: 0.76rem;
}
.mini-label {
display: none;
}
.mini-step {
width: auto;
}
.mini-track::before {
left: 13px;
right: 13px;
}
}/* DIY — Guide Step Navigator
Vertical stepper + compact horizontal variant driven by one shared state.
Done-state persists to localStorage; arrows / buttons move the active step. */
(function () {
"use strict";
var STORAGE_KEY = "diy-step-stepper:rt-0042";
var stepsList = document.getElementById("stepsList");
var steps = Array.prototype.slice.call(stepsList.querySelectorAll(".step"));
var total = steps.length;
var meterCount = document.getElementById("meterCount");
var meterPct = document.getElementById("meterPct");
var meterFill = document.getElementById("meterFill");
var meterBar = document.getElementById("meterBar");
var resetBtn = document.getElementById("resetBtn");
var prevBtn = document.getElementById("prevBtn");
var nextBtn = document.getElementById("nextBtn");
var miniTrack = document.getElementById("miniTrack");
var toastEl = document.getElementById("toast");
var MINI_LABELS = ["Power off", "Open up", "Unplug", "Old fan out", "New fan in", "Test"];
/* ---------- state ---------- */
var state = load();
function load() {
try {
var raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
var parsed = JSON.parse(raw);
if (
parsed &&
Array.isArray(parsed.done) &&
parsed.done.length === total &&
typeof parsed.active === "number"
) {
parsed.active = clamp(parsed.active, 0, total - 1);
return parsed;
}
}
} catch (e) {
/* corrupted storage — fall through to defaults */
}
return { done: new Array(total).fill(false), active: 0 };
}
function save() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
} catch (e) {
/* storage unavailable (private mode) — session-only state */
}
}
function clamp(n, min, max) {
return Math.max(min, Math.min(max, n));
}
/* ---------- toast ---------- */
var toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-visible");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-visible");
}, 2200);
}
/* ---------- compact variant (built once) ---------- */
var miniSteps = steps.map(function (_, i) {
var btn = document.createElement("button");
btn.type = "button";
btn.className = "mini-step";
btn.setAttribute("role", "listitem");
btn.setAttribute("aria-label", "Go to step " + (i + 1) + ": " + MINI_LABELS[i]);
var dot = document.createElement("span");
dot.className = "mini-dot";
dot.textContent = String(i + 1);
var label = document.createElement("span");
label.className = "mini-label";
label.textContent = MINI_LABELS[i];
btn.appendChild(dot);
btn.appendChild(label);
btn.addEventListener("click", function () {
setActive(i, true);
});
miniTrack.appendChild(btn);
return { btn: btn, dot: dot };
});
/* ---------- render ---------- */
function render() {
var doneCount = state.done.filter(Boolean).length;
var pct = Math.round((doneCount / total) * 100);
meterCount.textContent = doneCount + "/" + total;
meterPct.textContent = pct + "%";
meterFill.style.width = pct + "%";
meterFill.classList.toggle("is-complete", doneCount === total);
meterBar.setAttribute("aria-valuenow", String(pct));
steps.forEach(function (step, i) {
var isDone = state.done[i];
var isActive = i === state.active;
step.classList.toggle("is-done", isDone);
step.classList.toggle("is-active", isActive);
step.setAttribute("aria-current", isActive ? "step" : "false");
var check = step.querySelector(".done-check");
if (check.checked !== isDone) check.checked = isDone;
var label = step.querySelector(".done-label");
label.textContent = isDone ? "Done" : "Mark as done";
var mini = miniSteps[i];
mini.btn.classList.toggle("is-done", isDone);
mini.btn.classList.toggle("is-active", isActive);
mini.dot.textContent = isDone ? "✓" : String(i + 1);
});
prevBtn.disabled = state.active === 0;
nextBtn.disabled = state.active === total - 1;
}
function setActive(index, scroll) {
state.active = clamp(index, 0, total - 1);
save();
render();
if (scroll) {
steps[state.active].scrollIntoView({ behavior: "smooth", block: "nearest" });
}
}
function toggleDone(index) {
state.done[index] = !state.done[index];
save();
render();
var doneCount = state.done.filter(Boolean).length;
if (doneCount === total) {
toast("All 6 steps complete — enjoy the quiet fan!");
} else if (state.done[index]) {
toast("Step " + (index + 1) + " done · " + doneCount + "/" + total + " complete");
} else {
toast("Step " + (index + 1) + " reopened");
}
}
/* ---------- wiring ---------- */
steps.forEach(function (step, i) {
step.querySelector(".step-node").addEventListener("click", function () {
setActive(i, true);
});
step.querySelector(".done-check").addEventListener("change", function () {
toggleDone(i);
});
// Clicking anywhere on the card focuses that step (but not the toggle itself)
step.querySelector(".step-card").addEventListener("click", function (e) {
if (!e.target.closest(".done-toggle")) setActive(i, false);
});
});
prevBtn.addEventListener("click", function () {
setActive(state.active - 1, true);
});
nextBtn.addEventListener("click", function () {
setActive(state.active + 1, true);
});
resetBtn.addEventListener("click", function () {
state = { done: new Array(total).fill(false), active: 0 };
save();
render();
steps[0].scrollIntoView({ behavior: "smooth", block: "nearest" });
toast("Progress reset · back to step 1");
});
document.addEventListener("keydown", function (e) {
// Don't hijack arrows while typing in a form control
var tag = document.activeElement && document.activeElement.tagName;
if (tag === "INPUT" && document.activeElement.type === "text") return;
if (tag === "TEXTAREA" || tag === "SELECT") return;
if (e.key === "ArrowDown" || e.key === "ArrowRight") {
e.preventDefault();
setActive(state.active + 1, true);
} else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
e.preventDefault();
setActive(state.active - 1, true);
} else if (e.key === "Enter" && document.activeElement === document.body) {
toggleDone(state.active);
}
});
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DIY — Guide Step Navigator</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="stepper-shell">
<!-- ============ HEADER / COMPLETION METER ============ -->
<header class="guide-header">
<div class="guide-header-top">
<div class="guide-id-block">
<span class="mono-badge">GUIDE NO. RT-0042</span>
<span class="mono-badge badge-orange">45 MIN</span>
<span class="mono-badge badge-outline">DIFFICULTY ★★☆</span>
</div>
<h1 class="guide-title">Replace the Cooling Fan</h1>
<p class="guide-sub">Voltcraft Nomad 14 laptop · Fan module <span class="mono-inline">FAN-VN14-B</span> · Torque all screws to snug, not stripped.</p>
</div>
<div class="meter-row" aria-live="polite">
<div class="meter-count">
<span class="mono-inline meter-count-num" id="meterCount">0/6</span>
<span class="meter-count-label">steps done</span>
</div>
<div class="meter-track" role="progressbar" id="meterBar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" aria-label="Guide completion">
<div class="meter-fill" id="meterFill"></div>
</div>
<span class="mono-inline meter-pct" id="meterPct">0%</span>
<button type="button" class="reset-link" id="resetBtn">Reset progress</button>
</div>
</header>
<!-- ============ VERTICAL STEP NAVIGATOR ============ -->
<section class="steps-panel" aria-label="Repair steps">
<ol class="steps-list" id="stepsList">
<li class="step" data-step="0">
<div class="step-rail">
<button type="button" class="step-node" aria-label="Go to step 1">
<span class="node-num">1</span>
<svg class="node-check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8.5 6.4 12 13 4.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</div>
<article class="step-card">
<div class="step-thumb thumb-battery" aria-hidden="true">
<svg viewBox="0 0 64 40"><rect x="6" y="10" width="44" height="20" rx="4" fill="none" stroke="currentColor" stroke-width="2.5"/><rect x="52" y="16" width="6" height="8" rx="2" fill="currentColor"/><path d="M30 12 24 21h6l-4 7 10-11h-6l4-5z" fill="currentColor"/></svg>
</div>
<div class="step-body">
<div class="step-meta">
<span class="mono-badge badge-small">STEP 01</span>
<span class="mono-badge badge-small badge-warn">POWER OFF</span>
<span class="step-time mono-inline">~2 min</span>
</div>
<h2 class="step-title">Power down & disconnect</h2>
<p class="step-text">Shut the laptop down fully (no sleep mode), unplug the charger, and hold the power button for 10 seconds to drain residual charge.</p>
<label class="done-toggle">
<input type="checkbox" class="done-check" />
<span class="done-box" aria-hidden="true">
<svg viewBox="0 0 16 16"><path d="M3 8.5 6.4 12 13 4.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="done-label">Mark as done</span>
</label>
</div>
</article>
</li>
<li class="step" data-step="1">
<div class="step-rail">
<button type="button" class="step-node" aria-label="Go to step 2">
<span class="node-num">2</span>
<svg class="node-check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8.5 6.4 12 13 4.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</div>
<article class="step-card">
<div class="step-thumb thumb-screws" aria-hidden="true">
<svg viewBox="0 0 64 40"><circle cx="18" cy="20" r="9" fill="none" stroke="currentColor" stroke-width="2.5"/><path d="M12 14l12 12M24 14 12 26" stroke="currentColor" stroke-width="2.2"/><circle cx="46" cy="20" r="9" fill="none" stroke="currentColor" stroke-width="2.5"/><path d="M46 11v18M37 20h18" stroke="currentColor" stroke-width="2.2"/></svg>
</div>
<div class="step-body">
<div class="step-meta">
<span class="mono-badge badge-small">STEP 02</span>
<span class="step-time mono-inline">~5 min</span>
</div>
<h2 class="step-title">Remove the bottom cover</h2>
<p class="step-text">Undo the 9 × <span class="mono-inline">T5 Torx</span> screws around the base (the two under the rear feet are longer — keep them separate), then pry from the hinge side with a plastic spudger.</p>
<label class="done-toggle">
<input type="checkbox" class="done-check" />
<span class="done-box" aria-hidden="true">
<svg viewBox="0 0 16 16"><path d="M3 8.5 6.4 12 13 4.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="done-label">Mark as done</span>
</label>
</div>
</article>
</li>
<li class="step" data-step="2">
<div class="step-rail">
<button type="button" class="step-node" aria-label="Go to step 3">
<span class="node-num">3</span>
<svg class="node-check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8.5 6.4 12 13 4.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</div>
<article class="step-card">
<div class="step-thumb thumb-cable" aria-hidden="true">
<svg viewBox="0 0 64 40"><rect x="8" y="8" width="20" height="24" rx="3" fill="none" stroke="currentColor" stroke-width="2.5"/><path d="M28 20h10c6 0 6 8 12 8h6" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/><path d="M14 14h8M14 20h8M14 26h8" stroke="currentColor" stroke-width="2"/></svg>
</div>
<div class="step-body">
<div class="step-meta">
<span class="mono-badge badge-small">STEP 03</span>
<span class="mono-badge badge-small badge-danger">FRAGILE</span>
<span class="step-time mono-inline">~3 min</span>
</div>
<h2 class="step-title">Disconnect battery & fan cable</h2>
<p class="step-text">Lift the battery connector straight up with a fingernail, then flip the tiny latch on the fan's <span class="mono-inline">4-pin JST</span> connector and slide the ribbon free. Never pull by the wires.</p>
<label class="done-toggle">
<input type="checkbox" class="done-check" />
<span class="done-box" aria-hidden="true">
<svg viewBox="0 0 16 16"><path d="M3 8.5 6.4 12 13 4.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="done-label">Mark as done</span>
</label>
</div>
</article>
</li>
<li class="step" data-step="3">
<div class="step-rail">
<button type="button" class="step-node" aria-label="Go to step 4">
<span class="node-num">4</span>
<svg class="node-check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8.5 6.4 12 13 4.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</div>
<article class="step-card">
<div class="step-thumb thumb-fan" aria-hidden="true">
<svg viewBox="0 0 64 40"><circle cx="32" cy="20" r="14" fill="none" stroke="currentColor" stroke-width="2.5"/><circle cx="32" cy="20" r="3.5" fill="currentColor"/><path d="M32 20c-2-7 2-11 7-12M32 20c7-2 11 2 12 7M32 20c2 7-2 11-7 12M32 20c-7 2-11-2-12-7" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>
</div>
<div class="step-body">
<div class="step-meta">
<span class="mono-badge badge-small">STEP 04</span>
<span class="step-time mono-inline">~8 min</span>
</div>
<h2 class="step-title">Lift out the old fan</h2>
<p class="step-text">Remove the 3 × <span class="mono-inline">PH00</span> screws holding the fan shroud, peel back the strip of foil tape over the heat-pipe joint, and lift the fan out at an angle. Note the airflow arrow on the housing.</p>
<label class="done-toggle">
<input type="checkbox" class="done-check" />
<span class="done-box" aria-hidden="true">
<svg viewBox="0 0 16 16"><path d="M3 8.5 6.4 12 13 4.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="done-label">Mark as done</span>
</label>
</div>
</article>
</li>
<li class="step" data-step="4">
<div class="step-rail">
<button type="button" class="step-node" aria-label="Go to step 5">
<span class="node-num">5</span>
<svg class="node-check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8.5 6.4 12 13 4.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</div>
<article class="step-card">
<div class="step-thumb thumb-install" aria-hidden="true">
<svg viewBox="0 0 64 40"><circle cx="26" cy="20" r="11" fill="none" stroke="currentColor" stroke-width="2.5"/><circle cx="26" cy="20" r="3" fill="currentColor"/><path d="M44 20h12M52 14l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="step-body">
<div class="step-meta">
<span class="mono-badge badge-small">STEP 05</span>
<span class="mono-badge badge-small badge-part">FAN-VN14-B</span>
<span class="step-time mono-inline">~10 min</span>
</div>
<h2 class="step-title">Seat the new fan</h2>
<p class="step-text">Drop the replacement fan into the bay, airflow arrow pointing at the rear vent. Re-fit the 3 shroud screws in a triangle pattern, re-lay the foil tape, and click the fan cable back in until the latch snaps.</p>
<label class="done-toggle">
<input type="checkbox" class="done-check" />
<span class="done-box" aria-hidden="true">
<svg viewBox="0 0 16 16"><path d="M3 8.5 6.4 12 13 4.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="done-label">Mark as done</span>
</label>
</div>
</article>
</li>
<li class="step" data-step="5">
<div class="step-rail">
<button type="button" class="step-node" aria-label="Go to step 6">
<span class="node-num">6</span>
<svg class="node-check" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8.5 6.4 12 13 4.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</div>
<article class="step-card">
<div class="step-thumb thumb-test" aria-hidden="true">
<svg viewBox="0 0 64 40"><rect x="10" y="6" width="44" height="24" rx="3" fill="none" stroke="currentColor" stroke-width="2.5"/><path d="M18 34h28" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/><path d="M18 20l6-6 5 9 5-12 5 9h7" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="step-body">
<div class="step-meta">
<span class="mono-badge badge-small">STEP 06</span>
<span class="mono-badge badge-small badge-ok">FINAL CHECK</span>
<span class="step-time mono-inline">~5 min</span>
</div>
<h2 class="step-title">Close up & spin test</h2>
<p class="step-text">Reconnect the battery, snap the cover on, and refit all 9 screws (long ones at the rear). Boot up and run a fan test — a healthy idle spin is <span class="mono-inline">≈ 2,400 RPM</span> with no ticking.</p>
<label class="done-toggle">
<input type="checkbox" class="done-check" />
<span class="done-box" aria-hidden="true">
<svg viewBox="0 0 16 16"><path d="M3 8.5 6.4 12 13 4.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="done-label">Mark as done</span>
</label>
</div>
</article>
</li>
</ol>
<!-- Prev / Next controls -->
<nav class="step-nav" aria-label="Step navigation">
<button type="button" class="nav-btn" id="prevBtn">
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M10 3 5 8l5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
Prev step
</button>
<span class="nav-hint mono-inline" aria-hidden="true">↑ / ↓ arrow keys work too</span>
<button type="button" class="nav-btn nav-btn-primary" id="nextBtn">
Next step
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6 3l5 5-5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</nav>
</section>
<!-- ============ COMPACT HORIZONTAL VARIANT ============ -->
<section class="mini-panel" aria-label="Compact progress variant">
<div class="mini-head">
<h3 class="mini-title">Compact variant</h3>
<span class="mono-badge badge-small badge-outline">SAME STATE</span>
</div>
<div class="mini-track" id="miniTrack" role="list">
<!-- dots injected by script.js -->
</div>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Guide Step Navigator
A vertical step navigator dressed as a maker-workshop repair guide. Six steps of a fictional “replace the cooling fan” job for the Voltcraft Nomad 14 hang off a dashed connecting rail: each card carries a blueprint-grid thumbnail icon, a mono step badge (plus contextual FRAGILE / POWER OFF / FINAL CHECK tags), a time estimate, a short instruction, and a dashed “Mark as done” toggle.
Checking a step off turns its rail node into a green check, dims and strikes through the card, and animates the completion meter in the navy blueprint header (count, percentage, and bar — turning green at 6/6). All state persists to localStorage, and a “Reset progress” link clears it with a confirming toast.
Prev/Next buttons and the arrow keys move a safety-orange active highlight that auto-scrolls into view; clicking any numbered node or card jumps straight to that step. Below the list, a compact horizontal variant — dots plus short labels on a kraft graph-paper panel — is rendered from the exact same state object, showing how one source of truth can drive two stepper presentations.
Illustrative UI only — always follow real safety guidance when working with tools, electronics, or repairs.