DIY — Interactive Wiring Diagram
Interactive breadboard wiring diagram built as a hand-drawn SVG: a fictional NanoForge MK-01 microcontroller connects to a half breadboard holding an LED, a 220 ohm resistor, and a tactile button through six colored jumper wires. Hovering or focusing a row in the mono connections table highlights its wire with a glow while dimming the rest, a play button traces an animated signal pulse through the circuit in order, and a toggle shows or hides wire labels — all keyboard accessible.
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: 0 6px 24px rgba(28, 39, 51, 0.1);
--mono: "JetBrains Mono", ui-monospace, monospace;
}
*,
*::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;
}
.mono {
font-family: var(--mono);
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
kbd {
font-family: var(--mono);
font-size: 0.75em;
background: var(--kraft-l);
border: 1px solid var(--line-2);
border-bottom-width: 2px;
border-radius: 4px;
padding: 1px 5px;
}
/* ===== Hero (blueprint grid) ===== */
.hero {
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-bottom: 4px solid var(--orange);
padding: 44px 24px 40px;
}
.hero-inner {
max-width: 1100px;
margin: 0 auto;
}
.hero-meta {
display: flex;
gap: 10px;
margin-bottom: 14px;
}
.badge {
font-family: var(--mono);
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.12em;
padding: 4px 10px;
border-radius: var(--r-sm);
}
.badge-orange {
background: var(--orange);
color: #fff;
}
.badge-ghost {
color: var(--kraft);
border: 1px dashed rgba(217, 199, 167, 0.55);
}
.hero-title {
margin: 0 0 10px;
color: var(--white);
font-size: clamp(1.5rem, 4vw, 2.4rem);
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.hero-sub {
margin: 0;
max-width: 62ch;
color: var(--kraft);
font-size: 0.98rem;
}
/* ===== Layout ===== */
.wrap {
max-width: 1100px;
margin: 0 auto;
padding: 28px 24px 56px;
display: grid;
gap: 26px;
}
.panel {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow);
padding: 22px;
}
.panel-head {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 14px;
margin-bottom: 18px;
}
.panel-title {
margin: 0;
font-size: 0.95rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.14em;
color: var(--ink);
}
.panel-title::before {
content: "";
display: inline-block;
width: 10px;
height: 10px;
background: var(--orange);
margin-right: 10px;
transform: rotate(45deg);
}
/* ===== Toolbar ===== */
.toolbar {
display: flex;
align-items: center;
gap: 18px;
flex-wrap: wrap;
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
font: inherit;
font-weight: 700;
font-size: 0.85rem;
letter-spacing: 0.04em;
text-transform: uppercase;
border: none;
border-radius: var(--r-sm);
padding: 10px 16px;
cursor: pointer;
transition: transform 0.12s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.btn-ico {
width: 13px;
height: 13px;
}
.btn-primary {
background: var(--orange);
color: #fff;
box-shadow: 0 3px 0 var(--orange-d);
}
.btn-primary:hover {
background: var(--orange-d);
}
.btn-primary:active {
transform: translateY(2px);
box-shadow: 0 1px 0 var(--orange-d);
}
.btn-primary:focus-visible,
.con-row:focus-visible,
.switch input:focus-visible ~ .switch-track {
outline: 3px solid var(--orange);
outline-offset: 2px;
}
.btn-primary[aria-pressed="true"] {
background: var(--ink);
box-shadow: 0 3px 0 #000;
}
/* switch */
.switch {
display: inline-flex;
align-items: center;
gap: 9px;
cursor: pointer;
user-select: none;
}
.switch input {
position: absolute;
opacity: 0;
width: 1px;
height: 1px;
}
.switch-track {
width: 40px;
height: 22px;
border-radius: 999px;
background: var(--line-2);
position: relative;
transition: background 0.18s ease;
flex: none;
}
.switch-thumb {
position: absolute;
top: 3px;
left: 3px;
width: 16px;
height: 16px;
border-radius: 50%;
background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
transition: transform 0.18s ease;
}
.switch input:checked ~ .switch-track {
background: var(--ok);
}
.switch input:checked ~ .switch-track .switch-thumb {
transform: translateX(18px);
}
.switch-text {
font-size: 0.82rem;
font-weight: 600;
color: var(--ink-2);
}
/* ===== Diagram grid ===== */
.diagram-grid {
display: grid;
grid-template-columns: minmax(0, 1.5fr) minmax(300px, 1fr);
gap: 20px;
align-items: start;
}
.board-stage {
margin: 0;
border-radius: var(--r-md);
overflow: hidden;
border: 1px solid var(--blueprint-d);
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;
}
#diagram {
display: block;
width: 100%;
height: auto;
}
/* SVG text */
.silk {
font-family: var(--mono);
fill: var(--kraft-l);
}
.silk-b {
font-size: 15px;
font-weight: 700;
letter-spacing: 2px;
}
.silk-mono {
font-size: 11px;
letter-spacing: 1px;
fill: #9fb3c2;
}
.pin-name {
font-family: var(--mono);
font-size: 12px;
font-weight: 700;
fill: var(--kraft-l);
}
.silk-board {
font-family: var(--mono);
font-size: 11px;
letter-spacing: 2px;
fill: #a8977a;
}
.rail-sign {
font-family: var(--mono);
font-size: 15px;
font-weight: 700;
}
.rail-plus {
fill: #d4503e;
}
.rail-minus {
fill: #3b6ea5;
}
.row-num {
font-family: var(--mono);
font-size: 10px;
fill: #a8977a;
}
.hole {
fill: #c4b291;
stroke: #a8977a;
stroke-width: 1;
}
.hole.hot {
fill: var(--orange);
stroke: var(--orange-d);
}
.cmp-tag {
font-family: var(--mono);
font-size: 10px;
font-weight: 600;
fill: #7a6a4c;
}
/* wires */
.wire {
cursor: pointer;
outline: none;
}
.wire-hit {
fill: none;
stroke: transparent;
stroke-width: 18;
}
.wire-path {
fill: none;
stroke-width: 4;
stroke-linecap: round;
transition: stroke-width 0.15s ease, opacity 0.2s ease;
}
.ep {
fill: #0f1c2b;
stroke: #ffffff;
stroke-width: 1.5;
transition: opacity 0.2s ease;
}
.wlabel {
font-family: var(--mono);
font-size: 11px;
font-weight: 600;
fill: #cfe0ef;
paint-order: stroke;
stroke: var(--blueprint-d);
stroke-width: 4;
stroke-linejoin: round;
transition: opacity 0.2s ease;
pointer-events: none;
}
#stage.labels-off .wlabel {
opacity: 0;
}
/* dim / highlight states */
#diagram.dimmed .wire:not(.active) .wire-path,
#diagram.dimmed .wire:not(.active) .ep,
#diagram.dimmed .wlabel:not(.active) {
opacity: 0.16;
}
.wire.active .wire-path {
stroke-width: 6.5;
filter: url(#glow);
}
.wire.active .ep {
fill: var(--orange);
stroke-width: 2.5;
}
.wlabel.active {
fill: #ffffff;
}
/* signal pulse */
.pulse-path {
stroke: #ffffff;
stroke-width: 3;
stroke-linecap: round;
filter: url(#glow);
pointer-events: none;
}
/* ===== Connections table ===== */
.table-side {
min-width: 0;
}
.side-title {
margin: 2px 0 12px;
font-size: 0.85rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.12em;
display: flex;
align-items: center;
gap: 10px;
}
.count-badge {
font-size: 0.68rem;
font-weight: 700;
color: var(--orange-d);
background: var(--orange-soft);
border-radius: var(--r-sm);
padding: 3px 8px;
letter-spacing: 0.1em;
}
.table-scroll {
overflow-x: auto;
border: 1px solid var(--line);
border-radius: var(--r-md);
}
.con-table {
width: 100%;
border-collapse: collapse;
font-family: var(--mono);
font-size: 0.78rem;
}
.con-table th {
text-align: left;
font-size: 0.66rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--muted);
background: var(--kraft-l);
padding: 9px 12px;
border-bottom: 1px solid var(--line-2);
white-space: nowrap;
}
.con-table td {
padding: 10px 12px;
border-bottom: 1px solid var(--line);
white-space: nowrap;
vertical-align: middle;
}
.con-row {
cursor: pointer;
transition: background 0.12s ease;
}
.con-row:last-child td {
border-bottom: none;
}
.con-row:hover,
.con-row:focus-visible,
.con-row.active {
background: var(--orange-soft);
}
.con-row.active td:first-child {
box-shadow: inset 4px 0 0 var(--orange);
}
.wire-chip {
display: inline-flex;
align-items: center;
gap: 7px;
font-weight: 700;
}
.swatch {
width: 14px;
height: 14px;
border-radius: 4px;
border: 1.5px solid rgba(0, 0, 0, 0.25);
flex: none;
}
.cell-pin {
font-weight: 600;
}
.cell-purpose {
font-family: "Inter", system-ui, sans-serif;
color: var(--ink-2);
white-space: normal;
min-width: 130px;
}
.table-hint {
margin: 12px 2px 0;
font-size: 0.78rem;
color: var(--muted);
}
/* ===== Legend ===== */
.legend {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
gap: 14px;
}
.legend-item {
display: flex;
align-items: center;
gap: 12px;
border: 1px dashed var(--line-2);
border-radius: var(--r-md);
padding: 12px 14px;
background: var(--kraft-l);
transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.legend-item:hover {
transform: translateY(-2px);
box-shadow: var(--shadow);
}
.legend-swatch {
width: 44px;
height: 44px;
flex: none;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-sm);
display: grid;
place-items: center;
}
.legend-swatch svg {
width: 34px;
height: 34px;
}
.legend-item strong {
display: block;
font-size: 0.84rem;
}
.legend-mono {
display: block;
font-size: 0.7rem;
color: var(--muted);
letter-spacing: 0.04em;
}
/* ===== Footer ===== */
.site-foot {
max-width: 1100px;
margin: 0 auto;
padding: 0 24px 40px;
display: flex;
flex-wrap: wrap;
gap: 8px 20px;
justify-content: space-between;
color: var(--muted);
font-size: 0.75rem;
}
.site-foot .mono {
letter-spacing: 0.1em;
}
/* ===== Toast ===== */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 16px);
background: var(--ink);
color: var(--kraft-l);
font-family: var(--mono);
font-size: 0.78rem;
letter-spacing: 0.04em;
padding: 10px 18px;
border-radius: var(--r-sm);
border-left: 4px solid var(--orange);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 50;
max-width: min(92vw, 480px);
}
.toast.show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ===== Responsive ===== */
@media (max-width: 900px) {
.diagram-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 520px) {
.hero {
padding: 32px 16px 28px;
}
.wrap {
padding: 20px 12px 40px;
gap: 18px;
}
.panel {
padding: 16px 12px;
}
.panel-head {
align-items: flex-start;
flex-direction: column;
}
.toolbar {
width: 100%;
justify-content: space-between;
gap: 12px;
}
.btn {
padding: 9px 12px;
font-size: 0.78rem;
}
.con-table {
font-size: 0.72rem;
}
.con-table td,
.con-table th {
padding: 8px 8px;
}
.legend {
grid-template-columns: 1fr 1fr;
}
.legend-item {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
}
@media (prefers-reduced-motion: reduce) {
* {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}/* DIY — Interactive Wiring Diagram
Vanilla JS: builds the breadboard hole grid, wires up the connections
table <-> SVG highlighting, and animates a signal-flow pulse. */
(function () {
"use strict";
var svg = document.getElementById("diagram");
var stage = document.getElementById("stage");
var holesG = document.getElementById("holes");
var rowNumsG = document.getElementById("row-nums");
var tbody = document.getElementById("con-body");
var playBtn = document.getElementById("play-btn");
var playLabel = document.getElementById("play-label");
var labelsToggle = document.getElementById("labels-toggle");
var pulsePath = document.getElementById("pulse");
var toastEl = document.getElementById("toast");
var NS = "http://www.w3.org/2000/svg";
/* ---------------- Data ---------------- */
var WIRES = [
{
id: "w1",
color: "#d4503e",
colorName: "RED",
from: "MK-01 · 5V",
to: "+ RAIL",
purpose: "Feeds 5 V to the positive power rail.",
ends: [[406, 110]],
reverse: false,
step: "W1 — 5V feeds the + rail"
},
{
id: "w2",
color: "#1c2733",
colorName: "BLACK",
from: "MK-01 · GND",
to: "− RAIL",
purpose: "Ground return back to the board.",
ends: [[434, 136]],
reverse: true,
step: "W2 — return path to GND"
},
{
id: "w3",
color: "#ff6b35",
colorName: "ORANGE",
from: "MK-01 · D5",
to: "ROW 4 · a",
purpose: "Drive signal for LED1 through R1 (220 Ω).",
ends: [[490, 188]],
reverse: false,
step: "W3 — D5 drives R1 → LED1"
},
{
id: "w4",
color: "#3b6ea5",
colorName: "BLUE",
from: "ROW 8 · a",
to: "− RAIL",
purpose: "LED1 cathode down to ground rail.",
ends: [[434, 292], [490, 292]],
reverse: true,
step: "W4 — LED1 cathode to − rail"
},
{
id: "w5",
color: "#e8b64c",
colorName: "YELLOW",
from: "MK-01 · D2",
to: "ROW 11 · a",
purpose: "Button sense line (internal pull-up on D2).",
ends: [[490, 370]],
reverse: false,
step: "W5 — D2 senses SW1"
},
{
id: "w6",
color: "#2f9e6f",
colorName: "GREEN",
from: "ROW 13 · a",
to: "− RAIL",
purpose: "Button leg pulled to ground when pressed.",
ends: [[434, 422], [490, 422]],
reverse: true,
step: "W6 — SW1 leg to − rail"
}
];
/* Signal-flow narration order */
var FLOW_ORDER = ["w1", "w3", "w4", "w2", "w5", "w6"];
/* ---------------- Toast helper ---------------- */
var toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 1900);
}
/* ---------------- Build breadboard holes ---------------- */
var ROW_Y0 = 110;
var ROW_STEP = 26;
var ROWS = 15;
var COLS_LEFT = [490, 520, 550, 580, 610]; /* a–e */
var COLS_RIGHT = [656, 686, 716]; /* f–h (trimmed half board) */
var RAILS = [406, 434];
function makeHole(x, y) {
var c = document.createElementNS(NS, "circle");
c.setAttribute("class", "hole");
c.setAttribute("cx", x);
c.setAttribute("cy", y);
c.setAttribute("r", 3.5);
c.dataset.key = x + "," + y;
holesG.appendChild(c);
}
(function buildBoard() {
var r, x, y, i;
for (r = 0; r < ROWS; r++) {
y = ROW_Y0 + r * ROW_STEP;
for (i = 0; i < RAILS.length; i++) makeHole(RAILS[i], y);
for (i = 0; i < COLS_LEFT.length; i++) makeHole(COLS_LEFT[i], y);
for (i = 0; i < COLS_RIGHT.length; i++) makeHole(COLS_RIGHT[i], y);
if ((r + 1) % 2 === 1) {
var t = document.createElementNS(NS, "text");
t.setAttribute("class", "row-num");
t.setAttribute("x", 472);
t.setAttribute("y", y + 3.5);
t.setAttribute("text-anchor", "end");
t.textContent = String(r + 1);
rowNumsG.appendChild(t);
}
}
})();
/* ---------------- Build connections table ---------------- */
WIRES.forEach(function (w) {
var tr = document.createElement("tr");
tr.className = "con-row";
tr.dataset.wire = w.id;
tr.tabIndex = 0;
tr.setAttribute("role", "button");
tr.setAttribute("aria-describedby", "desc-" + w.id);
tr.setAttribute(
"aria-label",
w.colorName + " wire, " + w.from + " to " + w.to
);
tr.innerHTML =
'<td><span class="wire-chip"><span class="swatch" style="background:' +
w.color +
'"></span>' +
w.id.toUpperCase() +
"·" +
w.colorName +
"</span></td>" +
'<td class="cell-pin">' +
w.from +
"</td>" +
'<td class="cell-pin">' +
w.to +
"</td>" +
'<td class="cell-purpose">' +
w.purpose +
'<span class="sr-only" id="desc-' +
w.id +
'">' +
w.purpose +
" Press Enter to pulse this wire.</span></td>";
tbody.appendChild(tr);
});
/* ---------------- Highlight logic ---------------- */
function wireGroup(id) {
return svg.querySelector('.wire[data-wire="' + id + '"]');
}
function wireLabel(id) {
return svg.querySelector('.wlabel[data-wire="' + id + '"]');
}
function tableRow(id) {
return tbody.querySelector('.con-row[data-wire="' + id + '"]');
}
function wireData(id) {
for (var i = 0; i < WIRES.length; i++)
if (WIRES[i].id === id) return WIRES[i];
return null;
}
function setHot(w, on) {
/* light up the breadboard holes this wire lands in */
w.ends.forEach(function (p) {
var hole = holesG.querySelector('[data-key="' + p[0] + "," + p[1] + '"]');
if (hole) hole.classList.toggle("hot", on);
});
}
var currentId = null;
function highlight(id) {
if (currentId === id) return;
clearHighlight();
var g = wireGroup(id);
var d = wireData(id);
if (!g || !d) return;
currentId = id;
svg.classList.add("dimmed");
g.classList.add("active");
var lbl = wireLabel(id);
if (lbl) lbl.classList.add("active");
var row = tableRow(id);
if (row) row.classList.add("active");
setHot(d, true);
}
function clearHighlight() {
if (!currentId) return;
var g = wireGroup(currentId);
var lbl = wireLabel(currentId);
var row = tableRow(currentId);
var d = wireData(currentId);
if (g) g.classList.remove("active");
if (lbl) lbl.classList.remove("active");
if (row) row.classList.remove("active");
if (d) setHot(d, false);
svg.classList.remove("dimmed");
currentId = null;
}
/* table rows -> wires */
tbody.addEventListener("mouseover", function (e) {
if (playing) return;
var row = e.target.closest(".con-row");
if (row) highlight(row.dataset.wire);
});
tbody.addEventListener("mouseout", function (e) {
if (playing) return;
var row = e.target.closest(".con-row");
if (row && !row.matches(":focus-visible")) clearHighlight();
});
tbody.addEventListener("focusin", function (e) {
if (playing) return;
var row = e.target.closest(".con-row");
if (row) highlight(row.dataset.wire);
});
tbody.addEventListener("focusout", function () {
if (!playing) clearHighlight();
});
tbody.addEventListener("keydown", function (e) {
var row = e.target.closest(".con-row");
if (!row) return;
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
pulseSingle(row.dataset.wire);
}
});
/* wires -> table rows */
svg.addEventListener("mouseover", function (e) {
if (playing) return;
var g = e.target.closest(".wire");
if (g) highlight(g.dataset.wire);
});
svg.addEventListener("mouseout", function (e) {
if (playing) return;
var g = e.target.closest(".wire");
if (g) clearHighlight();
});
svg.addEventListener("click", function (e) {
var g = e.target.closest(".wire");
if (!g) return;
var row = tableRow(g.dataset.wire);
if (row) row.focus();
var d = wireData(g.dataset.wire);
if (d) toast(d.id.toUpperCase() + " · " + d.from + " → " + d.to);
});
/* ---------------- Signal-flow pulse ---------------- */
var playing = false;
var raf = null;
function animateAlong(id, duration) {
return new Promise(function (resolve) {
var g = wireGroup(id);
var d = wireData(id);
if (!g || !d) return resolve();
var path = g.querySelector(".wire-path");
var len = path.getTotalLength();
var dash = 26;
pulsePath.setAttribute("d", path.getAttribute("d"));
pulsePath.style.strokeDasharray = dash + " " + (len + dash);
highlight(id);
toast(d.step);
var start = null;
function frame(ts) {
if (!playing) {
pulsePath.setAttribute("d", "");
return resolve();
}
if (start === null) start = ts;
var t = Math.min((ts - start) / duration, 1);
var eased = t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
var off = d.reverse
? -len + eased * (len + dash)
: dash - eased * (len + dash);
pulsePath.style.strokeDashoffset = off;
if (t < 1) {
raf = requestAnimationFrame(frame);
} else {
pulsePath.setAttribute("d", "");
resolve();
}
}
raf = requestAnimationFrame(frame);
});
}
function stopFlow() {
playing = false;
if (raf) cancelAnimationFrame(raf);
pulsePath.setAttribute("d", "");
clearHighlight();
playBtn.setAttribute("aria-pressed", "false");
playLabel.textContent = "Trace signal flow";
}
function runFlow() {
playing = true;
playBtn.setAttribute("aria-pressed", "true");
playLabel.textContent = "Stop trace";
var chain = Promise.resolve();
FLOW_ORDER.forEach(function (id) {
chain = chain.then(function () {
if (!playing) return;
return animateAlong(id, 750).then(function () {
clearHighlight();
return new Promise(function (res) {
setTimeout(res, 140);
});
});
});
});
chain.then(function () {
if (playing) {
stopFlow();
toast("Trace complete — circuit OK ✓");
}
});
}
playBtn.addEventListener("click", function () {
if (playing) {
stopFlow();
toast("Trace stopped");
} else {
runFlow();
}
});
function pulseSingle(id) {
if (playing) return;
playing = true;
animateAlong(id, 650).then(function () {
playing = false;
/* keep the row highlighted while it still has focus */
var row = tableRow(id);
if (!(row && document.activeElement === row)) clearHighlight();
});
}
/* ---------------- Labels toggle ---------------- */
labelsToggle.addEventListener("change", function () {
stage.classList.toggle("labels-off", !labelsToggle.checked);
toast(labelsToggle.checked ? "Wire labels ON" : "Wire labels OFF");
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DIY — Interactive Wiring Diagram</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>
<header class="hero">
<div class="hero-inner">
<div class="hero-meta">
<span class="badge badge-orange">SHEET WD-07</span>
<span class="badge badge-ghost">REV C</span>
</div>
<h1 class="hero-title">Wiring Diagram — Blink + Button Kit</h1>
<p class="hero-sub">NanoForge MK-01 → half breadboard · 6 jumpers · LED, 220 Ω resistor, tactile switch. Hover or focus a connection to trace its wire.</p>
</div>
</header>
<main class="wrap">
<section class="panel" aria-labelledby="diagram-h">
<div class="panel-head">
<h2 id="diagram-h" class="panel-title">Circuit Layout</h2>
<div class="toolbar">
<button id="play-btn" class="btn btn-primary" type="button" aria-pressed="false">
<svg class="btn-ico" viewBox="0 0 16 16" aria-hidden="true"><path d="M4 2.5v11l9-5.5-9-5.5z" fill="currentColor"/></svg>
<span id="play-label">Trace signal flow</span>
</button>
<label class="switch">
<input type="checkbox" id="labels-toggle" checked />
<span class="switch-track" aria-hidden="true"><span class="switch-thumb"></span></span>
<span class="switch-text">Wire labels</span>
</label>
</div>
</div>
<div class="diagram-grid">
<figure class="board-stage" id="stage">
<svg id="diagram" viewBox="0 0 780 540" role="img" aria-labelledby="svg-title svg-desc">
<title id="svg-title">Wiring diagram of a NanoForge MK-01 microcontroller connected to a half breadboard</title>
<desc id="svg-desc">Six colored jumper wires connect the MK-01 header pins 5V, GND, D2 and D5 to breadboard rows holding a red LED, a 220 ohm resistor and a tactile button.</desc>
<defs>
<filter id="glow" x="-40%" y="-40%" width="180%" height="180%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="soft" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="4" stdDeviation="6" flood-color="#000" flood-opacity="0.35"/>
</filter>
</defs>
<!-- ====== MCU BOARD : NanoForge MK-01 ====== -->
<g id="mcu" filter="url(#soft)">
<rect x="40" y="118" width="150" height="300" rx="12" fill="#132c3a" stroke="#0c1f2a" stroke-width="2"/>
<rect x="52" y="132" width="126" height="46" rx="6" fill="#0c1f2a"/>
<text x="115" y="151" class="silk silk-b" text-anchor="middle">NANOFORGE</text>
<text x="115" y="169" class="silk silk-mono" text-anchor="middle">MK-01 · 5V</text>
<!-- chip -->
<rect x="76" y="252" width="78" height="58" rx="4" fill="#0a1620" stroke="#233c4c"/>
<text x="115" y="285" class="silk silk-mono" text-anchor="middle">XU-880</text>
<!-- usb -->
<rect x="30" y="250" width="18" height="62" rx="3" fill="#8a97a3" stroke="#5f6b76"/>
<!-- status led -->
<circle cx="72" cy="212" r="5" fill="#2f9e6f"/>
<text x="86" y="216" class="silk silk-mono">PWR</text>
<!-- header pins -->
<g id="mcu-pins" class="mono-label">
<g><rect x="180" y="154" width="18" height="12" rx="2" fill="#e8b64c" stroke="#a87d22"/><text x="172" y="164" text-anchor="end" class="pin-name">5V</text></g>
<g><rect x="180" y="194" width="18" height="12" rx="2" fill="#e8b64c" stroke="#a87d22"/><text x="172" y="204" text-anchor="end" class="pin-name">GND</text></g>
<g><rect x="180" y="234" width="18" height="12" rx="2" fill="#e8b64c" stroke="#a87d22"/><text x="172" y="244" text-anchor="end" class="pin-name">D2</text></g>
<g><rect x="180" y="274" width="18" height="12" rx="2" fill="#e8b64c" stroke="#a87d22"/><text x="172" y="284" text-anchor="end" class="pin-name">D5</text></g>
<g><rect x="180" y="314" width="18" height="12" rx="2" fill="#e8b64c" stroke="#a87d22"/><text x="172" y="324" text-anchor="end" class="pin-name">A0</text></g>
<g><rect x="180" y="354" width="18" height="12" rx="2" fill="#e8b64c" stroke="#a87d22"/><text x="172" y="364" text-anchor="end" class="pin-name">D7</text></g>
</g>
</g>
<!-- ====== BREADBOARD ====== -->
<g id="breadboard" filter="url(#soft)">
<rect x="376" y="78" width="374" height="430" rx="14" fill="#efe6d4" stroke="#c9b990" stroke-width="2"/>
<text x="563" y="98" class="silk-board" text-anchor="middle">PROTO-HALF · PB-400</text>
<!-- power rail stripes -->
<line x1="394" y1="104" x2="394" y2="486" stroke="#d4503e" stroke-width="3" stroke-linecap="round"/>
<line x1="450" y1="104" x2="450" y2="486" stroke="#3b6ea5" stroke-width="3" stroke-linecap="round"/>
<text x="394" y="500" class="rail-sign rail-plus" text-anchor="middle">+</text>
<text x="450" y="500" class="rail-sign rail-minus" text-anchor="middle">−</text>
<!-- center groove -->
<rect x="632" y="100" width="8" height="390" rx="4" fill="#e2d5bc"/>
<!-- holes + row numbers injected by script into #holes -->
<g id="holes"></g>
<g id="row-nums"></g>
</g>
<!-- ====== COMPONENTS ====== -->
<g id="components">
<!-- R1 220R : col b, row 4 -> row 7 -->
<g id="cmp-r1" class="cmp" data-part="R1">
<line x1="520" y1="188" x2="520" y2="266" stroke="#6b7885" stroke-width="3" stroke-linecap="round"/>
<rect x="510" y="204" width="20" height="46" rx="8" fill="#e8d9b8" stroke="#b09a6c" stroke-width="1.5"/>
<line x1="510" y1="215" x2="530" y2="215" stroke="#d4503e" stroke-width="4"/>
<line x1="510" y1="226" x2="530" y2="226" stroke="#d4503e" stroke-width="4"/>
<line x1="510" y1="237" x2="530" y2="237" stroke="#8f5a1e" stroke-width="4"/>
<text x="541" y="231" class="cmp-tag">R1 220Ω</text>
</g>
<!-- LED1 : col d, anode row 7 -> cathode row 8 -->
<g id="cmp-led" class="cmp" data-part="LED1">
<line x1="574" y1="266" x2="574" y2="252" stroke="#6b7885" stroke-width="3" stroke-linecap="round"/>
<line x1="586" y1="292" x2="586" y2="256" stroke="#6b7885" stroke-width="3" stroke-linecap="round"/>
<circle id="led-dome" cx="580" cy="240" r="13" fill="#d4503e" stroke="#8f2f22" stroke-width="2.5"/>
<circle cx="576" cy="235" r="3.5" fill="#f2a396" opacity="0.9"/>
<text x="600" y="240" class="cmp-tag">LED1</text>
</g>
<!-- SW1 : col b, rows 11 & 13 -->
<g id="cmp-sw" class="cmp" data-part="SW1">
<line x1="520" y1="370" x2="520" y2="380" stroke="#6b7885" stroke-width="3" stroke-linecap="round"/>
<line x1="520" y1="422" x2="520" y2="412" stroke="#6b7885" stroke-width="3" stroke-linecap="round"/>
<rect x="500" y="378" width="40" height="36" rx="6" fill="#3d4a57" stroke="#1c2733" stroke-width="2"/>
<circle cx="520" cy="396" r="9" fill="#1c2733" stroke="#0d141a" stroke-width="2"/>
<text x="548" y="400" class="cmp-tag">SW1</text>
</g>
</g>
<!-- ====== JUMPER WIRES ====== -->
<g id="wires">
<g class="wire" data-wire="w1" tabindex="-1" aria-label="Red wire, 5V pin to plus rail">
<path class="wire-hit" d="M198 160 C 260 118, 330 92, 406 110"/>
<path class="wire-path" d="M198 160 C 260 118, 330 92, 406 110" stroke="#d4503e"/>
<circle class="ep" cx="198" cy="160" r="5"/><circle class="ep" cx="406" cy="110" r="5"/>
</g>
<g class="wire" data-wire="w2" tabindex="-1" aria-label="Black wire, GND pin to minus rail">
<path class="wire-hit" d="M198 200 C 270 168, 350 140, 434 136"/>
<path class="wire-path" d="M198 200 C 270 168, 350 140, 434 136" stroke="#1c2733"/>
<circle class="ep" cx="198" cy="200" r="5"/><circle class="ep" cx="434" cy="136" r="5"/>
</g>
<g class="wire" data-wire="w3" tabindex="-1" aria-label="Orange wire, D5 pin to row 4">
<path class="wire-hit" d="M198 280 C 280 246, 390 204, 490 188"/>
<path class="wire-path" d="M198 280 C 280 246, 390 204, 490 188" stroke="#ff6b35"/>
<circle class="ep" cx="198" cy="280" r="5"/><circle class="ep" cx="490" cy="188" r="5"/>
</g>
<g class="wire" data-wire="w4" tabindex="-1" aria-label="Blue wire, minus rail to row 8">
<path class="wire-hit" d="M434 292 C 452 310, 472 310, 490 292"/>
<path class="wire-path" d="M434 292 C 452 310, 472 310, 490 292" stroke="#3b6ea5"/>
<circle class="ep" cx="434" cy="292" r="5"/><circle class="ep" cx="490" cy="292" r="5"/>
</g>
<g class="wire" data-wire="w5" tabindex="-1" aria-label="Yellow wire, D2 pin to row 11">
<path class="wire-hit" d="M198 240 C 300 300, 390 356, 490 370"/>
<path class="wire-path" d="M198 240 C 300 300, 390 356, 490 370" stroke="#e8b64c"/>
<circle class="ep" cx="198" cy="240" r="5"/><circle class="ep" cx="490" cy="370" r="5"/>
</g>
<g class="wire" data-wire="w6" tabindex="-1" aria-label="Green wire, minus rail to row 13">
<path class="wire-hit" d="M434 422 C 452 440, 472 440, 490 422"/>
<path class="wire-path" d="M434 422 C 452 440, 472 440, 490 422" stroke="#2f9e6f"/>
<circle class="ep" cx="434" cy="422" r="5"/><circle class="ep" cx="490" cy="422" r="5"/>
</g>
</g>
<!-- wire labels -->
<g id="wire-labels">
<text class="wlabel" data-wire="w1" x="300" y="106">W1·5V</text>
<text class="wlabel" data-wire="w2" x="316" y="146">W2·GND</text>
<text class="wlabel" data-wire="w3" x="344" y="216">W3·D5</text>
<text class="wlabel" data-wire="w4" x="462" y="318">W4·LED−</text>
<text class="wlabel" data-wire="w5" x="344" y="316">W5·D2</text>
<text class="wlabel" data-wire="w6" x="462" y="448">W6·SW−</text>
</g>
<!-- signal pulse overlay -->
<path id="pulse" class="pulse-path" d="" fill="none"/>
</svg>
<figcaption class="sr-only">Interactive diagram. Use the table to explore each wire.</figcaption>
</figure>
<div class="table-side">
<h3 class="side-title">Connections <span class="mono count-badge" id="wire-count">6 WIRES</span></h3>
<div class="table-scroll">
<table class="con-table" aria-label="Wire connections">
<thead>
<tr><th scope="col">Wire</th><th scope="col">From</th><th scope="col">To</th><th scope="col">Purpose</th></tr>
</thead>
<tbody id="con-body"><!-- rows injected --></tbody>
</table>
</div>
<p class="table-hint">Hover / focus a row to highlight its wire. Press <kbd>Enter</kbd> on a row to pulse it.</p>
</div>
</div>
</section>
<section class="panel legend-panel" aria-labelledby="legend-h">
<h2 id="legend-h" class="panel-title">Component Legend</h2>
<ul class="legend">
<li class="legend-item">
<span class="legend-swatch"><svg viewBox="0 0 40 40" aria-hidden="true"><circle cx="20" cy="18" r="9" fill="#d4503e" stroke="#8f2f22" stroke-width="2"/><line x1="16" y1="27" x2="16" y2="36" stroke="#6b7885" stroke-width="2"/><line x1="24" y1="27" x2="24" y2="34" stroke="#6b7885" stroke-width="2"/></svg></span>
<div><strong>LED1 — Red 5 mm</strong><span class="mono legend-mono">VF 2.0V · anode row 7</span></div>
</li>
<li class="legend-item">
<span class="legend-swatch"><svg viewBox="0 0 40 40" aria-hidden="true"><line x1="20" y1="4" x2="20" y2="36" stroke="#6b7885" stroke-width="2"/><rect x="13" y="12" width="14" height="16" rx="4" fill="#e8d9b8" stroke="#b09a6c" stroke-width="1.5"/><line x1="13" y1="16.5" x2="27" y2="16.5" stroke="#d4503e" stroke-width="2.5"/><line x1="13" y1="20.5" x2="27" y2="20.5" stroke="#d4503e" stroke-width="2.5"/><line x1="13" y1="24.5" x2="27" y2="24.5" stroke="#8f5a1e" stroke-width="2.5"/></svg></span>
<div><strong>R1 — Resistor</strong><span class="mono legend-mono">220 Ω · rows 4→7</span></div>
</li>
<li class="legend-item">
<span class="legend-swatch"><svg viewBox="0 0 40 40" aria-hidden="true"><rect x="8" y="10" width="24" height="22" rx="4" fill="#3d4a57" stroke="#1c2733" stroke-width="2"/><circle cx="20" cy="21" r="6" fill="#1c2733" stroke="#0d141a" stroke-width="1.5"/></svg></span>
<div><strong>SW1 — Tactile button</strong><span class="mono legend-mono">6×6 mm · rows 11/13</span></div>
</li>
<li class="legend-item">
<span class="legend-swatch"><svg viewBox="0 0 40 40" aria-hidden="true"><path d="M6 30 C 14 10, 26 10, 34 26" fill="none" stroke="#ff6b35" stroke-width="4" stroke-linecap="round"/><circle cx="6" cy="30" r="3.5" fill="#1c2733"/><circle cx="34" cy="26" r="3.5" fill="#1c2733"/></svg></span>
<div><strong>Jumper wire</strong><span class="mono legend-mono">M–M · 22 AWG</span></div>
</li>
<li class="legend-item">
<span class="legend-swatch"><svg viewBox="0 0 40 40" aria-hidden="true"><rect x="12" y="4" width="6" height="32" rx="2" fill="#ffe3d6" stroke="#d4503e" stroke-width="1.5"/><rect x="23" y="4" width="6" height="32" rx="2" fill="#dbe7f5" stroke="#3b6ea5" stroke-width="1.5"/></svg></span>
<div><strong>Power rails</strong><span class="mono legend-mono">+ 5V · − GND</span></div>
</li>
</ul>
</section>
</main>
<footer class="site-foot">
<span class="mono">STEALTHIS WORKSHOP · KIT #BLINK-BTN-01</span>
<span>Fictional kit — for UI demonstration only.</span>
</footer>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Interactive Wiring Diagram
A blueprint-style circuit sheet for a classic “blink + button” starter kit. The diagram is a hand-built SVG sitting on a navy blueprint grid: a fictional NanoForge MK-01 dev board with a labeled header on the left, and a kraft-toned half breadboard on the right with power rails, numbered rows, a red LED, a 220 Ω resistor, and a tactile switch. Six curved jumper wires connect header pins to breadboard rows, each tagged with a mono label.
The connections table is the diagram’s index. Hovering or focusing any row highlights the matching wire — thicker stroke, glow filter, orange endpoint holes — while every other wire dims, and hovering a wire highlights its row in return. Rows are real focus targets with aria-describedby descriptions, and pressing Enter on a row fires a single pulse down that wire.
The Trace signal flow button plays the whole circuit in order: power feed, D5 through the resistor into the LED, the cathode’s return to the ground rail, then the button’s sense and ground legs — each step animated as a bright dash pulse along the wire with a toast narrating what is happening. A switch toggles wire labels for a cleaner sheet, and the whole layout collapses to a single column on small screens.
Illustrative UI only — always follow real safety guidance when working with tools, electronics, or repairs.