DIY — Microcontroller Pinout Card
Interactive pinout reference card for a fictional Nano-style dev board, drawn as a blueprint-navy SVG top view with 30 clickable, keyboard-selectable pins. Selecting a pin opens a detail panel with capability chips (Digital, PWM, Analog, I2C, SPI, UART, Power), mono voltage and current ratings, and a copyable usage snippet. Category filter chips tint matching pads and dim the rest, hover tooltips preview each pin, and a compare strip holds two pins side by side beneath a matching color legend.
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;
--cat-power: #d4503e;
--cat-digital: #ff6b35;
--cat-pwm: #d98a2b;
--cat-analog: #2f9e6f;
--cat-comm: #4f8fd0;
--shadow-1: 0 1px 2px rgba(28, 39, 51, 0.06), 0 6px 18px rgba(28, 39, 51, 0.07);
--shadow-2: 0 2px 6px rgba(28, 39, 51, 0.1), 0 14px 34px rgba(28, 39, 51, 0.12);
}
*,
*::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,
.mono-badge {
font-family: "JetBrains Mono", ui-monospace, monospace;
}
.page {
max-width: 1060px;
margin: 0 auto;
padding: 28px 20px 64px;
}
/* ============ HERO ============ */
.hero {
border-radius: var(--r-lg);
overflow: hidden;
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;
box-shadow: var(--shadow-2);
border: 1px solid var(--blueprint-d);
}
.hero__inner {
padding: 40px 40px 34px;
}
.hero__meta {
display: flex;
gap: 8px;
margin-bottom: 18px;
}
.mono-badge {
display: inline-block;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.12em;
color: var(--kraft);
border: 1px solid rgba(217, 199, 167, 0.45);
padding: 4px 10px;
border-radius: var(--r-sm);
}
.mono-badge--orange {
color: #ffb08e;
border-color: rgba(255, 107, 53, 0.55);
background: rgba(255, 107, 53, 0.12);
}
.hero__title {
margin: 0 0 12px;
color: var(--white);
font-weight: 800;
font-size: clamp(28px, 4.6vw, 44px);
line-height: 1.08;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.hero__title span {
color: var(--orange);
font-size: 0.62em;
letter-spacing: 0.14em;
}
.hero__sub {
margin: 0;
max-width: 54ch;
color: var(--kraft-l);
font-size: 15px;
}
.hero__specs {
margin-top: 26px;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.spec {
display: flex;
flex-direction: column;
gap: 2px;
padding: 8px 14px;
border: 1px dashed rgba(217, 199, 167, 0.4);
border-radius: var(--r-sm);
min-width: 96px;
}
.spec__k {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 10px;
letter-spacing: 0.16em;
color: rgba(217, 199, 167, 0.75);
}
.spec__v {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 13px;
font-weight: 700;
color: var(--white);
}
/* ============ FILTERS ============ */
.filters {
margin: 26px 0 18px;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.filters__label {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.16em;
color: var(--muted);
}
.filters__chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.fchip {
font-family: "Inter", system-ui, sans-serif;
font-size: 13px;
font-weight: 600;
color: var(--ink-2);
background: var(--white);
border: 1px solid var(--line-2);
border-radius: 999px;
padding: 7px 14px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 7px;
transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease,
border-color 0.12s ease, color 0.12s ease;
}
.fchip i {
width: 9px;
height: 9px;
border-radius: 50%;
display: inline-block;
}
.fchip--power i { background: var(--cat-power); }
.fchip--digital i { background: var(--cat-digital); }
.fchip--analog i { background: var(--cat-analog); }
.fchip--comm i { background: var(--cat-comm); }
.fchip:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-1);
border-color: var(--ink-2);
}
.fchip:focus-visible {
outline: 3px solid rgba(255, 107, 53, 0.5);
outline-offset: 2px;
}
.fchip.is-active {
background: var(--ink);
border-color: var(--ink);
color: var(--white);
}
.fchip.is-active i {
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.35);
}
.filters__count {
margin-left: auto;
font-size: 12px;
color: var(--muted);
letter-spacing: 0.06em;
}
/* ============ WORKBENCH LAYOUT ============ */
.workbench {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr);
gap: 18px;
align-items: start;
}
/* --- board card --- */
.board-card {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow-1);
overflow: hidden;
}
.board-card__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 18px;
border-bottom: 1px dashed var(--line-2);
background: var(--kraft-l);
}
.board-card__part {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.1em;
color: var(--ink-2);
}
.board-card__hint {
font-size: 12px;
color: var(--muted);
}
.board-wrap {
position: relative;
padding: 22px 14px;
display: flex;
justify-content: center;
background:
linear-gradient(rgba(28, 39, 51, 0.045) 1px, transparent 1px),
linear-gradient(90deg, rgba(28, 39, 51, 0.045) 1px, transparent 1px),
var(--kraft-l);
background-size: 16px 16px, 16px 16px, auto;
}
#boardSvg {
width: 100%;
max-width: 380px;
height: auto;
display: block;
}
/* SVG pin interactivity */
.pin {
cursor: pointer;
}
.pin .pin-hit {
fill: transparent;
}
.pin .pad {
transition: fill 0.15s ease, opacity 0.15s ease;
}
.pin .pad-ring {
fill: none;
stroke: transparent;
stroke-width: 3;
transition: stroke 0.15s ease;
}
.pin:hover .pad-ring,
.pin:focus-visible .pad-ring {
stroke: rgba(255, 107, 53, 0.7);
}
.pin:focus {
outline: none;
}
.pin:focus-visible .pad-ring {
stroke: var(--orange);
}
.pin.is-selected .pad-ring {
stroke: var(--orange);
}
.pin.is-dim {
opacity: 0.22;
}
.pin-label {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 11px;
font-weight: 600;
fill: var(--kraft-l);
pointer-events: none;
}
/* tooltip */
.tooltip {
position: absolute;
z-index: 5;
pointer-events: none;
background: var(--ink);
color: var(--white);
border-radius: var(--r-sm);
padding: 7px 10px;
font-size: 12px;
line-height: 1.35;
box-shadow: var(--shadow-2);
opacity: 0;
transform: translateY(4px);
transition: opacity 0.12s ease, transform 0.12s ease;
max-width: 220px;
}
.tooltip.is-on {
opacity: 1;
transform: translateY(0);
}
.tooltip strong {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-weight: 700;
display: block;
}
.tooltip span {
color: rgba(255, 255, 255, 0.75);
font-size: 11px;
}
/* legend */
.legend {
display: flex;
flex-wrap: wrap;
gap: 6px 16px;
padding: 12px 18px 16px;
border-top: 1px dashed var(--line-2);
}
.legend__item {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 12px;
font-weight: 600;
color: var(--ink-2);
}
.legend__item i {
width: 10px;
height: 10px;
border-radius: 3px;
background: var(--c);
display: inline-block;
}
/* --- detail panel --- */
.detail {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow-1);
padding: 24px;
position: sticky;
top: 16px;
min-height: 380px;
}
.detail__empty {
text-align: center;
padding: 48px 12px;
color: var(--muted);
}
.detail__empty-art {
font-size: 30px;
font-weight: 700;
color: var(--kraft);
margin-bottom: 10px;
}
.detail__empty h2 {
margin: 0 0 6px;
font-size: 17px;
color: var(--ink-2);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.detail__empty p {
margin: 0 auto;
max-width: 34ch;
font-size: 13.5px;
}
.detail__top {
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
margin-bottom: 6px;
}
.detail__id {
font-size: 12px;
font-weight: 700;
letter-spacing: 0.1em;
color: var(--white);
background: var(--blueprint);
padding: 4px 9px;
border-radius: var(--r-sm);
}
.detail__cat {
font-size: 11px;
letter-spacing: 0.12em;
color: var(--muted);
}
.detail__name {
margin: 0 0 6px;
font-size: 26px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.detail__desc {
margin: 0 0 14px;
font-size: 14px;
color: var(--ink-2);
}
.detail__chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 16px;
}
.cap-chip {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.06em;
padding: 4px 9px;
border-radius: 999px;
color: var(--white);
background: var(--muted);
}
.cap-chip[data-cap="Power"] { background: var(--cat-power); }
.cap-chip[data-cap="Digital"] { background: var(--cat-digital); }
.cap-chip[data-cap="PWM"] { background: var(--cat-pwm); }
.cap-chip[data-cap="Analog"] { background: var(--cat-analog); }
.cap-chip[data-cap="I2C"],
.cap-chip[data-cap="SPI"],
.cap-chip[data-cap="UART"] { background: var(--cat-comm); }
.detail__facts {
margin: 0 0 16px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.fact {
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 10px 12px;
background: var(--kraft-l);
}
.fact dt {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.16em;
color: var(--muted);
margin-bottom: 3px;
}
.fact dd {
margin: 0;
font-size: 15px;
font-weight: 700;
color: var(--ink);
}
.detail__snippet {
border: 1px dashed var(--line-2);
border-radius: var(--r-md);
padding: 12px 14px;
margin-bottom: 16px;
background: var(--blueprint-d);
}
.detail__snippet-label {
display: block;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.18em;
color: var(--kraft);
margin-bottom: 6px;
}
.detail__snippet pre {
margin: 0 0 10px;
font-size: 12.5px;
line-height: 1.55;
color: #cfe4f7;
white-space: pre-wrap;
word-break: break-word;
}
/* buttons */
.btn {
font-family: "Inter", system-ui, sans-serif;
font-size: 14px;
font-weight: 700;
border-radius: var(--r-md);
border: 1px solid transparent;
padding: 11px 18px;
cursor: pointer;
transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
width: 100%;
}
.btn:focus-visible {
outline: 3px solid rgba(255, 107, 53, 0.5);
outline-offset: 2px;
}
.btn--orange {
background: var(--orange);
color: var(--white);
letter-spacing: 0.03em;
box-shadow: 0 4px 12px rgba(255, 107, 53, 0.35);
}
.btn--orange:hover {
background: var(--orange-d);
transform: translateY(-1px);
}
.btn--orange:active {
transform: translateY(0);
}
.btn--ghost {
background: transparent;
color: var(--ink-2);
border-color: var(--line-2);
width: auto;
}
.btn--ghost:hover {
background: rgba(28, 39, 51, 0.05);
}
.btn--sm {
font-size: 12px;
padding: 6px 12px;
border-radius: var(--r-sm);
}
.detail__snippet .btn--ghost {
color: var(--kraft);
border-color: rgba(217, 199, 167, 0.4);
}
.detail__snippet .btn--ghost:hover {
background: rgba(217, 199, 167, 0.12);
}
/* ============ COMPARE STRIP ============ */
.compare {
margin-top: 22px;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow-1);
padding: 20px 22px 24px;
}
.compare__head {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px 14px;
margin-bottom: 16px;
}
.compare__title {
margin: 0;
font-size: 15px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.12em;
}
.compare__hint {
font-size: 12.5px;
color: var(--muted);
}
.compare__head .btn {
margin-left: auto;
}
.compare__slots {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 14px;
align-items: stretch;
}
.compare__vs {
align-self: center;
font-size: 13px;
font-weight: 700;
color: var(--muted);
letter-spacing: 0.14em;
}
.cslot {
border: 2px dashed var(--line-2);
border-radius: var(--r-md);
min-height: 150px;
padding: 14px 16px;
display: flex;
flex-direction: column;
justify-content: center;
transition: border-color 0.15s ease, background 0.15s ease;
}
.cslot.is-filled {
border-style: solid;
border-color: var(--blueprint);
background: var(--kraft-l);
justify-content: flex-start;
animation: slot-pop 0.25s ease;
}
@keyframes slot-pop {
0% { transform: scale(0.97); opacity: 0.5; }
100% { transform: scale(1); opacity: 1; }
}
.cslot__empty {
text-align: center;
font-size: 12px;
letter-spacing: 0.14em;
color: var(--muted);
}
.cslot__head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.cslot__name {
font-size: 17px;
font-weight: 800;
text-transform: uppercase;
}
.cslot__id {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 10.5px;
font-weight: 700;
color: var(--white);
background: var(--blueprint);
padding: 2px 7px;
border-radius: 4px;
letter-spacing: 0.08em;
}
.cslot__chips {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin-bottom: 10px;
}
.cslot__chips .cap-chip {
font-size: 10px;
padding: 3px 8px;
}
.cslot__row {
display: flex;
justify-content: space-between;
gap: 10px;
font-size: 12.5px;
padding: 4px 0;
border-top: 1px dashed var(--line);
}
.cslot__row .k {
color: var(--muted);
font-weight: 600;
letter-spacing: 0.06em;
font-size: 11px;
text-transform: uppercase;
}
.cslot__row .v {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-weight: 700;
}
/* ============ FOOT / TOAST ============ */
.foot {
margin-top: 26px;
text-align: center;
font-size: 10.5px;
letter-spacing: 0.16em;
color: var(--muted);
}
.toast {
position: fixed;
left: 50%;
bottom: 24px;
transform: translate(-50%, 16px);
background: var(--ink);
color: var(--white);
font-size: 13px;
font-weight: 600;
padding: 10px 18px;
border-radius: 999px;
box-shadow: var(--shadow-2);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
z-index: 50;
}
.toast.is-on {
opacity: 1;
transform: translate(-50%, 0);
}
/* ============ RESPONSIVE ============ */
@media (max-width: 880px) {
.workbench {
grid-template-columns: 1fr;
}
.detail {
position: static;
min-height: 0;
}
}
@media (max-width: 520px) {
.page {
padding: 16px 12px 48px;
}
.hero__inner {
padding: 26px 22px 24px;
}
.hero__specs {
gap: 8px;
}
.spec {
min-width: calc(50% - 8px);
flex: 1 1 auto;
}
.filters__count {
margin-left: 0;
width: 100%;
}
.detail {
padding: 18px;
}
.detail__facts {
grid-template-columns: 1fr;
}
.compare__slots {
grid-template-columns: 1fr;
}
.compare__vs {
justify-self: center;
}
.compare__head .btn {
margin-left: 0;
}
}/* =========================================================
Forge Nano MK-1 — interactive pinout card
Vanilla JS, no dependencies.
========================================================= */
(function () {
"use strict";
/* ---------- pin data ---------- */
// cats: power | digital | analog | comm (a pin can belong to several)
// primary: category used for pad color
var PINS = [
// LEFT SIDE — top to bottom
{ id: "L01", name: "TX1", label: "TX1", side: "L", primary: "comm", cats: ["digital", "comm"], caps: ["Digital", "UART"], volt: "3.3 V logic", current: "12 mA", desc: "Hardware UART transmit line. Shared with the USB bridge during flashing.", snip: "Serial1.begin(115200);\nSerial1.println(\"hello bench\");" },
{ id: "L02", name: "RX0", label: "RX0", side: "L", primary: "comm", cats: ["digital", "comm"], caps: ["Digital", "UART"], volt: "3.3 V logic", current: "12 mA", desc: "Hardware UART receive line. 5 V-tolerant through the input clamp.", snip: "if (Serial1.available()) {\n char c = Serial1.read();\n}" },
{ id: "L03", name: "RST", label: "RST", side: "L", primary: "power", cats: ["power"], caps: ["Power"], volt: "3.3 V, active LOW", current: "—", desc: "Pull low for 2 ms to reset the SF-32 core. Tied to the bench reset button.", snip: "// Hardware only — pulse LOW to reset\n// (no sketch code required)" },
{ id: "L04", name: "GND", label: "GND", side: "L", primary: "power", cats: ["power"], caps: ["Power"], volt: "0 V", current: "return path", desc: "Common ground. Star-ground your sensors here to keep analog reads quiet.", snip: "// Tie all module GNDs together\n// before powering anything up" },
{ id: "L05", name: "D2", label: "D2", side: "L", primary: "digital", cats: ["digital"], caps: ["Digital"], volt: "3.3 V logic", current: "12 mA", desc: "General-purpose I/O with external interrupt INT0. Great for encoders.", snip: "attachInterrupt(digitalPinToInterrupt(2),\n onTick, RISING);" },
{ id: "L06", name: "D3", label: "D3 ~", side: "L", primary: "digital", cats: ["digital"], caps: ["Digital", "PWM"], volt: "3.3 V logic", current: "12 mA", desc: "PWM-capable I/O on timer T2 plus interrupt INT1. Dim LEDs or drive a servo.", snip: "analogWrite(3, 128); // 50% duty" },
{ id: "L07", name: "D4", label: "D4", side: "L", primary: "digital", cats: ["digital"], caps: ["Digital"], volt: "3.3 V logic", current: "12 mA", desc: "Plain digital I/O. Boot-safe: floats high-Z during reset.", snip: "pinMode(4, INPUT_PULLUP);\nbool pressed = !digitalRead(4);" },
{ id: "L08", name: "D5", label: "D5 ~", side: "L", primary: "digital", cats: ["digital"], caps: ["Digital", "PWM"], volt: "3.3 V logic", current: "12 mA", desc: "High-frequency PWM on timer T0 — the go-to pin for motor drivers.", snip: "analogWriteFreq(20000);\nanalogWrite(5, 200);" },
{ id: "L09", name: "D6", label: "D6 ~", side: "L", primary: "digital", cats: ["digital"], caps: ["Digital", "PWM"], volt: "3.3 V logic", current: "12 mA", desc: "PWM output paired with D5 on timer T0. Good for the second motor channel.", snip: "analogWrite(6, 90); // channel B" },
{ id: "L10", name: "D7", label: "D7", side: "L", primary: "digital", cats: ["digital"], caps: ["Digital"], volt: "3.3 V logic", current: "12 mA", desc: "Digital I/O commonly used as a chip-enable for radio breakouts.", snip: "digitalWrite(7, HIGH); // enable radio" },
{ id: "L11", name: "D8", label: "D8", side: "L", primary: "digital", cats: ["digital"], caps: ["Digital"], volt: "3.3 V logic", current: "12 mA", desc: "Digital I/O with input-capture on timer T1 — ideal for reading RC pulses.", snip: "unsigned long t =\n pulseIn(8, HIGH, 25000);" },
{ id: "L12", name: "D9", label: "D9 ~", side: "L", primary: "digital", cats: ["digital"], caps: ["Digital", "PWM"], volt: "3.3 V logic", current: "12 mA", desc: "16-bit PWM on timer T1 — the smoothest servo pin on the board.", snip: "servo.attach(9);\nservo.write(90);" },
{ id: "L13", name: "D10", label: "D10 ~", side: "L", primary: "comm", cats: ["digital", "comm"], caps: ["Digital", "PWM", "SPI"], volt: "3.3 V logic", current: "12 mA", desc: "SPI chip-select (CS) with 16-bit PWM as a bonus. Keep it OUTPUT in SPI master mode.", snip: "pinMode(10, OUTPUT);\ndigitalWrite(10, LOW); // select" },
{ id: "L14", name: "D11", label: "D11 ~", side: "L", primary: "comm", cats: ["digital", "comm"], caps: ["Digital", "PWM", "SPI"], volt: "3.3 V logic", current: "12 mA", desc: "SPI MOSI — data out to displays, SD cards and flash chips.", snip: "SPI.begin();\nSPI.transfer(0x9F); // JEDEC ID" },
{ id: "L15", name: "D12", label: "D12", side: "L", primary: "comm", cats: ["digital", "comm"], caps: ["Digital", "SPI"], volt: "3.3 V logic", current: "12 mA", desc: "SPI MISO — data back from the peripheral. Input by default in master mode.", snip: "byte id = SPI.transfer(0x00);" },
// RIGHT SIDE — top to bottom
{ id: "R01", name: "D13", label: "D13", side: "R", primary: "comm", cats: ["digital", "comm"], caps: ["Digital", "SPI"], volt: "3.3 V logic", current: "12 mA", desc: "SPI SCK, wired to the onboard status LED. Blinks during SPI bursts — free debug light.", snip: "pinMode(13, OUTPUT);\ndigitalWrite(13, HIGH); // LED on" },
{ id: "R02", name: "3V3", label: "3V3", side: "R", primary: "power", cats: ["power"], caps: ["Power"], volt: "3.3 V out", current: "400 mA max", desc: "Regulated 3.3 V rail from the onboard LDO. Powers most sensor breakouts directly.", snip: "// Sensor VCC → 3V3\n// Sensor GND → GND" },
{ id: "R03", name: "AREF", label: "AREF", side: "R", primary: "analog", cats: ["analog"], caps: ["Analog"], volt: "0 – 3.3 V in", current: "—", desc: "External ADC reference input. Feed it a precision 2.500 V source for tighter reads.", snip: "analogReference(EXTERNAL);\nint raw = analogRead(A0);" },
{ id: "R04", name: "A0", label: "A0", side: "R", primary: "analog", cats: ["analog", "digital"], caps: ["Analog", "Digital"], volt: "0 – 3.3 V in", current: "12 mA", desc: "12-bit ADC channel 0. The classic potentiometer / battery-divider pin.", snip: "int mv = analogRead(A0) * 3300 / 4095;" },
{ id: "R05", name: "A1", label: "A1", side: "R", primary: "analog", cats: ["analog", "digital"], caps: ["Analog", "Digital"], volt: "0 – 3.3 V in", current: "12 mA", desc: "ADC channel 1 — pairs with A0 for differential-style sensor reads.", snip: "int light = analogRead(A1);" },
{ id: "R06", name: "A2", label: "A2", side: "R", primary: "analog", cats: ["analog", "digital"], caps: ["Analog", "Digital"], volt: "0 – 3.3 V in", current: "12 mA", desc: "ADC channel 2 with a touch-sense alternate function for capacitive pads.", snip: "int touch = touchRead(A2);" },
{ id: "R07", name: "A3", label: "A3", side: "R", primary: "analog", cats: ["analog", "digital"], caps: ["Analog", "Digital"], volt: "0 – 3.3 V in", current: "12 mA", desc: "ADC channel 3. Doubles as a wake-from-sleep input on the SF-32.", snip: "int soil = analogRead(A3);" },
{ id: "R08", name: "A4", label: "A4 SDA", side: "R", primary: "comm", cats: ["analog", "comm"], caps: ["Analog", "I2C"], volt: "3.3 V logic", current: "12 mA", desc: "I2C data line (SDA), shared with ADC channel 4. Onboard 4.7 kΩ pull-up.", snip: "Wire.begin();\nWire.beginTransmission(0x3C);" },
{ id: "R09", name: "A5", label: "A5 SCL", side: "R", primary: "comm", cats: ["analog", "comm"], caps: ["Analog", "I2C"], volt: "3.3 V logic", current: "12 mA", desc: "I2C clock line (SCL), shared with ADC channel 5. 400 kHz fast-mode capable.", snip: "Wire.setClock(400000); // fast mode" },
{ id: "R10", name: "A6", label: "A6", side: "R", primary: "analog", cats: ["analog"], caps: ["Analog"], volt: "0 – 3.3 V in", current: "—", desc: "Analog-only input (no digital driver). Perfect for a quiet thermistor read.", snip: "float t = readThermC(analogRead(A6));" },
{ id: "R11", name: "A7", label: "A7", side: "R", primary: "analog", cats: ["analog"], caps: ["Analog"], volt: "0 – 3.3 V in", current: "—", desc: "Analog-only input, routed away from the switcher for the lowest noise floor.", snip: "int vbat = analogRead(A7);" },
{ id: "R12", name: "5V", label: "5V", side: "R", primary: "power", cats: ["power"], caps: ["Power"], volt: "5.0 V out", current: "800 mA max", desc: "USB-fed 5 V rail. Powers strips and servos — never feed it back while on USB.", snip: "// NeoPixel VCC → 5V\n// Add a 1000 µF cap across the rail" },
{ id: "R13", name: "RST2", label: "RST", side: "R", primary: "power", cats: ["power"], caps: ["Power"], volt: "3.3 V, active LOW", current: "—", desc: "Second reset pad, mirrored for shield layouts. Electrically tied to L03.", snip: "// Same net as pin L03 (RST)" },
{ id: "R14", name: "GND2", label: "GND", side: "R", primary: "power", cats: ["power"], caps: ["Power"], volt: "0 V", current: "return path", desc: "Ground pad on the analog side — reference your ADC dividers here.", snip: "// Divider bottom leg → this GND" },
{ id: "R15", name: "VIN", label: "VIN", side: "R", primary: "power", cats: ["power"], caps: ["Power"], volt: "6 – 16 V in", current: "1 A max", desc: "Unregulated input to the onboard buck. Feed it a battery pack or wall wart.", snip: "// 2S LiPo (7.4 V) → VIN\n// Mind the polarity marking!" },
];
var CAT_COLORS = {
power: "#d4503e",
digital: "#ff6b35",
analog: "#2f9e6f",
comm: "#4f8fd0",
};
var CAT_LABELS = {
power: "POWER",
digital: "DIGITAL I/O",
analog: "ANALOG",
comm: "COMMUNICATION",
};
/* ---------- dom refs ---------- */
var svg = document.getElementById("boardSvg");
var boardWrap = document.getElementById("boardWrap");
var tooltip = document.getElementById("tooltip");
var toastEl = document.getElementById("toast");
var filterCount = document.getElementById("filterCount");
var detailEmpty = document.getElementById("detailEmpty");
var detailBody = document.getElementById("detailBody");
var dId = document.getElementById("dId");
var dCat = document.getElementById("dCat");
var dName = document.getElementById("dName");
var dDesc = document.getElementById("dDesc");
var dChips = document.getElementById("dChips");
var dVolt = document.getElementById("dVolt");
var dCurrent = document.getElementById("dCurrent");
var dSnippet = document.getElementById("dSnippet");
var copyBtn = document.getElementById("copyBtn");
var compareBtn = document.getElementById("compareBtn");
var compareClear = document.getElementById("compareClear");
var cslotA = document.getElementById("cslotA");
var cslotB = document.getElementById("cslotB");
var state = {
filter: "all",
selected: null, // pin id
compare: [], // up to 2 pin ids
};
var toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-on");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-on");
}, 2200);
}
function byId(id) {
for (var i = 0; i < PINS.length; i++) if (PINS[i].id === id) return PINS[i];
return null;
}
/* ---------- build SVG board ---------- */
var NS = "http://www.w3.org/2000/svg";
function el(tag, attrs) {
var node = document.createElementNS(NS, tag);
for (var k in attrs) node.setAttribute(k, attrs[k]);
return node;
}
var TOP = 78; // first pin y
var PITCH = 37;
var LX = 34; // left pad x
var RX = 346; // right pad x
function buildBoard() {
// PCB body — blueprint navy with rounded corners
svg.appendChild(el("rect", { x: 14, y: 10, width: 352, height: 644, rx: 18, fill: "#0f2a4a", stroke: "#0a1e36", "stroke-width": 3 }));
// inner grid feel: subtle silkscreen frame
svg.appendChild(el("rect", { x: 26, y: 22, width: 328, height: 620, rx: 12, fill: "none", stroke: "rgba(140,180,220,0.25)", "stroke-width": 1, "stroke-dasharray": "5 5" }));
// USB-C connector at top
svg.appendChild(el("rect", { x: 150, y: 2, width: 80, height: 40, rx: 8, fill: "#9aa7b4", stroke: "#6b7885", "stroke-width": 2 }));
svg.appendChild(el("rect", { x: 162, y: 12, width: 56, height: 20, rx: 6, fill: "#3d4a57" }));
// mounting holes
[[40, 40], [340, 40], [40, 624], [340, 624]].forEach(function (p) {
svg.appendChild(el("circle", { cx: p[0], cy: p[1], r: 7, fill: "#0a1e36", stroke: "rgba(217,199,167,0.6)", "stroke-width": 2 }));
});
// MCU chip
var chipG = el("g", {});
chipG.appendChild(el("rect", { x: 128, y: 250, width: 124, height: 124, rx: 8, fill: "#0a1e36", stroke: "#16385f", "stroke-width": 2 }));
// chip legs
for (var i = 0; i < 7; i++) {
chipG.appendChild(el("rect", { x: 138 + i * 16, y: 242, width: 7, height: 8, fill: "#8fa3b8" }));
chipG.appendChild(el("rect", { x: 138 + i * 16, y: 374, width: 7, height: 8, fill: "#8fa3b8" }));
}
var chipTxt = el("text", { x: 190, y: 306, "text-anchor": "middle", class: "pin-label", "font-size": 13 });
chipTxt.textContent = "SF-32";
var chipTxt2 = el("text", { x: 190, y: 324, "text-anchor": "middle", class: "pin-label", "font-size": 10, fill: "rgba(217,199,167,0.8)" });
chipTxt2.textContent = "KESTREL";
chipG.appendChild(chipTxt);
chipG.appendChild(chipTxt2);
chipG.appendChild(el("circle", { cx: 140, cy: 262, r: 3.5, fill: "#ff6b35" })); // pin-1 dot
svg.appendChild(chipG);
// crystal + passives for flavor
svg.appendChild(el("rect", { x: 160, y: 420, width: 60, height: 24, rx: 12, fill: "#c9cfd6", stroke: "#8fa3b8", "stroke-width": 1.5 }));
var xt = el("text", { x: 190, y: 436, "text-anchor": "middle", "font-size": 9, fill: "#3d4a57", "font-family": '"JetBrains Mono", monospace', "font-weight": 700 });
xt.textContent = "16.000";
svg.appendChild(xt);
[[150, 480], [178, 480], [206, 480], [234, 480]].forEach(function (p, idx) {
svg.appendChild(el("rect", { x: p[0], y: p[1], width: 18, height: 9, rx: 2, fill: idx % 2 ? "#b78650" : "#4a5a6a" }));
});
// status LED next to D13
svg.appendChild(el("rect", { x: 96, y: 72, width: 12, height: 12, rx: 2, fill: "#ffd166", stroke: "rgba(255,255,255,0.4)" }));
var ledTxt = el("text", { x: 116, y: 82, "font-size": 8.5, class: "pin-label", fill: "rgba(217,199,167,0.7)" });
ledTxt.textContent = "LED";
svg.appendChild(ledTxt);
// silkscreen title
var t1 = el("text", { x: 190, y: 560, "text-anchor": "middle", class: "pin-label", "font-size": 14, fill: "#efe6d4" });
t1.textContent = "FORGE NANO MK-1";
var t2 = el("text", { x: 190, y: 578, "text-anchor": "middle", class: "pin-label", "font-size": 9, fill: "rgba(217,199,167,0.7)" });
t2.textContent = "TINKER & BOLT LABS · REV C";
svg.appendChild(t1);
svg.appendChild(t2);
// pins
PINS.forEach(function (pin, idx) {
var row = idx % 15;
var y = TOP + row * PITCH;
var x = pin.side === "L" ? LX : RX;
var g = el("g", {
class: "pin",
tabindex: "0",
role: "button",
"data-id": pin.id,
"aria-label": pin.name + " pin. " + pin.caps.join(", ") + ". " + pin.volt + ". Press Enter to inspect.",
});
// castellated pad
g.appendChild(el("circle", { class: "pad-ring", cx: x, cy: y, r: 12 }));
g.appendChild(el("circle", { class: "pad", cx: x, cy: y, r: 9, fill: CAT_COLORS[pin.primary], stroke: "rgba(255,255,255,0.55)", "stroke-width": 1.5 }));
g.appendChild(el("circle", { cx: x, cy: y, r: 3.2, fill: "#0a1e36" }));
// label
var label = el("text", {
class: "pin-label",
x: pin.side === "L" ? x + 18 : x - 18,
y: y + 4,
"text-anchor": pin.side === "L" ? "start" : "end",
});
label.textContent = pin.label;
g.appendChild(label);
// large invisible hit area
g.appendChild(el("rect", {
class: "pin-hit",
x: pin.side === "L" ? x - 16 : x - 64,
y: y - PITCH / 2 + 2,
width: 80,
height: PITCH - 4,
}));
svg.appendChild(g);
});
}
/* ---------- selection + detail panel ---------- */
function capChipHtml(cap) {
return '<span class="cap-chip" data-cap="' + cap + '">' + cap + "</span>";
}
function selectPin(id) {
state.selected = id;
var pin = byId(id);
if (!pin) return;
// svg highlight
var groups = svg.querySelectorAll(".pin");
groups.forEach(function (g) {
g.classList.toggle("is-selected", g.getAttribute("data-id") === id);
});
detailEmpty.hidden = true;
detailBody.hidden = false;
dId.textContent = "PIN " + pin.id;
dCat.textContent = CAT_LABELS[pin.primary];
dName.textContent = pin.name;
dDesc.textContent = pin.desc;
dChips.innerHTML = pin.caps.map(capChipHtml).join("");
dVolt.textContent = pin.volt;
dCurrent.textContent = pin.current;
dSnippet.textContent = pin.snip;
}
/* ---------- filter ---------- */
function applyFilter(filter) {
state.filter = filter;
var visible = 0;
svg.querySelectorAll(".pin").forEach(function (g) {
var pin = byId(g.getAttribute("data-id"));
var match = filter === "all" || pin.cats.indexOf(filter) !== -1;
g.classList.toggle("is-dim", !match);
if (match) visible++;
});
filterCount.textContent = visible + " / " + PINS.length + " pins";
document.querySelectorAll(".fchip").forEach(function (btn) {
var on = btn.getAttribute("data-filter") === filter;
btn.classList.toggle("is-active", on);
btn.setAttribute("aria-pressed", String(on));
});
}
/* ---------- tooltip ---------- */
function showTooltip(pin, evt) {
tooltip.innerHTML =
"<strong>" + pin.name + "</strong><span>" + pin.caps.join(" · ") + " — " + pin.volt + "</span>";
tooltip.classList.add("is-on");
tooltip.setAttribute("aria-hidden", "false");
moveTooltip(evt);
}
function moveTooltip(evt) {
var rect = boardWrap.getBoundingClientRect();
var x = evt.clientX - rect.left + 14;
var y = evt.clientY - rect.top - 10;
// keep inside wrap
var maxX = rect.width - tooltip.offsetWidth - 8;
if (x > maxX) x = evt.clientX - rect.left - tooltip.offsetWidth - 14;
tooltip.style.left = x + "px";
tooltip.style.top = y + "px";
}
function hideTooltip() {
tooltip.classList.remove("is-on");
tooltip.setAttribute("aria-hidden", "true");
}
/* ---------- compare strip ---------- */
function renderCompare() {
[cslotA, cslotB].forEach(function (slot, i) {
var id = state.compare[i];
if (!id) {
slot.classList.remove("is-filled");
slot.innerHTML =
'<div class="cslot__empty mono">SLOT ' + (i === 0 ? "A" : "B") + " — EMPTY</div>";
return;
}
var pin = byId(id);
slot.classList.add("is-filled");
slot.innerHTML =
'<div class="cslot__head"><span class="cslot__id">' + pin.id + "</span>" +
'<span class="cslot__name">' + pin.name + "</span></div>" +
'<div class="cslot__chips">' + pin.caps.map(capChipHtml).join("") + "</div>" +
'<div class="cslot__row"><span class="k">Voltage</span><span class="v">' + pin.volt + "</span></div>" +
'<div class="cslot__row"><span class="k">Max current</span><span class="v">' + pin.current + "</span></div>" +
'<div class="cslot__row"><span class="k">Category</span><span class="v">' + CAT_LABELS[pin.primary] + "</span></div>";
});
}
function addToCompare(id) {
if (state.compare.indexOf(id) !== -1) {
toast("Pin " + byId(id).name + " is already on the strip");
return;
}
if (state.compare.length >= 2) state.compare.shift();
state.compare.push(id);
renderCompare();
toast("Pinned " + byId(id).name + " to compare strip");
}
/* ---------- events ---------- */
svg.addEventListener("click", function (evt) {
var g = evt.target.closest(".pin");
if (!g) return;
selectPin(g.getAttribute("data-id"));
});
svg.addEventListener("keydown", function (evt) {
var g = evt.target.closest(".pin");
if (!g) return;
if (evt.key === "Enter" || evt.key === " ") {
evt.preventDefault();
selectPin(g.getAttribute("data-id"));
}
});
svg.addEventListener("mouseover", function (evt) {
var g = evt.target.closest(".pin");
if (!g) return;
showTooltip(byId(g.getAttribute("data-id")), evt);
});
svg.addEventListener("mousemove", function (evt) {
if (!tooltip.classList.contains("is-on")) return;
if (!evt.target.closest(".pin")) { hideTooltip(); return; }
moveTooltip(evt);
});
svg.addEventListener("mouseout", function (evt) {
var g = evt.target.closest(".pin");
if (g && !g.contains(evt.relatedTarget)) hideTooltip();
});
document.querySelectorAll(".fchip").forEach(function (btn) {
btn.addEventListener("click", function () {
applyFilter(btn.getAttribute("data-filter"));
});
});
compareBtn.addEventListener("click", function () {
if (!state.selected) return;
addToCompare(state.selected);
});
compareClear.addEventListener("click", function () {
if (!state.compare.length) { toast("Compare strip is already empty"); return; }
state.compare = [];
renderCompare();
toast("Compare strip cleared");
});
copyBtn.addEventListener("click", function () {
var text = dSnippet.textContent;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(
function () { toast("Snippet copied to clipboard"); },
function () { toast("Copy failed — select the text manually"); }
);
} else {
toast("Clipboard unavailable — select the text manually");
}
});
/* ---------- boot ---------- */
buildBoard();
applyFilter("all");
renderCompare();
selectPin("R01"); // start on D13 so the panel demos itself
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DIY — Microcontroller Pinout Card</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="page">
<!-- ============ HERO ============ -->
<header class="hero">
<div class="hero__inner">
<div class="hero__meta">
<span class="mono-badge">REF-CARD 07</span>
<span class="mono-badge mono-badge--orange">REV C</span>
</div>
<h1 class="hero__title">Forge Nano MK-1<br /><span>Pinout Reference Card</span></h1>
<p class="hero__sub">
Interactive top-view of the fictional Tinker & Bolt Labs dev board.
Click any pin — or tab to it — for capabilities, voltage and a usage snippet.
</p>
<div class="hero__specs" aria-label="Board specs">
<div class="spec"><span class="spec__k">MCU</span><span class="spec__v">SF-32 KESTREL</span></div>
<div class="spec"><span class="spec__k">LOGIC</span><span class="spec__v">3.3 V</span></div>
<div class="spec"><span class="spec__k">CLOCK</span><span class="spec__v">160 MHz</span></div>
<div class="spec"><span class="spec__k">FLASH</span><span class="spec__v">4 MB</span></div>
<div class="spec"><span class="spec__k">PINS</span><span class="spec__v">2 × 15</span></div>
</div>
</div>
</header>
<!-- ============ FILTERS ============ -->
<section class="filters" aria-label="Pin category filters">
<span class="filters__label">FILTER PINS</span>
<div class="filters__chips" role="group" aria-label="Category filter">
<button class="fchip is-active" data-filter="all" aria-pressed="true">All</button>
<button class="fchip fchip--power" data-filter="power" aria-pressed="false"><i></i>Power</button>
<button class="fchip fchip--digital" data-filter="digital" aria-pressed="false"><i></i>Digital</button>
<button class="fchip fchip--analog" data-filter="analog" aria-pressed="false"><i></i>Analog</button>
<button class="fchip fchip--comm" data-filter="comm" aria-pressed="false"><i></i>Communication</button>
</div>
<span class="filters__count mono" id="filterCount">30 / 30 pins</span>
</section>
<!-- ============ BOARD + DETAIL ============ -->
<section class="workbench">
<div class="board-card">
<div class="board-card__head">
<span class="mono board-card__part">PN TB-NANO-MK1</span>
<span class="board-card__hint">hover for tooltip · click to inspect</span>
</div>
<div class="board-wrap" id="boardWrap">
<svg id="boardSvg" viewBox="0 0 380 664" role="group" aria-label="Forge Nano MK-1 board top view, 30 selectable pins"></svg>
<div class="tooltip" id="tooltip" role="tooltip" aria-hidden="true"></div>
</div>
<div class="legend" aria-label="Color legend">
<span class="legend__item"><i style="--c:var(--cat-power)"></i>Power</span>
<span class="legend__item"><i style="--c:var(--cat-digital)"></i>Digital</span>
<span class="legend__item"><i style="--c:var(--cat-pwm)"></i>PWM</span>
<span class="legend__item"><i style="--c:var(--cat-analog)"></i>Analog</span>
<span class="legend__item"><i style="--c:var(--cat-comm)"></i>I2C / SPI / UART</span>
</div>
</div>
<aside class="detail" id="detailPanel" aria-live="polite">
<div class="detail__empty" id="detailEmpty">
<div class="detail__empty-art mono">[ ? ]</div>
<h2>No pin selected</h2>
<p>Pick a pin on the board to see its capabilities, voltage rating and a ready-to-paste snippet.</p>
</div>
<div class="detail__body" id="detailBody" hidden>
<div class="detail__top">
<span class="mono detail__id" id="dId">—</span>
<span class="detail__cat mono" id="dCat">—</span>
</div>
<h2 class="detail__name" id="dName">—</h2>
<p class="detail__desc" id="dDesc">—</p>
<div class="detail__chips" id="dChips" aria-label="Pin capabilities"></div>
<dl class="detail__facts">
<div class="fact">
<dt>VOLTAGE</dt>
<dd class="mono" id="dVolt">—</dd>
</div>
<div class="fact">
<dt>MAX CURRENT</dt>
<dd class="mono" id="dCurrent">—</dd>
</div>
</dl>
<div class="detail__snippet">
<span class="detail__snippet-label mono">USAGE</span>
<pre class="mono" id="dSnippet">—</pre>
<button class="btn btn--ghost btn--sm" id="copyBtn" type="button">Copy snippet</button>
</div>
<button class="btn btn--orange" id="compareBtn" type="button">+ Pin to compare strip</button>
</div>
</aside>
</section>
<!-- ============ COMPARE STRIP ============ -->
<section class="compare" aria-label="Pin comparison">
<div class="compare__head">
<h2 class="compare__title">Compare Strip</h2>
<span class="compare__hint">Select a pin, then “Pin to compare” — holds two at a time.</span>
<button class="btn btn--ghost btn--sm" id="compareClear" type="button">Clear</button>
</div>
<div class="compare__slots">
<div class="cslot" id="cslotA">
<div class="cslot__empty mono">SLOT A — EMPTY</div>
</div>
<div class="compare__vs mono" aria-hidden="true">VS</div>
<div class="cslot" id="cslotB">
<div class="cslot__empty mono">SLOT B — EMPTY</div>
</div>
</div>
</section>
<footer class="foot">
<span class="mono">TINKER & BOLT LABS · FICTIONAL HARDWARE · SHEET 1 / 1</span>
</footer>
</main>
<div class="toast mono" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Microcontroller Pinout Card
A workshop-style reference card for the fictional Forge Nano MK-1 by Tinker & Bolt Labs. The board is rendered as an SVG top view on blueprint navy — USB connector, SF-32 “Kestrel” MCU, crystal, silkscreen and all — with fifteen castellated pads down each side. Every pad is a real focusable control: hover shows a tooltip with the pin’s capabilities and voltage, and clicking (or pressing Enter/Space) loads the detail panel with capability chips, mono-typeset voltage and max-current facts, and a one-line usage snippet you can copy with one button.
Above the board, filter chips for Power, Digital, Analog and Communication tint the matching pads and drop the rest to low opacity, with a live “matching pins” counter. A color legend under the board maps pad colors to categories so the tinting always reads at a glance.
The compare strip at the bottom holds two pins side by side — select a pin, hit “Pin to compare”, and its name, chips, voltage and current land in a slot; a third pin rotates the oldest one out. A small toast helper confirms every action. Everything is vanilla JS: the SVG board, tooltips, filtering and compare logic are all generated and wired at runtime with no libraries.
Illustrative UI only — always follow real safety guidance when working with tools, electronics, or repairs.