DIY — Arduino Tutorial
A blueprint-styled electronics lesson page teaching the classic Blink + Button circuit on a fictional UNO-compatible board. It pairs a components list with mono part refs and a resistor color-band graphic, an interactive SVG wiring diagram cross-highlighted with its connection table, a hand-highlighted sketch with line numbers, copy button and setup()/loop() callouts, a progress-tracked upload checklist, a troubleshooting accordion, and next-lesson cards.
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;
--mono: "JetBrains Mono", ui-monospace, monospace;
--shadow-1: 0 2px 8px rgba(28, 39, 51, 0.06), 0 8px 24px rgba(28, 39, 51, 0.07);
--shadow-2: 0 4px 14px rgba(28, 39, 51, 0.1), 0 16px 40px rgba(28, 39, 51, 0.12);
}
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
color: var(--ink);
background: var(--bg);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
code { font-family: var(--mono); font-size: 0.9em; background: rgba(28, 39, 51, 0.07); padding: 0.1em 0.35em; border-radius: 4px; }
.mono { font-family: var(--mono); }
.wrap { max-width: 1120px; margin: 0 auto; padding: 0 24px; }
/* ============ HERO ============ */
.hero {
color: var(--kraft-l);
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;
padding: 32px 0 64px;
border-bottom: 4px solid var(--orange);
}
.crumbs {
font-family: var(--mono);
font-size: 12px;
letter-spacing: 0.04em;
display: flex;
gap: 10px;
color: rgba(239, 230, 212, 0.6);
margin-bottom: 40px;
}
.crumbs a { color: rgba(239, 230, 212, 0.75); text-decoration: none; }
.crumbs a:hover, .crumbs a:focus-visible { color: var(--orange); }
.crumbs [aria-current] { color: var(--kraft-l); }
.hero-grid {
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 48px;
align-items: start;
}
.kicker {
font-family: var(--mono);
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.22em;
color: var(--orange);
margin: 0 0 16px;
display: flex;
align-items: center;
gap: 10px;
}
.kicker .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--orange); box-shadow: 0 0 0 4px rgba(255, 107, 53, 0.25); animation: pulse 2.4s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { box-shadow: 0 0 0 3px rgba(255, 107, 53, 0.25); } 50% { box-shadow: 0 0 0 7px rgba(255, 107, 53, 0.08); } }
.hero h1 {
font-size: clamp(30px, 4.4vw, 50px);
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.02em;
line-height: 1.06;
margin: 0 0 18px;
color: var(--white);
}
.hero h1 .accent { color: var(--orange); }
.lede { font-size: 17px; color: rgba(239, 230, 212, 0.85); max-width: 46ch; margin: 0 0 26px; }
.meta-row { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 30px; }
.badge {
display: inline-flex;
align-items: center;
gap: 7px;
font-family: var(--mono);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.03em;
padding: 7px 12px;
border-radius: 999px;
border: 1px solid rgba(239, 230, 212, 0.28);
color: var(--kraft-l);
background: rgba(255, 255, 255, 0.05);
}
.badge-board { border-color: var(--orange); color: #ffb08e; background: rgba(255, 107, 53, 0.12); }
.pips { display: inline-flex; gap: 3px; }
.pips i { width: 7px; height: 7px; border-radius: 2px; background: rgba(239, 230, 212, 0.25); }
.pips i.on { background: var(--orange); }
.hero-cta { display: flex; gap: 12px; flex-wrap: wrap; }
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 700;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.08em;
text-decoration: none;
padding: 13px 22px;
border-radius: var(--r-sm);
border: 2px solid transparent;
cursor: pointer;
transition: transform 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--orange); color: var(--blueprint-d); box-shadow: 0 6px 18px rgba(255, 107, 53, 0.35); }
.btn-primary:hover, .btn-primary:focus-visible { background: var(--orange-d); color: var(--white); }
.btn-ghost { border-color: rgba(239, 230, 212, 0.4); color: var(--kraft-l); }
.btn-ghost:hover, .btn-ghost:focus-visible { border-color: var(--orange); color: var(--orange); }
.learn-card {
background: rgba(10, 30, 54, 0.7);
border: 1px dashed rgba(239, 230, 212, 0.35);
border-radius: var(--r-lg);
padding: 26px 26px 20px;
backdrop-filter: blur(2px);
}
.learn-card h2 {
margin: 0 0 16px;
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.2em;
color: var(--orange);
}
.learn-list { list-style: none; margin: 0; padding: 0; }
.learn-list li {
display: flex;
gap: 12px;
align-items: baseline;
padding: 9px 0;
border-bottom: 1px solid rgba(239, 230, 212, 0.12);
font-size: 14.5px;
color: rgba(239, 230, 212, 0.92);
}
.learn-list li:last-child { border-bottom: 0; }
.learn-list code { background: rgba(255, 255, 255, 0.1); color: #ffc9ae; }
.ref {
font-family: var(--mono);
font-size: 11px;
font-weight: 700;
color: var(--blueprint-d);
background: var(--kraft);
border-radius: 4px;
padding: 2px 6px;
flex-shrink: 0;
}
.learn-note { margin: 14px 0 0; font-family: var(--mono); font-size: 11.5px; color: rgba(239, 230, 212, 0.55); }
/* ============ SECTIONS ============ */
.main { padding: 56px 24px 72px; }
.section { margin-bottom: 72px; }
.section-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16px;
border-bottom: 2px solid var(--ink);
padding-bottom: 12px;
margin-bottom: 26px;
}
.section-head h2 {
margin: 0;
font-size: clamp(20px, 2.6vw, 27px);
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.05em;
display: flex;
align-items: center;
gap: 14px;
}
.step-no {
font-family: var(--mono);
font-size: 15px;
font-weight: 700;
color: var(--white);
background: var(--orange);
border-radius: var(--r-sm);
padding: 4px 10px;
}
.section-tag { font-family: var(--mono); font-size: 12px; font-weight: 600; color: var(--muted); letter-spacing: 0.08em; }
.section-lede { color: var(--ink-2); max-width: 68ch; margin: 0 0 24px; }
/* ============ BOM ============ */
.bom-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
gap: 18px;
}
.bom-card {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 22px 20px 18px;
box-shadow: var(--shadow-1);
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.bom-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-2); border-color: var(--orange); }
.bom-card h3 { margin: 0 0 4px; font-size: 16px; font-weight: 700; }
.bom-ref { font-family: var(--mono); font-size: 11.5px; color: var(--muted); margin: 0 0 6px; }
.bom-qty {
font-family: var(--mono);
font-size: 13px;
font-weight: 700;
color: var(--orange-d);
margin: 0 0 8px;
}
.bom-note { font-size: 13px; color: var(--ink-2); margin: 0; }
.bom-visual {
height: 74px;
border-radius: var(--r-sm);
background:
linear-gradient(rgba(28, 39, 51, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(28, 39, 51, 0.05) 1px, transparent 1px),
var(--kraft-l);
background-size: 8px 8px, 8px 8px, auto;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
margin-bottom: 16px;
position: relative;
overflow: hidden;
}
/* LED mini graphic */
.led-visual { flex-direction: column; gap: 0; }
.led-dome {
width: 26px; height: 30px;
background: linear-gradient(160deg, #ff8f74, var(--danger));
border-radius: 13px 13px 4px 4px;
box-shadow: 0 0 14px rgba(212, 80, 62, 0.5);
}
.led-leg { width: 2px; height: 14px; background: #8a939c; display: inline-block; }
.led-visual { position: relative; }
.led-visual .led-leg { position: absolute; top: 50px; }
.led-visual .led-leg.long { height: 20px; left: calc(50% - 7px); }
.led-visual .led-leg:not(.long) { left: calc(50% + 5px); }
/* Resistor mini graphic with color bands */
.res-lead { width: 22px; height: 2px; background: #8a939c; }
.res-body {
width: 62px; height: 22px;
background: linear-gradient(180deg, #e8d8b8, #cdb488);
border-radius: 10px;
display: flex;
align-items: stretch;
justify-content: center;
gap: 6px;
padding: 0 8px;
box-shadow: inset 0 -3px 4px rgba(28, 39, 51, 0.18);
}
.band { width: 6px; border-radius: 1px; }
.b-red { background: #c8372c; }
.b-brown { background: #7a4a24; }
.b-gold { background: linear-gradient(180deg, #e8c25a, #b98d2e); }
/* Button mini graphic */
.btn-base {
width: 44px; height: 44px;
background: linear-gradient(180deg, #4a5866, #313d49);
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
box-shadow: 0 3px 6px rgba(28, 39, 51, 0.3);
}
.btn-cap { width: 20px; height: 20px; border-radius: 50%; background: radial-gradient(circle at 35% 30%, #56636f, #232d37); }
/* Jumper wires mini graphic */
.jw { width: 52px; height: 5px; border-radius: 3px; transform: rotate(-18deg); }
.wires-visual { flex-direction: column; gap: 9px; }
.jw-o { background: var(--orange); }
.jw-b { background: #3577c9; }
.jw-g { background: #6b7885; }
.bench-tip {
margin: 22px 0 0;
padding: 14px 18px;
border-left: 4px solid var(--warn);
background: var(--white);
border-radius: 0 var(--r-sm) var(--r-sm) 0;
font-size: 14px;
color: var(--ink-2);
box-shadow: var(--shadow-1);
}
/* ============ WIRING ============ */
.wiring-grid {
display: grid;
grid-template-columns: 1.5fr 1fr;
gap: 22px;
align-items: start;
}
.diagram-card {
margin: 0;
background: var(--blueprint);
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px);
background-size: 8px 8px;
border-radius: var(--r-md);
padding: 18px;
box-shadow: var(--shadow-2);
}
.diagram-card svg { display: block; width: 100%; height: auto; }
.diagram-card figcaption {
font-family: var(--mono);
font-size: 11.5px;
color: rgba(239, 230, 212, 0.65);
margin-top: 12px;
letter-spacing: 0.04em;
}
/* SVG parts */
.board-body { fill: var(--blueprint-l); }
.board-outline { fill: none; stroke: rgba(239, 230, 212, 0.5); stroke-width: 1.5; }
.board-chip { fill: var(--blueprint-d); stroke: rgba(239, 230, 212, 0.35); }
.board-usb { fill: #55616d; }
.svg-label { fill: var(--kraft-l); font-family: var(--mono); }
.svg-label-lg { font-size: 18px; font-weight: 700; letter-spacing: 2px; }
.svg-label-sm { font-size: 9px; letter-spacing: 1.5px; fill: rgba(239, 230, 212, 0.6); }
.svg-label-xs { font-size: 10px; fill: rgba(239, 230, 212, 0.8); }
.pin rect { fill: var(--kraft); transition: fill 0.15s ease; }
.pin text { fill: var(--kraft-l); font-family: var(--mono); font-size: 11px; font-weight: 600; }
.bb-body { fill: var(--kraft-l); }
.bb-outline { fill: none; stroke: rgba(28, 39, 51, 0.35); stroke-width: 1.5; }
.bb-rail { stroke-width: 3; stroke-linecap: round; }
.bb-rail-neg { stroke: #55616d; }
.bb-rail-pos { stroke: var(--danger); }
.bb-gap { stroke: rgba(28, 39, 51, 0.18); stroke-width: 8; }
.bb-holes circle { fill: rgba(28, 39, 51, 0.22); }
.cmp-lead { stroke: #8a939c; stroke-width: 2.5; }
.res-svg { fill: #e0cda6; stroke: #b39a6b; }
.rb-red { fill: #c8372c; }
.rb-brown { fill: #7a4a24; }
.rb-gold { fill: #d9ab3c; }
.led-svg { fill: rgba(212, 80, 62, 0.35); stroke: var(--danger); stroke-width: 2; }
.led-core { fill: var(--danger); }
.sw-svg { fill: #3d4a57; stroke: rgba(239, 230, 212, 0.4); }
.sw-cap { fill: #232d37; stroke: #55616d; }
.wire { fill: none; stroke-width: 4; stroke-linecap: round; transition: stroke-width 0.15s ease, opacity 0.2s ease, filter 0.2s ease; cursor: pointer; }
.wire-orange { stroke: var(--orange); }
.wire-blue { stroke: #4d8fe0; }
.wire-gray { stroke: #9aa5b0; }
.wire-black { stroke: #313d49; }
.dot-o { fill: var(--orange); }
.dot-b { fill: #4d8fe0; }
.dot-g { fill: #9aa5b0; }
.dot-k { fill: #313d49; stroke: rgba(239, 230, 212, 0.5); }
/* highlight states driven by data-hl on the svg root */
#wiring-svg.has-hl .wire { opacity: 0.18; }
#wiring-svg.has-hl .wire.hl { opacity: 1; stroke-width: 6; filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.5)); }
#wiring-svg.has-hl .cmp { opacity: 0.35; transition: opacity 0.2s ease; }
#wiring-svg.has-hl .cmp.hl { opacity: 1; }
#wiring-svg .pin.hl rect { fill: var(--orange); }
.conn-card {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
box-shadow: var(--shadow-1);
overflow: hidden;
}
.conn-title {
margin: 0;
padding: 16px 20px 12px;
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.14em;
color: var(--ink-2);
border-bottom: 1px solid var(--line);
}
.conn-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.conn-table th {
text-align: left;
font-family: var(--mono);
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
padding: 10px 14px 8px;
border-bottom: 1px solid var(--line);
}
.conn-table td { padding: 11px 14px; border-bottom: 1px solid var(--line); vertical-align: top; }
.conn-row { cursor: pointer; transition: background 0.15s ease; }
.conn-row:hover, .conn-row:focus-visible, .conn-row.hl {
background: var(--orange-soft);
outline: none;
}
.conn-row.hl td:first-child { box-shadow: inset 3px 0 0 var(--orange); }
.conn-table .mono { font-size: 12px; font-weight: 600; background: rgba(28, 39, 51, 0.06); border-radius: 4px; padding: 1px 5px; }
.swatch { display: inline-block; width: 11px; height: 11px; border-radius: 3px; margin-right: 7px; vertical-align: -1px; }
.sw-orange { background: var(--orange); }
.sw-blue { background: #4d8fe0; }
.sw-gray { background: #9aa5b0; }
.sw-black { background: #313d49; }
.conn-note { font-size: 12.5px; color: var(--muted); margin: 0; padding: 12px 20px 16px; }
/* ============ CODE ============ */
.code-card {
border-radius: var(--r-md);
overflow: hidden;
box-shadow: var(--shadow-2);
background: var(--blueprint-d);
}
.code-bar {
display: flex;
align-items: center;
gap: 14px;
padding: 12px 16px;
background: var(--blueprint);
border-bottom: 1px solid rgba(239, 230, 212, 0.12);
}
.code-dots { display: flex; gap: 6px; }
.code-dots i { width: 10px; height: 10px; border-radius: 50%; background: rgba(239, 230, 212, 0.22); }
.code-dots i:first-child { background: var(--danger); }
.code-dots i:nth-child(2) { background: var(--warn); }
.code-dots i:nth-child(3) { background: var(--ok); }
.code-file { font-family: var(--mono); font-size: 12.5px; color: var(--kraft-l); flex: 1; }
.copy-btn {
display: inline-flex;
align-items: center;
gap: 6px;
font-family: var(--mono);
font-size: 12px;
font-weight: 600;
color: var(--kraft-l);
background: rgba(255, 255, 255, 0.07);
border: 1px solid rgba(239, 230, 212, 0.25);
border-radius: var(--r-sm);
padding: 6px 12px;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.copy-btn:hover, .copy-btn:focus-visible { border-color: var(--orange); color: var(--orange); }
.copy-btn.copied { border-color: var(--ok); color: #6fd4a6; }
.code-body {
margin: 0;
padding: 20px 0;
overflow-x: auto;
font-family: var(--mono);
font-size: 13.5px;
line-height: 1.7;
color: #d7e3f0;
counter-reset: line;
}
.code-body code { background: none; padding: 0; font-size: inherit; display: block; min-width: max-content; }
.cl { display: block; padding: 0 20px 0 0; counter-increment: line; position: relative; }
.cl::before {
content: counter(line, decimal-leading-zero);
display: inline-block;
width: 52px;
padding-right: 18px;
text-align: right;
color: rgba(215, 227, 240, 0.3);
user-select: none;
}
.cl.mark-setup { background: rgba(255, 107, 53, 0.08); box-shadow: inset 3px 0 0 var(--orange); }
.cl.mark-loop { background: rgba(77, 143, 224, 0.08); box-shadow: inset 3px 0 0 #4d8fe0; }
.tk-kw { color: #ff9d6e; }
.tk-type { color: #7fc7ff; }
.tk-fn { color: #ffd479; }
.tk-num { color: #9be49b; }
.tk-str { color: #9be49b; }
.tk-cm { color: rgba(215, 227, 240, 0.42); font-style: italic; }
.tk-op { color: #d7e3f0; }
.tk-const { color: #e0a8ff; font-weight: 600; }
.callout-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-top: 20px; }
.callout {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px 20px;
font-size: 14px;
color: var(--ink-2);
box-shadow: var(--shadow-1);
}
.callout p { margin: 8px 0 0; }
.callout-ref {
font-family: var(--mono);
font-size: 11.5px;
font-weight: 700;
letter-spacing: 0.06em;
padding: 3px 9px;
border-radius: 4px;
color: var(--white);
}
.callout-setup { border-top: 3px solid var(--orange); }
.callout-setup .callout-ref { background: var(--orange); }
.callout-loop { border-top: 3px solid #4d8fe0; }
.callout-loop .callout-ref { background: #4d8fe0; }
/* ============ CHECKLIST ============ */
.check-card {
background: var(--white);
border: 1px dashed var(--line-2);
border-radius: var(--r-lg);
padding: 26px 28px;
box-shadow: var(--shadow-1);
}
.check-progress { height: 8px; border-radius: 4px; background: rgba(28, 39, 51, 0.08); overflow: hidden; }
.check-progress-bar {
height: 100%;
width: 0%;
border-radius: 4px;
background: linear-gradient(90deg, var(--orange), var(--ok));
transition: width 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.check-count { font-family: var(--mono); font-size: 12px; font-weight: 600; color: var(--muted); margin: 10px 0 16px; }
.checklist { list-style: none; margin: 0; padding: 0; }
.checklist li { border-bottom: 1px solid var(--line); }
.checklist li:last-child { border-bottom: 0; }
.checklist label {
display: flex;
align-items: flex-start;
gap: 14px;
padding: 13px 4px;
cursor: pointer;
font-size: 14.5px;
}
.checklist input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.ck {
flex-shrink: 0;
width: 22px; height: 22px;
border: 2px solid var(--line-2);
border-radius: var(--r-sm);
margin-top: 1px;
position: relative;
transition: background 0.15s ease, border-color 0.15s ease;
}
.ck::after {
content: "";
position: absolute;
left: 6px; top: 2px;
width: 5px; height: 10px;
border: solid var(--white);
border-width: 0 2.5px 2.5px 0;
transform: rotate(45deg) scale(0);
transition: transform 0.15s ease;
}
.checklist input:checked + .ck { background: var(--ok); border-color: var(--ok); }
.checklist input:checked + .ck::after { transform: rotate(45deg) scale(1); }
.checklist input:focus-visible + .ck { outline: 2px solid var(--orange); outline-offset: 2px; }
.checklist input:checked ~ .ck-text { color: var(--muted); text-decoration: line-through; text-decoration-color: rgba(28, 39, 51, 0.3); }
.checklist label:hover .ck { border-color: var(--orange); }
/* ============ ACCORDION ============ */
.accordion { display: flex; flex-direction: column; gap: 12px; }
.acc-item {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
overflow: hidden;
box-shadow: var(--shadow-1);
transition: border-color 0.15s ease;
}
.acc-item:has(.acc-btn[aria-expanded="true"]) { border-color: var(--orange); }
.acc-heading { margin: 0; }
.acc-btn {
width: 100%;
display: flex;
align-items: center;
gap: 14px;
text-align: left;
font: inherit;
font-size: 15px;
font-weight: 600;
color: var(--ink);
background: none;
border: 0;
padding: 17px 20px;
cursor: pointer;
}
.acc-btn:hover, .acc-btn:focus-visible { color: var(--orange-d); outline: none; }
.acc-btn:focus-visible { box-shadow: inset 0 0 0 2px var(--orange); }
.acc-code {
font-family: var(--mono);
font-size: 11px;
font-weight: 700;
color: var(--white);
background: var(--ink-2);
border-radius: 4px;
padding: 3px 8px;
flex-shrink: 0;
}
.acc-btn[aria-expanded="true"] .acc-code { background: var(--orange); }
.acc-chev {
margin-left: auto;
width: 9px; height: 9px;
border-right: 2px solid currentColor;
border-bottom: 2px solid currentColor;
transform: rotate(45deg);
transition: transform 0.2s ease;
flex-shrink: 0;
}
.acc-btn[aria-expanded="true"] .acc-chev { transform: rotate(-135deg); }
.acc-panel { padding: 0 20px 18px 20px; font-size: 14px; color: var(--ink-2); }
.acc-panel ul { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 7px; }
.acc-panel .mono { font-size: 12.5px; background: rgba(28, 39, 51, 0.07); border-radius: 4px; padding: 1px 5px; }
/* ============ NEXT LESSONS ============ */
.next-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.next-card {
display: block;
text-decoration: none;
color: var(--kraft-l);
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;
border-radius: var(--r-md);
padding: 24px 22px;
border: 1px solid transparent;
box-shadow: var(--shadow-1);
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.next-card:hover, .next-card:focus-visible { transform: translateY(-4px); box-shadow: var(--shadow-2); border-color: var(--orange); outline: none; }
.next-ref { font-family: var(--mono); font-size: 11px; font-weight: 700; letter-spacing: 0.16em; color: var(--orange); }
.next-card h3 { margin: 10px 0 8px; font-size: 18px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.03em; color: var(--white); }
.next-card p { margin: 0 0 16px; font-size: 13.5px; color: rgba(239, 230, 212, 0.8); }
.next-card code { background: rgba(255, 255, 255, 0.1); color: #ffc9ae; }
.next-meta { font-family: var(--mono); font-size: 11.5px; color: rgba(239, 230, 212, 0.55); }
.footer-note {
text-align: center;
font-size: 11px;
letter-spacing: 0.18em;
color: var(--muted);
margin: 44px 0 0;
}
/* ============ TOAST ============ */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 20px);
background: var(--ink);
color: var(--kraft-l);
font-family: var(--mono);
font-size: 13px;
font-weight: 600;
padding: 12px 20px;
border-radius: var(--r-sm);
border-left: 4px solid var(--orange);
box-shadow: var(--shadow-2);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 60;
max-width: calc(100vw - 40px);
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
/* ============ RESPONSIVE ============ */
@media (max-width: 900px) {
.hero-grid { grid-template-columns: 1fr; gap: 30px; }
.wiring-grid { grid-template-columns: 1fr; }
.callout-grid { grid-template-columns: 1fr; }
.next-grid { grid-template-columns: 1fr; }
}
@media (max-width: 520px) {
.wrap { padding: 0 16px; }
.main { padding: 40px 16px 56px; }
.section { margin-bottom: 52px; }
.hero { padding: 24px 0 44px; }
.crumbs { margin-bottom: 26px; flex-wrap: wrap; }
.lede { font-size: 15.5px; }
.meta-row { gap: 8px; }
.badge { font-size: 11px; padding: 6px 10px; }
.btn { width: 100%; justify-content: center; }
.hero-cta { flex-direction: column; }
.learn-card { padding: 20px 18px 14px; }
.section-head { flex-direction: column; align-items: flex-start; gap: 6px; }
.bom-grid { grid-template-columns: 1fr; }
.diagram-card { padding: 12px; }
.conn-table { font-size: 12.5px; }
.conn-table th, .conn-table td { padding: 9px 10px; }
.check-card { padding: 20px 16px; }
.code-body { font-size: 12px; }
.cl::before { width: 40px; padding-right: 12px; }
}/* Lesson 03 — Blink + Button · interactions */
(function () {
"use strict";
/* ---------- toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer = null;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2400);
}
/* ---------- breadboard hole grid (generated) ---------- */
var holes = document.getElementById("holes");
if (holes) {
var frag = document.createDocumentFragment();
var ns = "http://www.w3.org/2000/svg";
for (var col = 0; col < 30; col++) {
for (var row = 0; row < 10; row++) {
var c = document.createElementNS(ns, "circle");
var x = 322 + col * 12.1;
var y = row < 5 ? 78 + row * 18 : 194 + (row - 5) * 18;
c.setAttribute("cx", x.toFixed(1));
c.setAttribute("cy", String(y));
c.setAttribute("r", "2.4");
frag.appendChild(c);
}
}
holes.appendChild(frag);
}
/* ---------- wiring diagram <-> table cross-highlight ---------- */
var svg = document.getElementById("wiring-svg");
var rows = Array.prototype.slice.call(document.querySelectorAll(".conn-row"));
var svgParts = svg
? Array.prototype.slice.call(svg.querySelectorAll("[data-wire]"))
: [];
function setHighlight(wireId) {
if (!svg) return;
if (wireId) {
svg.classList.add("has-hl");
} else {
svg.classList.remove("has-hl");
}
svgParts.forEach(function (el) {
el.classList.toggle("hl", el.getAttribute("data-wire") === wireId);
});
rows.forEach(function (r) {
r.classList.toggle("hl", r.getAttribute("data-wire") === wireId);
});
}
rows.forEach(function (row) {
var id = row.getAttribute("data-wire");
row.addEventListener("mouseenter", function () { setHighlight(id); });
row.addEventListener("mouseleave", function () { setHighlight(null); });
row.addEventListener("focus", function () { setHighlight(id); });
row.addEventListener("blur", function () { setHighlight(null); });
});
svgParts.forEach(function (part) {
var id = part.getAttribute("data-wire");
part.addEventListener("mouseenter", function () { setHighlight(id); });
part.addEventListener("mouseleave", function () { setHighlight(null); });
});
/* ---------- copy sketch ---------- */
var copyBtn = document.getElementById("copy-btn");
var codeBody = document.getElementById("code-body");
if (copyBtn && codeBody) {
copyBtn.addEventListener("click", function () {
var text = Array.prototype.map
.call(codeBody.querySelectorAll(".cl"), function (line) {
return line.textContent;
})
.join("\n");
function done() {
copyBtn.classList.add("copied");
var label = copyBtn.querySelector("span");
if (label) label.textContent = "Copied!";
toast("Sketch copied — paste it into the IDE");
setTimeout(function () {
copyBtn.classList.remove("copied");
if (label) label.textContent = "Copy";
}, 2000);
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(done, function () {
fallbackCopy(text, done);
});
} else {
fallbackCopy(text, done);
}
});
}
function fallbackCopy(text, done) {
var ta = document.createElement("textarea");
ta.value = text;
ta.setAttribute("readonly", "");
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
try {
document.execCommand("copy");
done();
} catch (e) {
toast("Copy failed — select the code manually");
}
document.body.removeChild(ta);
}
/* ---------- upload & test checklist ---------- */
var checklist = document.getElementById("checklist");
var checkBar = document.getElementById("check-bar");
var checkCount = document.getElementById("check-count");
var celebrated = false;
if (checklist && checkBar && checkCount) {
var boxes = Array.prototype.slice.call(
checklist.querySelectorAll('input[type="checkbox"]')
);
function updateProgress() {
var done = boxes.filter(function (b) { return b.checked; }).length;
var pct = Math.round((done / boxes.length) * 100);
checkBar.style.width = pct + "%";
checkCount.textContent = done + " / " + boxes.length + " steps done";
if (done === boxes.length && !celebrated) {
celebrated = true;
toast("QC PASS — circuit verified. On to Lesson 04!");
}
if (done < boxes.length) celebrated = false;
}
boxes.forEach(function (b) {
b.addEventListener("change", updateProgress);
});
updateProgress();
}
/* ---------- troubleshooting accordion ---------- */
var accButtons = Array.prototype.slice.call(
document.querySelectorAll(".acc-btn")
);
accButtons.forEach(function (btn) {
btn.addEventListener("click", function () {
var expanded = btn.getAttribute("aria-expanded") === "true";
/* close others (single-open behavior) */
accButtons.forEach(function (other) {
if (other !== btn) {
other.setAttribute("aria-expanded", "false");
var p = document.getElementById(other.getAttribute("aria-controls"));
if (p) p.hidden = true;
}
});
btn.setAttribute("aria-expanded", String(!expanded));
var panel = document.getElementById(btn.getAttribute("aria-controls"));
if (panel) panel.hidden = expanded;
});
});
/* ---------- smooth in-page anchors ---------- */
Array.prototype.forEach.call(
document.querySelectorAll('a[href^="#"]'),
function (a) {
a.addEventListener("click", function (ev) {
var target = document.querySelector(a.getAttribute("href"));
if (target) {
ev.preventDefault();
target.scrollIntoView({ behavior: "smooth", block: "start" });
}
});
}
);
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Lesson 03 — Blink + Button · NV-UNO Workshop</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>
<!-- ============ HERO ============ -->
<header class="hero" role="banner">
<div class="wrap">
<nav class="crumbs" aria-label="Breadcrumb">
<a href="#">Maker Bench</a>
<span aria-hidden="true">/</span>
<a href="#">Electronics Track</a>
<span aria-hidden="true">/</span>
<span aria-current="page">Lesson 03</span>
</nav>
<div class="hero-grid">
<div class="hero-copy">
<p class="kicker"><span class="dot" aria-hidden="true"></span> Electronics · Beginner Track</p>
<h1>Blink + Button<span class="accent">.</span><br />Your first input circuit</h1>
<p class="lede">
Wire a tactile button to a digital pin, read its state, and drive an LED —
the foundation of every interactive gadget you'll ever build.
</p>
<div class="meta-row" aria-label="Lesson metadata">
<span class="badge badge-board">
<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><rect x="1" y="4" width="14" height="8" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M4 4V2M8 4V2M12 4V2M4 14v-2M8 14v-2M12 14v-2" stroke="currentColor" stroke-width="1.5"/></svg>
NV-UNO R3 · UNO-compatible
</span>
<span class="badge badge-diff" title="Difficulty">
<span class="pips" aria-hidden="true"><i class="on"></i><i class="on"></i><i></i><i></i></span>
Difficulty 2/4
</span>
<span class="badge badge-time">⏱ ~35 min bench time</span>
<span class="badge badge-rev">REV C · 2026-07</span>
</div>
<div class="hero-cta">
<a class="btn btn-primary" href="#wiring">Jump to wiring</a>
<a class="btn btn-ghost" href="#code">View the sketch</a>
</div>
</div>
<aside class="learn-card" aria-labelledby="learn-title">
<h2 id="learn-title">What you'll learn</h2>
<ul class="learn-list">
<li><span class="ref">L-01</span> Reading a digital input with <code>digitalRead()</code></li>
<li><span class="ref">L-02</span> Why <code>INPUT_PULLUP</code> beats a floating pin</li>
<li><span class="ref">L-03</span> Sizing a current-limit resistor for a 5 mm LED</li>
<li><span class="ref">L-04</span> Breadboard rails, rows & the "column rule"</li>
<li><span class="ref">L-05</span> Serial monitor as your first debug tool</li>
</ul>
<p class="learn-note">Prereq: Lesson 02 — "Blink without a breadboard"</p>
</aside>
</div>
</div>
</header>
<main class="wrap main" id="main">
<!-- ============ COMPONENTS ============ -->
<section class="section" aria-labelledby="bom-title">
<div class="section-head">
<h2 id="bom-title"><span class="step-no">01</span> Components & tools</h2>
<span class="section-tag">BOM-L03</span>
</div>
<div class="bom-grid">
<article class="bom-card">
<div class="bom-visual led-visual" aria-hidden="true">
<span class="led-dome"></span>
<span class="led-leg long"></span>
<span class="led-leg"></span>
</div>
<h3>LED, 5 mm red</h3>
<p class="bom-ref">LED-5R-RED · Vf ≈ 2.0 V</p>
<p class="bom-qty">×1</p>
<p class="bom-note">Long leg = anode (+). It matters.</p>
</article>
<article class="bom-card">
<div class="bom-visual resistor-visual" aria-hidden="true">
<span class="res-lead"></span>
<span class="res-body">
<i class="band b-red" title="2"></i>
<i class="band b-red" title="2"></i>
<i class="band b-brown" title="×10"></i>
<i class="band b-gold" title="±5%"></i>
</span>
<span class="res-lead"></span>
</div>
<h3>Resistor, 220 Ω</h3>
<p class="bom-ref">R-220-0.25W · red · red · brown · gold</p>
<p class="bom-qty">×1</p>
<p class="bom-note">Limits LED current to ≈ 14 mA from a 5 V pin.</p>
</article>
<article class="bom-card">
<div class="bom-visual btn-visual" aria-hidden="true">
<span class="btn-base"><span class="btn-cap"></span></span>
</div>
<h3>Tactile button, 6 mm</h3>
<p class="bom-ref">SW-TACT-6 · momentary, 4-pin</p>
<p class="bom-qty">×1</p>
<p class="bom-note">Legs bridge in pairs across the center gap.</p>
</article>
<article class="bom-card">
<div class="bom-visual wires-visual" aria-hidden="true">
<span class="jw jw-o"></span><span class="jw jw-b"></span><span class="jw jw-g"></span>
</div>
<h3>Jumper wires, M-M</h3>
<p class="bom-ref">JW-MM-150 · 150 mm</p>
<p class="bom-qty">×4</p>
<p class="bom-note">One per connection in the table below.</p>
</article>
</div>
<p class="bench-tip"><strong>Bench tip —</strong> no multimeter needed for this lesson, but keep the board unplugged from USB while you wire. Power last, always.</p>
</section>
<!-- ============ WIRING ============ -->
<section class="section" id="wiring" aria-labelledby="wiring-title">
<div class="section-head">
<h2 id="wiring-title"><span class="step-no">02</span> Wiring diagram</h2>
<span class="section-tag">FIG-03.1</span>
</div>
<p class="section-lede">Hover (or focus) a row in the connection table to light up the matching wire on the diagram — and vice-versa.</p>
<div class="wiring-grid">
<figure class="diagram-card">
<svg id="wiring-svg" viewBox="0 0 720 380" role="img" aria-label="Wiring diagram: NV-UNO board connected to a breadboard with an LED, a 220 ohm resistor and a tactile button">
<!-- board -->
<g>
<rect x="26" y="58" width="204" height="264" rx="10" class="board-body"/>
<rect x="26" y="58" width="204" height="264" rx="10" class="board-outline"/>
<text x="128" y="100" class="svg-label svg-label-lg" text-anchor="middle">NV-UNO</text>
<text x="128" y="118" class="svg-label svg-label-sm" text-anchor="middle">R3 · UNO-COMPATIBLE</text>
<rect x="52" y="140" width="60" height="44" rx="4" class="board-chip"/>
<text x="82" y="166" class="svg-label svg-label-xs" text-anchor="middle">MCU-328</text>
<rect x="40" y="286" width="36" height="22" rx="3" class="board-usb"/>
<text x="58" y="301" class="svg-label svg-label-xs" text-anchor="middle">USB</text>
<!-- pin headers -->
<g class="pin" data-wire="w1"><rect x="216" y="126" width="22" height="14" rx="2"/><text x="212" y="137" text-anchor="end">D13</text></g>
<g class="pin" data-wire="w3"><rect x="216" y="166" width="22" height="14" rx="2"/><text x="212" y="177" text-anchor="end">D2</text></g>
<g class="pin" data-wire="w5"><rect x="216" y="246" width="22" height="14" rx="2"/><text x="212" y="257" text-anchor="end">GND</text></g>
</g>
<!-- breadboard -->
<g>
<rect x="300" y="40" width="392" height="300" rx="10" class="bb-body"/>
<rect x="300" y="40" width="392" height="300" rx="10" class="bb-outline"/>
<!-- rails -->
<line x1="316" y1="316" x2="676" y2="316" class="bb-rail bb-rail-neg"/>
<text x="684" y="320" class="svg-label svg-label-xs">−</text>
<line x1="316" y1="300" x2="676" y2="300" class="bb-rail bb-rail-pos"/>
<text x="684" y="304" class="svg-label svg-label-xs">+</text>
<!-- hole grid -->
<g class="bb-holes" aria-hidden="true">
<!-- rows a-e -->
<g id="holes"></g>
</g>
<!-- center gap -->
<line x1="316" y1="170" x2="676" y2="170" class="bb-gap"/>
<text x="322" y="64" class="svg-label svg-label-xs">1</text>
<text x="660" y="64" class="svg-label svg-label-xs">30</text>
</g>
<!-- components on breadboard -->
<!-- resistor between rows -->
<g class="cmp" data-wire="w1">
<line x1="400" y1="96" x2="400" y2="150" class="cmp-lead"/>
<rect x="392" y="104" width="16" height="36" rx="5" class="res-svg"/>
<rect x="393.5" y="110" width="13" height="4" class="rb rb-red"/>
<rect x="393.5" y="117" width="13" height="4" class="rb rb-red"/>
<rect x="393.5" y="124" width="13" height="4" class="rb rb-brown"/>
<rect x="393.5" y="131" width="13" height="4" class="rb rb-gold"/>
<text x="416" y="126" class="svg-label svg-label-xs">R1 220Ω</text>
</g>
<!-- LED -->
<g class="cmp" data-wire="w2">
<line x1="400" y1="186" x2="400" y2="214" class="cmp-lead"/>
<line x1="428" y1="186" x2="428" y2="214" class="cmp-lead"/>
<circle cx="414" cy="224" r="14" class="led-svg"/>
<circle cx="414" cy="224" r="7" class="led-core"/>
<text x="436" y="228" class="svg-label svg-label-xs">LED1</text>
<text x="392" y="252" class="svg-label svg-label-xs">A</text>
<text x="424" y="252" class="svg-label svg-label-xs">K</text>
</g>
<!-- button -->
<g class="cmp" data-wire="w3">
<rect x="536" y="146" width="48" height="48" rx="6" class="sw-svg"/>
<circle cx="560" cy="170" r="12" class="sw-cap"/>
<text x="592" y="174" class="svg-label svg-label-xs">SW1</text>
</g>
<!-- wires -->
<path id="p-w1" class="wire wire-orange" data-wire="w1" d="M238 133 C 300 133, 330 96, 400 96" />
<path id="p-w2" class="wire wire-gray" data-wire="w2" d="M428 214 C 428 268, 452 316, 470 316" />
<path id="p-w3" class="wire wire-blue" data-wire="w3" d="M238 173 C 300 173, 470 146, 540 150" />
<path id="p-w4" class="wire wire-gray" data-wire="w4" d="M580 190 C 600 240, 606 300, 620 316" />
<path id="p-w5" class="wire wire-black" data-wire="w5" d="M238 253 C 290 253, 300 316, 340 316" />
<!-- endpoint dots -->
<g class="dots" aria-hidden="true">
<circle cx="238" cy="133" r="4" class="dot-o"/><circle cx="400" cy="96" r="4" class="dot-o"/>
<circle cx="428" cy="214" r="4" class="dot-g"/><circle cx="470" cy="316" r="4" class="dot-g"/>
<circle cx="238" cy="173" r="4" class="dot-b"/><circle cx="540" cy="150" r="4" class="dot-b"/>
<circle cx="580" cy="190" r="4" class="dot-g"/><circle cx="620" cy="316" r="4" class="dot-g"/>
<circle cx="238" cy="253" r="4" class="dot-k"/><circle cx="340" cy="316" r="4" class="dot-k"/>
</g>
</svg>
<figcaption>FIG-03.1 — Button uses the internal pull-up, so no external resistor is needed on SW1.</figcaption>
</figure>
<div class="conn-card">
<h3 class="conn-title">Connection table</h3>
<table class="conn-table" aria-describedby="wiring-title">
<thead>
<tr><th scope="col">#</th><th scope="col">From</th><th scope="col">To</th><th scope="col">Wire</th></tr>
</thead>
<tbody>
<tr class="conn-row" data-wire="w1" tabindex="0">
<td class="mono">W1</td>
<td><span class="mono">D13</span> (board)</td>
<td><span class="mono">R1</span> 220 Ω → LED1 anode</td>
<td><span class="swatch sw-orange" aria-hidden="true"></span>orange</td>
</tr>
<tr class="conn-row" data-wire="w2" tabindex="0">
<td class="mono">W2</td>
<td><span class="mono">LED1</span> cathode (K)</td>
<td><span class="mono">GND</span> rail (−)</td>
<td><span class="swatch sw-gray" aria-hidden="true"></span>gray</td>
</tr>
<tr class="conn-row" data-wire="w3" tabindex="0">
<td class="mono">W3</td>
<td><span class="mono">D2</span> (board)</td>
<td><span class="mono">SW1</span> pin 1</td>
<td><span class="swatch sw-blue" aria-hidden="true"></span>blue</td>
</tr>
<tr class="conn-row" data-wire="w4" tabindex="0">
<td class="mono">W4</td>
<td><span class="mono">SW1</span> pin 2</td>
<td><span class="mono">GND</span> rail (−)</td>
<td><span class="swatch sw-gray" aria-hidden="true"></span>gray</td>
</tr>
<tr class="conn-row" data-wire="w5" tabindex="0">
<td class="mono">W5</td>
<td><span class="mono">GND</span> (board)</td>
<td><span class="mono">GND</span> rail (−)</td>
<td><span class="swatch sw-black" aria-hidden="true"></span>black</td>
</tr>
</tbody>
</table>
<p class="conn-note">Polarity check: the LED's flat side / short leg (cathode, K) always points toward ground.</p>
</div>
</div>
</section>
<!-- ============ CODE ============ -->
<section class="section" id="code" aria-labelledby="code-title">
<div class="section-head">
<h2 id="code-title"><span class="step-no">03</span> The sketch</h2>
<span class="section-tag">SKETCH-03.INO</span>
</div>
<p class="section-lede">Two functions run everything: <code>setup()</code> once at power-on, then <code>loop()</code> forever. Callouts below the editor explain both.</p>
<div class="code-card">
<div class="code-bar">
<span class="code-dots" aria-hidden="true"><i></i><i></i><i></i></span>
<span class="code-file">blink_button.ino</span>
<button type="button" class="copy-btn" id="copy-btn" aria-label="Copy sketch to clipboard">
<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><rect x="5" y="5" width="9" height="9" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
<span>Copy</span>
</button>
</div>
<pre class="code-body" id="code-body" tabindex="0"><code><span class="cl"><span class="tk-cm">// Lesson 03 — Blink + Button</span></span>
<span class="cl"><span class="tk-cm">// Board: NV-UNO R3 (UNO-compatible) · IDE 2.x</span></span>
<span class="cl"></span>
<span class="cl"><span class="tk-kw">const</span> <span class="tk-type">int</span> LED_PIN <span class="tk-op">=</span> <span class="tk-num">13</span>; <span class="tk-cm">// through R1 (220Ω)</span></span>
<span class="cl"><span class="tk-kw">const</span> <span class="tk-type">int</span> BTN_PIN <span class="tk-op">=</span> <span class="tk-num">2</span>; <span class="tk-cm">// tactile button SW1</span></span>
<span class="cl"></span>
<span class="cl mark-setup"><span class="tk-type">void</span> <span class="tk-fn">setup</span>() {</span>
<span class="cl mark-setup"> <span class="tk-fn">pinMode</span>(LED_PIN, <span class="tk-const">OUTPUT</span>);</span>
<span class="cl mark-setup"> <span class="tk-fn">pinMode</span>(BTN_PIN, <span class="tk-const">INPUT_PULLUP</span>); <span class="tk-cm">// no external resistor!</span></span>
<span class="cl mark-setup"> Serial.<span class="tk-fn">begin</span>(<span class="tk-num">9600</span>);</span>
<span class="cl mark-setup">}</span>
<span class="cl"></span>
<span class="cl mark-loop"><span class="tk-type">void</span> <span class="tk-fn">loop</span>() {</span>
<span class="cl mark-loop"> <span class="tk-cm">// pull-up means: pressed = LOW, released = HIGH</span></span>
<span class="cl mark-loop"> <span class="tk-kw">if</span> (<span class="tk-fn">digitalRead</span>(BTN_PIN) <span class="tk-op">==</span> <span class="tk-const">LOW</span>) {</span>
<span class="cl mark-loop"> <span class="tk-fn">digitalWrite</span>(LED_PIN, <span class="tk-const">HIGH</span>);</span>
<span class="cl mark-loop"> Serial.<span class="tk-fn">println</span>(<span class="tk-str">"SW1 pressed"</span>);</span>
<span class="cl mark-loop"> } <span class="tk-kw">else</span> {</span>
<span class="cl mark-loop"> <span class="tk-fn">digitalWrite</span>(LED_PIN, <span class="tk-const">LOW</span>);</span>
<span class="cl mark-loop"> }</span>
<span class="cl mark-loop"> <span class="tk-fn">delay</span>(<span class="tk-num">10</span>); <span class="tk-cm">// crude debounce — Lesson 04 does it properly</span></span>
<span class="cl mark-loop">}</span></code></pre>
</div>
<div class="callout-grid">
<div class="callout callout-setup">
<span class="callout-ref">L07–L11 · setup()</span>
<p>Runs <strong>once</strong> when the board powers up or resets. Here we declare pin directions and open the serial port. <code>INPUT_PULLUP</code> switches on an internal ~20 kΩ resistor so the pin idles at HIGH instead of floating randomly.</p>
</div>
<div class="callout callout-loop">
<span class="callout-ref">L13–L23 · loop()</span>
<p>Repeats <strong>forever</strong>, thousands of times per second. Because of the pull-up, a pressed button reads <code>LOW</code> — the logic looks inverted but is completely normal. The <code>delay(10)</code> softens contact bounce for now.</p>
</div>
</div>
</section>
<!-- ============ UPLOAD & TEST ============ -->
<section class="section" aria-labelledby="upload-title">
<div class="section-head">
<h2 id="upload-title"><span class="step-no">04</span> Upload & test</h2>
<span class="section-tag">QC-PASS</span>
</div>
<div class="check-card">
<div class="check-progress" aria-hidden="true">
<div class="check-progress-bar" id="check-bar"></div>
</div>
<p class="check-count" id="check-count">0 / 6 steps done</p>
<ul class="checklist" id="checklist">
<li><label><input type="checkbox" /> <span class="ck" aria-hidden="true"></span><span class="ck-text">Double-check LED polarity — flat side toward the GND rail</span></label></li>
<li><label><input type="checkbox" /> <span class="ck" aria-hidden="true"></span><span class="ck-text">Connect the NV-UNO over USB; the green <span class="mono">PWR</span> LED should light</span></label></li>
<li><label><input type="checkbox" /> <span class="ck" aria-hidden="true"></span><span class="ck-text">Select <span class="mono">Tools → Board → NV-UNO R3</span> and the right serial port</span></label></li>
<li><label><input type="checkbox" /> <span class="ck" aria-hidden="true"></span><span class="ck-text">Hit Upload — the <span class="mono">TX/RX</span> LEDs flicker while flashing</span></label></li>
<li><label><input type="checkbox" /> <span class="ck" aria-hidden="true"></span><span class="ck-text">Press SW1 → LED1 lights; release → it goes dark</span></label></li>
<li><label><input type="checkbox" /> <span class="ck" aria-hidden="true"></span><span class="ck-text">Open Serial Monitor at <span class="mono">9600 baud</span> — see "SW1 pressed"</span></label></li>
</ul>
</div>
</section>
<!-- ============ TROUBLESHOOTING ============ -->
<section class="section" aria-labelledby="ts-title">
<div class="section-head">
<h2 id="ts-title"><span class="step-no">05</span> Troubleshooting</h2>
<span class="section-tag">FAQ-L03</span>
</div>
<div class="accordion" id="accordion">
<div class="acc-item">
<h3 class="acc-heading">
<button type="button" class="acc-btn" aria-expanded="false" aria-controls="acc-p1" id="acc-b1">
<span class="acc-code">TS-01</span> LED never lights up
<span class="acc-chev" aria-hidden="true"></span>
</button>
</h3>
<div class="acc-panel" id="acc-p1" role="region" aria-labelledby="acc-b1" hidden>
<ul>
<li><strong>Polarity first.</strong> Flip the LED — 9 times out of 10 it's in backwards. It won't be damaged.</li>
<li>Confirm R1 and the LED anode share the <em>same breadboard row</em> (the column rule).</li>
<li>Swap wire W1 to pin <span class="mono">D13</span> — a neighbor pin off-by-one is a classic.</li>
</ul>
</div>
</div>
<div class="acc-item">
<h3 class="acc-heading">
<button type="button" class="acc-btn" aria-expanded="false" aria-controls="acc-p2" id="acc-b2">
<span class="acc-code">TS-02</span> LED is always on, button does nothing
<span class="acc-chev" aria-hidden="true"></span>
</button>
</h3>
<div class="acc-panel" id="acc-p2" role="region" aria-labelledby="acc-b2" hidden>
<ul>
<li>SW1 is probably rotated 90°. The 4 legs bridge in pairs — rotate it so pins 1/2 sit on opposite sides of the center gap.</li>
<li>Verify the sketch uses <span class="mono">INPUT_PULLUP</span>, not plain <span class="mono">INPUT</span>. A floating pin reads garbage.</li>
</ul>
</div>
</div>
<div class="acc-item">
<h3 class="acc-heading">
<button type="button" class="acc-btn" aria-expanded="false" aria-controls="acc-p3" id="acc-b3">
<span class="acc-code">TS-03</span> Serial monitor prints dozens of lines per press
<span class="acc-chev" aria-hidden="true"></span>
</button>
</h3>
<div class="acc-panel" id="acc-p3" role="region" aria-labelledby="acc-b3" hidden>
<ul>
<li>That's <strong>contact bounce</strong> — the metal contacts chatter for a few milliseconds. <span class="mono">delay(10)</span> tames most of it.</li>
<li>Lesson 04 replaces the delay with a proper <span class="mono">millis()</span>-based debounce that never blocks the loop.</li>
</ul>
</div>
</div>
<div class="acc-item">
<h3 class="acc-heading">
<button type="button" class="acc-btn" aria-expanded="false" aria-controls="acc-p4" id="acc-b4">
<span class="acc-code">TS-04</span> Upload fails: "port not found"
<span class="acc-chev" aria-hidden="true"></span>
</button>
</h3>
<div class="acc-panel" id="acc-p4" role="region" aria-labelledby="acc-b4" hidden>
<ul>
<li>Try a different USB cable — charge-only cables have no data lines and are the #1 culprit.</li>
<li>On clones, install the <span class="mono">CH340</span> USB-serial driver, then reopen the IDE.</li>
</ul>
</div>
</div>
</div>
</section>
<!-- ============ NEXT LESSONS ============ -->
<section class="section next-section" aria-labelledby="next-title">
<div class="section-head">
<h2 id="next-title"><span class="step-no">→</span> Keep building</h2>
<span class="section-tag">TRACK-E</span>
</div>
<div class="next-grid">
<a href="#" class="next-card">
<span class="next-ref">LESSON 04</span>
<h3>Debounce like a pro</h3>
<p>Replace <code>delay()</code> with <code>millis()</code> timing and toggle the LED with a single clean click.</p>
<span class="next-meta">⏱ 40 min · Difficulty 2/4</span>
</a>
<a href="#" class="next-card">
<span class="next-ref">LESSON 05</span>
<h3>Fade with PWM</h3>
<p>Meet <code>analogWrite()</code> — breathe your LED up and down using pulse-width modulation on pin D9.</p>
<span class="next-meta">⏱ 30 min · Difficulty 2/4</span>
</a>
<a href="#" class="next-card">
<span class="next-ref">LESSON 06</span>
<h3>Read a potentiometer</h3>
<p>Your first analog input: map a knob's 0–1023 reading to LED brightness in ten lines of code.</p>
<span class="next-meta">⏱ 45 min · Difficulty 3/4</span>
</a>
</div>
<p class="footer-note mono">MAKER BENCH · ELECTRONICS TRACK · SHEET 3 OF 12 · REV C</p>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Arduino Tutorial
A full maker-workshop lesson page for a first input circuit: “Blink + Button” on a fictional NV-UNO R3 board. The blueprint-grid hero carries a board badge, difficulty pips, bench time, and a “what you’ll learn” card with mono lesson refs. Below it, a bill-of-materials grid renders each part with a small CSS graphic — including a 220 Ω resistor with its red-red-brown-gold color bands — plus part numbers in JetBrains Mono.
The centerpiece is the wiring section: an SVG drawing of the microcontroller, breadboard (with a generated hole grid), LED, resistor, and tactile button sits next to a connection table. Hovering or focusing a table row dims everything else in the diagram and glows the matching wire, pin, and component — and hovering a wire highlights its row in return. The sketch is shown in a hand-rolled syntax-highlighted code block with CSS-counter line numbers, marked setup()/loop() regions, a clipboard copy button with fallback, and two callout cards explaining each function.
An “Upload & test” checklist animates a progress bar and fires a toast on QC pass, a single-open troubleshooting accordion covers dead LEDs, rotated buttons, contact bounce, and missing serial ports, and three next-lesson cards close out the page.
Illustrative UI only — always follow real safety guidance when working with tools, electronics, or repairs.