DIY — Maker Unit Converter
A three-tab bench-side unit converter in the workshop blueprint style: a live two-way mm to inches converter that understands fractional inches like 3/8 and snaps to the nearest 1/64 with a drift note, an AWG wire gauge slider with diameter, cross-section and a scaling cross-section preview, and an interactive 4-band resistor SVG whose bands cycle on click or keyboard and decode to ohms with tolerance range, plus reverse lookup from an ohms input and copy-to-clipboard toasts.
MCP
Code
:root {
--blueprint: #0f2a4a;
--blueprint-d: #0a1e36;
--blueprint-l: #16385f;
--grid: rgba(140, 180, 220, 0.14);
--grid-major: rgba(140, 180, 220, 0.26);
--orange: #ff6b35;
--orange-d: #e0521f;
--orange-soft: #ffe3d6;
--kraft: #d9c7a7;
--kraft-l: #efe6d4;
--ink: #1c2733;
--ink-2: #3d4a57;
--muted: #6b7885;
--bg: #f4f1ea;
--white: #ffffff;
--line: rgba(28, 39, 51, 0.12);
--line-2: rgba(28, 39, 51, 0.2);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--r-sm: 6px;
--r-md: 12px;
--r-lg: 18px;
--shadow-sm: 0 1px 3px rgba(28, 39, 51, 0.08);
--shadow-md: 0 6px 20px rgba(28, 39, 51, 0.1);
}
*,
*::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 {
font-family: "JetBrains Mono", ui-monospace, monospace;
}
.shop {
max-width: 960px;
margin: 0 auto;
padding: 24px 20px 60px;
}
/* ============ HERO ============ */
.hero {
position: relative;
background-color: var(--blueprint);
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px),
linear-gradient(var(--grid-major) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-major) 1px, transparent 1px);
background-size: 8px 8px, 8px 8px, 80px 80px, 80px 80px;
border-radius: var(--r-lg);
padding: 40px 40px 48px;
color: var(--kraft-l);
overflow: hidden;
box-shadow: var(--shadow-md);
}
.hero-inner {
max-width: 620px;
}
.hero-badge-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 20px;
}
.mono-badge {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.08em;
padding: 4px 10px;
border-radius: var(--r-sm);
border: 1px solid rgba(217, 199, 167, 0.4);
color: var(--kraft);
background: rgba(10, 30, 54, 0.5);
}
.badge-orange {
border-color: var(--orange);
color: var(--orange);
background: rgba(255, 107, 53, 0.1);
}
.panel .mono-badge {
border-color: var(--line-2);
color: var(--ink-2);
background: var(--kraft-l);
}
.hero-title {
margin: 0 0 14px;
font-size: clamp(2.1rem, 5.5vw, 3.4rem);
font-weight: 800;
line-height: 1.05;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--white);
}
.hero-sub {
margin: 0 0 22px;
font-size: 1rem;
color: rgba(239, 230, 212, 0.85);
max-width: 54ch;
}
.hero-meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.meta-chip {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.05em;
color: var(--kraft-l);
border: 1px dashed rgba(217, 199, 167, 0.45);
border-radius: 999px;
padding: 5px 12px;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
}
.dot-ok {
background: var(--ok);
box-shadow: 0 0 8px rgba(47, 158, 111, 0.8);
}
.hero-corner {
position: absolute;
right: 20px;
bottom: 14px;
display: flex;
gap: 18px;
font-size: 10px;
letter-spacing: 0.12em;
color: rgba(217, 199, 167, 0.55);
}
/* ============ TABS ============ */
.tabbar {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin: 22px 0 18px;
}
.tab {
appearance: none;
border: 1px solid var(--line-2);
background: var(--white);
border-radius: var(--r-md);
padding: 14px 16px;
text-align: left;
cursor: pointer;
font-family: inherit;
color: var(--ink);
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto auto;
column-gap: 10px;
align-items: baseline;
transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.12s ease, background 0.15s ease;
box-shadow: var(--shadow-sm);
}
.tab:hover {
border-color: var(--orange);
transform: translateY(-1px);
}
.tab:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 2px;
}
.tab:active {
transform: translateY(0);
}
.tab-num {
grid-row: 1 / 3;
font-size: 20px;
font-weight: 700;
color: var(--muted);
transition: color 0.15s ease;
}
.tab-label {
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 13px;
}
.tab-hint {
grid-column: 2;
font-size: 12px;
color: var(--muted);
}
.tab.is-active {
background: var(--blueprint);
border-color: var(--blueprint);
color: var(--white);
box-shadow: var(--shadow-md);
}
.tab.is-active .tab-num {
color: var(--orange);
}
.tab.is-active .tab-hint {
color: var(--kraft);
}
/* ============ PANELS ============ */
.panel {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 28px;
box-shadow: var(--shadow-sm);
animation: panel-in 0.25s ease;
}
.panel:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 2px;
}
@keyframes panel-in {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: none;
}
}
.panel-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 22px;
padding-bottom: 14px;
border-bottom: 2px dashed var(--line-2);
}
.panel-title {
margin: 0;
font-size: 1.15rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.08em;
}
/* ============ LENGTH ============ */
.len-grid {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 14px;
align-items: stretch;
}
.len-card {
background: var(--kraft-l);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px;
}
.field-label {
display: block;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--ink-2);
margin-bottom: 10px;
}
.field-row {
display: flex;
gap: 8px;
align-items: center;
}
.big-input {
flex: 1;
min-width: 0;
font-size: 1.4rem;
font-weight: 600;
padding: 10px 14px;
border: 2px solid var(--line-2);
border-radius: var(--r-sm);
background: var(--white);
color: var(--ink);
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.big-input:focus {
outline: none;
border-color: var(--orange);
box-shadow: 0 0 0 3px var(--orange-soft);
}
.big-input.is-bad {
border-color: var(--danger);
}
.unit-tag {
font-size: 13px;
font-weight: 700;
background: var(--blueprint);
color: var(--kraft);
border-radius: var(--r-sm);
padding: 8px 10px;
letter-spacing: 0.06em;
}
.field-hint {
margin: 10px 0 0;
font-size: 12px;
color: var(--muted);
}
.len-swap {
display: flex;
align-items: center;
justify-content: center;
}
.swap-glyph {
width: 42px;
height: 42px;
display: grid;
place-items: center;
border-radius: 50%;
background: var(--orange);
color: var(--white);
font-size: 20px;
font-weight: 700;
box-shadow: var(--shadow-sm);
}
.len-readout {
display: flex;
flex-wrap: wrap;
gap: 20px;
align-items: center;
margin-top: 18px;
padding: 16px 18px;
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);
color: var(--white);
}
.readout-block {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 130px;
}
.readout-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--kraft);
}
.readout-value {
font-size: 1.5rem;
font-weight: 700;
color: var(--white);
}
.readout-note {
font-size: 11px;
color: rgba(239, 230, 212, 0.7);
}
/* ============ BUTTONS / CHIPS ============ */
.copy-btn,
.set-btn {
appearance: none;
font-family: inherit;
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
border: none;
border-radius: var(--r-sm);
background: var(--orange);
color: var(--white);
padding: 11px 18px;
cursor: pointer;
margin-left: auto;
transition: background 0.15s ease, transform 0.12s ease, box-shadow 0.15s ease;
box-shadow: 0 2px 0 var(--orange-d);
}
.set-btn {
margin-left: 0;
flex-shrink: 0;
}
.copy-btn:hover,
.set-btn:hover {
background: var(--orange-d);
}
.copy-btn:active,
.set-btn:active {
transform: translateY(2px);
box-shadow: 0 0 0 var(--orange-d);
}
.copy-btn:focus-visible,
.set-btn:focus-visible {
outline: 3px solid var(--blueprint);
outline-offset: 2px;
}
.chip-section {
margin-top: 22px;
}
.chip-title {
margin: 0 0 10px;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--ink-2);
}
.chip-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.chip {
appearance: none;
font-size: 12px;
font-weight: 600;
border: 1px solid var(--line-2);
border-radius: 999px;
background: var(--kraft-l);
color: var(--ink);
padding: 7px 14px;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.12s ease;
}
.chip:hover {
border-color: var(--orange);
background: var(--orange-soft);
transform: translateY(-1px);
}
.chip:active {
transform: none;
}
.chip:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 2px;
}
.note-strip {
display: flex;
gap: 12px;
align-items: flex-start;
margin-top: 22px;
padding: 14px 16px;
border: 2px dashed var(--line-2);
border-radius: var(--r-md);
background: var(--kraft-l);
}
.note-strip p {
margin: 0;
font-size: 13px;
color: var(--ink-2);
}
.note-icon {
font-size: 16px;
line-height: 1.3;
}
/* ============ WIRE ============ */
.wire-layout {
display: grid;
grid-template-columns: 1.2fr 1fr;
gap: 24px;
align-items: start;
}
.awg-current {
float: right;
color: var(--orange-d);
font-size: 14px;
}
input[type="range"] {
width: 100%;
height: 28px;
appearance: none;
background: transparent;
cursor: pointer;
margin: 4px 0 2px;
}
input[type="range"]::-webkit-slider-runnable-track {
height: 8px;
border-radius: 4px;
background: linear-gradient(90deg, var(--blueprint), var(--blueprint-l));
}
input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 24px;
height: 24px;
border-radius: 50%;
background: var(--orange);
border: 3px solid var(--white);
box-shadow: var(--shadow-sm);
margin-top: -8px;
transition: transform 0.12s ease;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.12);
}
input[type="range"]::-moz-range-track {
height: 8px;
border-radius: 4px;
background: linear-gradient(90deg, var(--blueprint), var(--blueprint-l));
}
input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--orange);
border: 3px solid var(--white);
box-shadow: var(--shadow-sm);
}
input[type="range"]:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 4px;
border-radius: 4px;
}
.slider-scale {
display: flex;
justify-content: space-between;
font-size: 10px;
color: var(--muted);
padding: 0 4px;
margin-bottom: 16px;
}
.wire-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 16px;
}
.stat-card {
background: var(--kraft-l);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 12px;
display: flex;
flex-direction: column;
gap: 4px;
}
.stat-label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.09em;
color: var(--muted);
}
.stat-value {
font-size: 1.05rem;
font-weight: 700;
color: var(--ink);
}
.wire-uses {
margin-bottom: 16px;
}
.wire-use-text {
margin: 4px 0 0;
font-size: 13.5px;
color: var(--ink-2);
}
.fine-print {
margin: 12px 0 0;
font-size: 11px;
color: var(--muted);
}
.wire-controls .copy-btn {
margin-left: 0;
}
.preview-frame {
background-color: var(--blueprint);
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px),
linear-gradient(var(--grid-major) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-major) 1px, transparent 1px);
background-size: 8px 8px, 8px 8px, 80px 80px, 80px 80px;
border-radius: var(--r-md);
padding: 16px;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.preview-caption {
font-size: 10px;
letter-spacing: 0.12em;
color: var(--kraft);
}
.wire-stage {
width: 100%;
height: 220px;
display: grid;
place-items: center;
}
.wire-disc {
width: 184px;
height: 184px;
border-radius: 50%;
background: radial-gradient(circle at 35% 32%, #f0f2f4, #b8c2cb 58%, #8d99a5);
border: 6px solid rgba(239, 230, 212, 0.25);
display: grid;
place-items: center;
transition: transform 0.25s cubic-bezier(0.34, 1.4, 0.64, 1);
will-change: transform;
}
.wire-core {
width: 62%;
height: 62%;
border-radius: 50%;
background: radial-gradient(circle at 35% 30%, #ffb08a, #d9773f 55%, #a34e1c);
}
.preview-ruler {
font-size: 14px;
font-weight: 700;
color: var(--white);
border-top: 1px dashed rgba(217, 199, 167, 0.5);
padding-top: 8px;
width: 100%;
text-align: center;
}
/* ============ RESISTOR ============ */
.res-stage {
background: var(--kraft-l);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px 18px 10px;
}
.res-svg {
display: block;
width: 100%;
height: auto;
max-width: 560px;
margin: 0 auto;
}
.band {
cursor: pointer;
stroke: rgba(28, 39, 51, 0.35);
stroke-width: 1;
transition: filter 0.12s ease;
}
.band:hover {
filter: brightness(1.15);
}
.band:focus-visible {
outline: none;
stroke: var(--orange);
stroke-width: 4;
}
.res-band-labels {
display: flex;
max-width: 560px;
margin: 6px auto 0;
font-size: 10px;
letter-spacing: 0.1em;
color: var(--muted);
padding: 0 0 0 26%;
}
.res-band-labels span {
width: 12%;
text-align: center;
}
.res-band-labels span:last-child {
margin-left: 14%;
}
.res-readout {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 20px;
margin-top: 16px;
padding: 16px 18px;
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);
}
.res-readout .readout-label {
width: 100%;
}
.res-value {
font-size: 1.9rem;
font-weight: 700;
color: var(--white);
}
.res-range {
font-size: 12px;
color: rgba(239, 230, 212, 0.75);
}
.res-columns {
display: grid;
grid-template-columns: 1.1fr 1fr;
gap: 24px;
margin-top: 22px;
}
.swatch-group {
margin-bottom: 14px;
}
.swatch-label {
display: block;
font-size: 10px;
font-weight: 600;
letter-spacing: 0.1em;
color: var(--muted);
margin-bottom: 6px;
}
.swatch-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.swatch {
appearance: none;
width: 30px;
height: 30px;
border-radius: var(--r-sm);
border: 2px solid var(--line-2);
cursor: pointer;
padding: 0;
transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.swatch:hover {
transform: translateY(-2px) scale(1.06);
box-shadow: var(--shadow-sm);
}
.swatch:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 2px;
}
.swatch[aria-checked="true"] {
border-color: var(--orange);
box-shadow: 0 0 0 3px var(--orange-soft);
}
.reverse-panel .field-row {
align-items: stretch;
}
.reverse-panel .big-input {
font-size: 1.1rem;
}
/* ============ FOOTER / TOAST ============ */
.shop-footer {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 8px;
margin-top: 26px;
font-size: 10px;
letter-spacing: 0.12em;
color: var(--muted);
}
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 20px);
background: var(--ink);
color: var(--kraft-l);
font-size: 13px;
font-weight: 600;
letter-spacing: 0.04em;
padding: 12px 22px;
border-radius: 999px;
border: 1px solid var(--orange);
box-shadow: var(--shadow-md);
opacity: 0;
pointer-events: none;
transition: opacity 0.22s ease, transform 0.22s ease;
z-index: 50;
max-width: calc(100vw - 40px);
}
.toast.is-show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ============ RESPONSIVE ============ */
@media (max-width: 820px) {
.wire-layout,
.res-columns {
grid-template-columns: 1fr;
}
.len-grid {
grid-template-columns: 1fr;
}
.len-swap {
padding: 2px 0;
}
.swap-glyph {
transform: rotate(90deg);
}
}
@media (max-width: 520px) {
.shop {
padding: 14px 12px 48px;
}
.hero {
padding: 26px 22px 44px;
}
.hero-corner {
right: 14px;
bottom: 10px;
gap: 10px;
}
.tabbar {
grid-template-columns: 1fr;
}
.tab {
padding: 12px 14px;
}
.panel {
padding: 18px 16px;
}
.wire-stats {
grid-template-columns: 1fr 1fr;
}
.len-readout {
gap: 12px;
}
.len-readout .copy-btn,
.res-readout .copy-btn {
margin-left: 0;
width: 100%;
}
.readout-value {
font-size: 1.25rem;
}
.res-value {
font-size: 1.5rem;
}
.wire-stage {
height: 180px;
}
.wire-disc {
width: 150px;
height: 150px;
}
}/* ==========================================================================
DIY — Maker Unit Converter
Tabs: Length (mm ↔ in), Wire gauge (AWG ↔ mm), Resistor color code.
Vanilla JS, no dependencies.
========================================================================== */
"use strict";
/* ---------- toast helper ---------- */
const toastEl = document.getElementById("toast");
let toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toastEl.classList.remove("is-show"), 2200);
}
function copyText(text, label) {
const done = () => toast(label + " copied to clipboard");
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(done, () => fallbackCopy(text, done));
} else {
fallbackCopy(text, done);
}
}
function fallbackCopy(text, done) {
const 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 (_) {
toast("Copy failed — select manually");
}
document.body.removeChild(ta);
}
/* ==========================================================================
TABS — roving tabindex + arrow-key navigation
========================================================================== */
const tabs = Array.from(document.querySelectorAll("[role='tab']"));
const panels = Array.from(document.querySelectorAll("[role='tabpanel']"));
function activateTab(tab, focus) {
tabs.forEach((t) => {
const active = t === tab;
t.classList.toggle("is-active", active);
t.setAttribute("aria-selected", String(active));
t.tabIndex = active ? 0 : -1;
});
panels.forEach((p) => {
const active = p.id === tab.getAttribute("aria-controls");
p.classList.toggle("is-active", active);
p.hidden = !active;
});
if (focus) tab.focus();
}
tabs.forEach((tab, i) => {
tab.addEventListener("click", () => activateTab(tab, false));
tab.addEventListener("keydown", (e) => {
let target = null;
if (e.key === "ArrowRight" || e.key === "ArrowDown") target = tabs[(i + 1) % tabs.length];
if (e.key === "ArrowLeft" || e.key === "ArrowUp") target = tabs[(i - 1 + tabs.length) % tabs.length];
if (e.key === "Home") target = tabs[0];
if (e.key === "End") target = tabs[tabs.length - 1];
if (target) {
e.preventDefault();
activateTab(target, true);
}
});
});
/* ==========================================================================
TAB 1 — LENGTH (mm ↔ inches, fractional)
========================================================================== */
const mmInput = document.getElementById("mm-input");
const inInput = document.getElementById("in-input");
const fracReadout = document.getElementById("frac-readout");
const fracError = document.getElementById("frac-error");
const decReadout = document.getElementById("dec-readout");
const MM_PER_IN = 25.4;
let lenState = { mm: 9.525 };
/** Parse "0.375", "3/8", "1 1/2", "1-1/2" → decimal inches (or null). */
function parseInches(raw) {
const s = raw.trim().replace(/["″in]+$/i, "").trim();
if (!s) return null;
const mixed = s.match(/^(\d+(?:\.\d+)?)[\s-]+(\d+)\s*\/\s*(\d+)$/);
if (mixed) {
const den = Number(mixed[3]);
return den ? Number(mixed[1]) + Number(mixed[2]) / den : null;
}
const frac = s.match(/^(\d+)\s*\/\s*(\d+)$/);
if (frac) {
const den = Number(frac[2]);
return den ? Number(frac[1]) / den : null;
}
const dec = Number(s);
return Number.isFinite(dec) ? dec : null;
}
function parseMm(raw) {
const s = raw.trim().replace(/mm$/i, "").trim();
if (!s) return null;
const v = Number(s);
return Number.isFinite(v) ? v : null;
}
/** Snap decimal inches to nearest 1/64 and reduce. */
function nearestFraction(inches) {
const whole = Math.floor(inches);
let num = Math.round((inches - whole) * 64);
let den = 64;
let w = whole;
if (num === 64) {
w += 1;
num = 0;
}
while (num > 0 && num % 2 === 0) {
num /= 2;
den /= 2;
}
const value = w + (num ? num / den : 0);
let label;
if (num === 0) label = w + "″";
else if (w === 0) label = num + "/" + den + "″";
else label = w + " " + num + "/" + den + "″";
return { label, value };
}
function renderLength(source) {
const mm = lenState.mm;
const inches = mm / MM_PER_IN;
if (source !== "mm") mmInput.value = stripZeros(mm.toFixed(3));
if (source !== "in") inInput.value = stripZeros(inches.toFixed(4));
const frac = nearestFraction(inches);
fracReadout.textContent = frac.label;
decReadout.textContent = inches.toFixed(4) + "″";
const driftMm = (frac.value - inches) * MM_PER_IN;
fracError.textContent =
Math.abs(driftMm) < 0.0005 ? "exact" : (driftMm > 0 ? "+" : "−") + Math.abs(driftMm).toFixed(3) + " mm drift";
}
function stripZeros(s) {
return s.replace(/\.?0+$/, "");
}
mmInput.addEventListener("input", () => {
const v = parseMm(mmInput.value);
mmInput.classList.toggle("is-bad", v === null && mmInput.value.trim() !== "");
if (v === null) return;
lenState.mm = v;
renderLength("mm");
});
inInput.addEventListener("input", () => {
const v = parseInches(inInput.value);
inInput.classList.toggle("is-bad", v === null && inInput.value.trim() !== "");
if (v === null) return;
lenState.mm = v * MM_PER_IN;
renderLength("in");
});
document.getElementById("length-chips").addEventListener("click", (e) => {
const chip = e.target.closest("[data-mm]");
if (!chip) return;
lenState.mm = Number(chip.dataset.mm);
renderLength("chip");
toast(chip.textContent.trim() + " loaded");
});
document.getElementById("copy-length").addEventListener("click", () => {
copyText(
stripZeros(lenState.mm.toFixed(3)) + " mm = " + (lenState.mm / MM_PER_IN).toFixed(4) + "\" (" + fracReadout.textContent.replace("″", "\"") + ")",
"Length"
);
});
renderLength("init");
/* ==========================================================================
TAB 2 — WIRE GAUGE (AWG ↔ mm)
========================================================================== */
const awgSlider = document.getElementById("awg-slider");
const awgVal = document.getElementById("awg-slider-val");
const wireDia = document.getElementById("wire-dia");
const wireArea = document.getElementById("wire-area");
const wireAmps = document.getElementById("wire-amps");
const wireUse = document.getElementById("wire-use");
const wireDisc = document.getElementById("wire-disc");
const wireRuler = document.getElementById("wire-ruler");
/** AWG n → diameter in mm (standard formula). */
function awgToMm(n) {
return 0.127 * Math.pow(92, (36 - n) / 39);
}
/* Approximate single-wire chassis ratings (illustrative). */
const AMP_TABLE = {
0: 245, 1: 211, 2: 181, 3: 158, 4: 135, 5: 118, 6: 101, 7: 89, 8: 73,
9: 64, 10: 55, 11: 47, 12: 41, 13: 35, 14: 32, 15: 28, 16: 22, 17: 19,
18: 16, 19: 14, 20: 11, 21: 9, 22: 7, 23: 4.7, 24: 3.5, 25: 2.7,
26: 2.2, 27: 1.7, 28: 1.4, 29: 1.2, 30: 0.86,
};
const USE_NOTES = [
[0, 4, "Battery banks, welding leads, off-grid inverter runs."],
[5, 9, "Sub-panels, heavy chassis grounds, big amp power feeds."],
[10, 12, "Mains circuits, e-bike battery leads, shop tool cords."],
[13, 15, "Speaker runs, power supplies, appliance rewiring."],
[16, 19, "Hookup wire, breadboard jumpers, small DC motors."],
[20, 23, "Arduino sensor leads, LED strips, servo pigtails."],
[24, 27, "Ribbon cable repairs, fine soldering, JST connectors."],
[28, 30, "Magnet wire, coil winding, wearable e-textiles."],
];
const MAX_WIRE_MM = awgToMm(0); // ≈ 8.25 mm — reference for preview scale
function renderWire() {
const n = Number(awgSlider.value);
const d = awgToMm(n);
const area = (Math.PI / 4) * d * d;
awgVal.textContent = n + " AWG";
awgSlider.setAttribute("aria-valuetext", n + " AWG, " + d.toFixed(3) + " millimeters diameter");
wireDia.textContent = d.toFixed(3) + " mm";
wireArea.textContent = (area < 0.1 ? area.toFixed(4) : area.toFixed(3)) + " mm²";
const amps = AMP_TABLE[n];
wireAmps.textContent = (amps >= 10 ? Math.round(amps) : amps) + " A";
wireRuler.textContent = "⌀ " + d.toFixed(3) + " mm";
const note = USE_NOTES.find(([lo, hi]) => n >= lo && n <= hi);
wireUse.textContent = note ? note[2] : "General purpose wiring.";
/* scale preview disc — sqrt easing keeps thin wires visible */
const scale = Math.max(0.14, Math.sqrt(d / MAX_WIRE_MM));
wireDisc.style.transform = "scale(" + scale.toFixed(3) + ")";
}
awgSlider.addEventListener("input", renderWire);
document.querySelectorAll("[data-awg]").forEach((chip) => {
chip.addEventListener("click", () => {
awgSlider.value = chip.dataset.awg;
renderWire();
toast(chip.dataset.awg + " AWG loaded");
});
});
document.getElementById("copy-wire").addEventListener("click", () => {
const n = Number(awgSlider.value);
const d = awgToMm(n);
copyText(
n + " AWG = " + d.toFixed(3) + " mm dia, " + ((Math.PI / 4) * d * d).toFixed(3) + " mm²",
"Wire gauge"
);
});
renderWire();
/* ==========================================================================
TAB 3 — RESISTOR COLOR CODE (4-band)
========================================================================== */
const DIGIT_COLORS = [
{ name: "black", hex: "#20242a", digit: 0 },
{ name: "brown", hex: "#7a4a21", digit: 1 },
{ name: "red", hex: "#d4503e", digit: 2 },
{ name: "orange", hex: "#ff6b35", digit: 3 },
{ name: "yellow", hex: "#ffd60a", digit: 4 },
{ name: "green", hex: "#2f9e6f", digit: 5 },
{ name: "blue", hex: "#2a6fdb", digit: 6 },
{ name: "violet", hex: "#8f5bbd", digit: 7 },
{ name: "grey", hex: "#8b959e", digit: 8 },
{ name: "white", hex: "#f2f4f6", digit: 9 },
];
const MULT_COLORS = DIGIT_COLORS.map((c) => ({ ...c, mult: Math.pow(10, c.digit) })).concat([
{ name: "gold", hex: "#c9a227", mult: 0.1 },
{ name: "silver", hex: "#b9c0c6", mult: 0.01 },
]);
const TOL_COLORS = [
{ name: "brown", hex: "#7a4a21", tol: 1 },
{ name: "red", hex: "#d4503e", tol: 2 },
{ name: "gold", hex: "#c9a227", tol: 5 },
{ name: "silver", hex: "#b9c0c6", tol: 10 },
];
const BAND_SETS = [DIGIT_COLORS, DIGIT_COLORS, MULT_COLORS, TOL_COLORS];
const BAND_ROLES = ["first digit", "second digit", "multiplier", "tolerance"];
/* state = index into each band's color set → 4.7 kΩ ±5% by default */
const bandState = [4, 7, 2, 2]; // yellow, violet, red(×100), gold
const bandEls = [0, 1, 2, 3].map((i) => document.getElementById("band-" + i));
const resValue = document.getElementById("res-value");
const resRange = document.getElementById("res-range");
function formatOhms(v) {
if (v >= 1e6) return trimNum(v / 1e6) + " MΩ";
if (v >= 1e3) return trimNum(v / 1e3) + " kΩ";
if (v >= 1) return trimNum(v) + " Ω";
return v.toFixed(2) + " Ω";
}
function trimNum(v) {
const s = v >= 100 ? v.toFixed(1) : v.toFixed(3);
return stripZeros(s);
}
function currentOhms() {
const d1 = DIGIT_COLORS[bandState[0]].digit;
const d2 = DIGIT_COLORS[bandState[1]].digit;
const mult = MULT_COLORS[bandState[2]].mult;
return (d1 * 10 + d2) * mult;
}
function renderResistor() {
bandState.forEach((idx, i) => {
const color = BAND_SETS[i][idx];
bandEls[i].setAttribute("fill", color.hex);
bandEls[i].setAttribute(
"aria-label",
"Band " + (i + 1) + ": " + BAND_ROLES[i] + ", " + color.name + ". Press Enter to cycle."
);
});
const ohms = currentOhms();
const tol = TOL_COLORS[bandState[3]].tol;
resValue.textContent = formatOhms(ohms) + " ±" + tol + "%";
resRange.textContent = formatOhms(ohms * (1 - tol / 100)) + " – " + formatOhms(ohms * (1 + tol / 100));
/* sync swatch selection */
document.querySelectorAll(".swatch-row").forEach((row, i) => {
Array.from(row.children).forEach((sw, j) => {
sw.setAttribute("aria-checked", String(j === bandState[i]));
sw.tabIndex = j === bandState[i] ? 0 : -1;
});
});
}
/* click / keyboard cycling on SVG bands */
bandEls.forEach((el, i) => {
const cycle = () => {
bandState[i] = (bandState[i] + 1) % BAND_SETS[i].length;
renderResistor();
toast("Band " + (i + 1) + " → " + BAND_SETS[i][bandState[i]].name);
};
el.addEventListener("click", cycle);
el.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
cycle();
}
});
});
/* build swatch rows */
BAND_SETS.forEach((set, i) => {
const row = document.getElementById("swatches-" + i);
set.forEach((color, j) => {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "swatch";
btn.style.background = color.hex;
btn.setAttribute("role", "radio");
btn.setAttribute("aria-checked", "false");
btn.setAttribute(
"aria-label",
color.name +
(color.digit !== undefined && i < 2 ? " (" + color.digit + ")" : "") +
(i === 2 ? " (×" + set[j].mult + ")" : "") +
(i === 3 ? " (±" + set[j].tol + "%)" : "")
);
btn.title = btn.getAttribute("aria-label");
btn.addEventListener("click", () => {
bandState[i] = j;
renderResistor();
});
btn.addEventListener("keydown", (e) => {
let next = null;
if (e.key === "ArrowRight" || e.key === "ArrowDown") next = (bandState[i] + 1) % set.length;
if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = (bandState[i] - 1 + set.length) % set.length;
if (next !== null) {
e.preventDefault();
bandState[i] = next;
renderResistor();
row.children[next].focus();
}
});
row.appendChild(btn);
});
});
/* reverse lookup: ohms → bands */
const ohmsInput = document.getElementById("ohms-input");
function parseOhms(raw) {
const s = raw.trim().toLowerCase().replace(/[ωΩ]|ohms?/g, "").trim();
if (!s) return null;
const m = s.match(/^(\d+(?:\.\d+)?)\s*(k|m)?$/);
if (!m) return null;
let v = Number(m[1]);
if (m[2] === "k") v *= 1e3;
if (m[2] === "m") v *= 1e6;
return Number.isFinite(v) && v > 0 ? v : null;
}
function setBandsFromOhms(ohms) {
/* find best 2-significant-digit representation: ohms ≈ (d1 d2) × 10^p */
let best = null;
for (let mi = 0; mi < MULT_COLORS.length; mi++) {
const mult = MULT_COLORS[mi].mult;
const code = Math.round(ohms / mult);
if (code < 10 || code > 99) continue;
const err = Math.abs(code * mult - ohms) / ohms;
if (!best || err < best.err) best = { mi, code, err };
}
if (!best) {
toast("Value out of 4-band range (1.0 Ω – 99 GΩ)");
return false;
}
bandState[0] = Math.floor(best.code / 10);
bandState[1] = best.code % 10;
bandState[2] = best.mi;
renderResistor();
if (best.err > 0.001) {
toast("Snapped to " + formatOhms(currentOhms()));
} else {
toast("Bands set to " + formatOhms(currentOhms()));
}
return true;
}
function applyOhmsInput() {
const v = parseOhms(ohmsInput.value);
ohmsInput.classList.toggle("is-bad", v === null && ohmsInput.value.trim() !== "");
if (v === null) {
if (ohmsInput.value.trim() !== "") toast("Could not read that value");
return;
}
setBandsFromOhms(v);
}
document.getElementById("ohms-apply").addEventListener("click", applyOhmsInput);
ohmsInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") applyOhmsInput();
});
document.querySelectorAll("[data-ohms]").forEach((chip) => {
chip.addEventListener("click", () => {
ohmsInput.value = chip.textContent.trim();
setBandsFromOhms(Number(chip.dataset.ohms));
});
});
document.getElementById("copy-resistor").addEventListener("click", () => {
const bands = bandState.map((idx, i) => BAND_SETS[i][idx].name).join(" · ");
copyText(resValue.textContent + " (" + bands + ")", "Resistor value");
});
renderResistor();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DIY — Maker Unit Converter</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="shop">
<!-- ============ HERO ============ -->
<header class="hero">
<div class="hero-inner">
<div class="hero-badge-row">
<span class="mono-badge">TOOL-047</span>
<span class="mono-badge badge-orange">BENCH UTILITY</span>
<span class="mono-badge">REV 2.3</span>
</div>
<h1 class="hero-title">Maker Unit<br />Converter</h1>
<p class="hero-sub">
Three bench-side calculators for the workshop: length conversion with
fractional inches, AWG wire gauge lookup, and an interactive resistor
color-code decoder. Everything updates live — no round trips, no lookup charts.
</p>
<div class="hero-meta">
<span class="meta-chip"><span class="dot dot-ok" aria-hidden="true"></span> CALIBRATED</span>
<span class="meta-chip mono">± 0.001 mm precision</span>
<span class="meta-chip mono">IEC 60062</span>
</div>
</div>
<div class="hero-corner mono" aria-hidden="true">
<span>SHEET 1 / 1</span>
<span>SCALE 1:1</span>
</div>
</header>
<!-- ============ TABS ============ -->
<nav class="tabbar" role="tablist" aria-label="Converter tools">
<button class="tab is-active" id="tab-length" role="tab" aria-selected="true" aria-controls="panel-length">
<span class="tab-num mono">01</span>
<span class="tab-label">Length</span>
<span class="tab-hint">mm ↔ inches</span>
</button>
<button class="tab" id="tab-wire" role="tab" aria-selected="false" aria-controls="panel-wire" tabindex="-1">
<span class="tab-num mono">02</span>
<span class="tab-label">Wire Gauge</span>
<span class="tab-hint">AWG ↔ mm</span>
</button>
<button class="tab" id="tab-resistor" role="tab" aria-selected="false" aria-controls="panel-resistor" tabindex="-1">
<span class="tab-num mono">03</span>
<span class="tab-label">Resistor Code</span>
<span class="tab-hint">4-band decoder</span>
</button>
</nav>
<!-- ============ PANEL 1 : LENGTH ============ -->
<section class="panel is-active" id="panel-length" role="tabpanel" aria-labelledby="tab-length" tabindex="0">
<div class="panel-head">
<h2 class="panel-title">Length Converter</h2>
<span class="mono-badge">MM ↔ IN</span>
</div>
<div class="len-grid">
<div class="len-card">
<label class="field-label" for="mm-input">Millimeters</label>
<div class="field-row">
<input class="big-input mono" id="mm-input" type="text" inputmode="decimal" value="9.525" autocomplete="off" spellcheck="false" aria-describedby="mm-hint" />
<span class="unit-tag mono">mm</span>
</div>
<p class="field-hint" id="mm-hint">Decimal, e.g. <span class="mono">12.7</span></p>
</div>
<div class="len-swap" aria-hidden="true">
<span class="swap-glyph">⇄</span>
</div>
<div class="len-card">
<label class="field-label" for="in-input">Inches</label>
<div class="field-row">
<input class="big-input mono" id="in-input" type="text" inputmode="text" value="3/8" autocomplete="off" spellcheck="false" aria-describedby="in-hint" />
<span class="unit-tag mono">in</span>
</div>
<p class="field-hint" id="in-hint">Decimal or fraction, e.g. <span class="mono">3/8</span> or <span class="mono">1 1/2</span></p>
</div>
</div>
<div class="len-readout" aria-live="polite">
<div class="readout-block">
<span class="readout-label">Nearest fraction</span>
<span class="readout-value mono" id="frac-readout">3/8″</span>
<span class="readout-note mono" id="frac-error">exact</span>
</div>
<div class="readout-block">
<span class="readout-label">Decimal inches</span>
<span class="readout-value mono" id="dec-readout">0.3750″</span>
</div>
<button class="copy-btn" id="copy-length" type="button">
<span aria-hidden="true">⧉</span> Copy result
</button>
</div>
<div class="chip-section">
<h3 class="chip-title">Common shop sizes</h3>
<div class="chip-row" id="length-chips">
<button class="chip mono" type="button" data-mm="1.5875">1/16″</button>
<button class="chip mono" type="button" data-mm="3.175">1/8″</button>
<button class="chip mono" type="button" data-mm="6.35">1/4″</button>
<button class="chip mono" type="button" data-mm="9.525">3/8″</button>
<button class="chip mono" type="button" data-mm="12.7">1/2″</button>
<button class="chip mono" type="button" data-mm="19.05">3/4″</button>
<button class="chip mono" type="button" data-mm="25.4">1″</button>
<button class="chip mono" type="button" data-mm="3">3 mm</button>
<button class="chip mono" type="button" data-mm="5">5 mm</button>
<button class="chip mono" type="button" data-mm="8">8 mm</button>
<button class="chip mono" type="button" data-mm="10">10 mm</button>
<button class="chip mono" type="button" data-mm="18">18 mm ply</button>
</div>
</div>
<div class="note-strip">
<span class="note-icon" aria-hidden="true">✎</span>
<p>Fractions snap to the nearest <span class="mono">1/64″</span>. The drift note shows how far the rounded fraction is from your exact value — handy when a plan calls for imperial stock but your calipers speak metric.</p>
</div>
</section>
<!-- ============ PANEL 2 : WIRE GAUGE ============ -->
<section class="panel" id="panel-wire" role="tabpanel" aria-labelledby="tab-wire" tabindex="0" hidden>
<div class="panel-head">
<h2 class="panel-title">Wire Gauge</h2>
<span class="mono-badge">AWG ↔ MM</span>
</div>
<div class="wire-layout">
<div class="wire-controls">
<label class="field-label" for="awg-slider">AWG size <span class="mono awg-current" id="awg-slider-val">18 AWG</span></label>
<input type="range" id="awg-slider" min="0" max="30" step="1" value="18" aria-valuetext="18 AWG" />
<div class="slider-scale mono" aria-hidden="true">
<span>0</span><span>5</span><span>10</span><span>15</span><span>20</span><span>25</span><span>30</span>
</div>
<div class="wire-stats" aria-live="polite">
<div class="stat-card">
<span class="stat-label">Diameter</span>
<span class="stat-value mono" id="wire-dia">1.024 mm</span>
</div>
<div class="stat-card">
<span class="stat-label">Cross-section</span>
<span class="stat-value mono" id="wire-area">0.823 mm²</span>
</div>
<div class="stat-card">
<span class="stat-label">Chassis rating*</span>
<span class="stat-value mono" id="wire-amps">16 A</span>
</div>
</div>
<div class="wire-uses">
<span class="stat-label">Typical bench use</span>
<p class="wire-use-text" id="wire-use">Hookup wire, breadboard jumpers, small DC motors.</p>
</div>
<button class="copy-btn" id="copy-wire" type="button">
<span aria-hidden="true">⧉</span> Copy result
</button>
<p class="fine-print">*Approximate single-wire chassis rating in free air. Illustrative only — derate for bundles and check a real ampacity table.</p>
</div>
<div class="wire-preview" aria-hidden="true">
<div class="preview-frame">
<span class="preview-caption mono">CROSS-SECTION · SCALE 20:1</span>
<div class="wire-stage">
<div class="wire-disc" id="wire-disc">
<div class="wire-core" id="wire-core"></div>
</div>
</div>
<div class="preview-ruler mono" id="wire-ruler">⌀ 1.024 mm</div>
</div>
</div>
</div>
<div class="chip-section">
<h3 class="chip-title">Quick picks</h3>
<div class="chip-row">
<button class="chip mono" type="button" data-awg="10">10 AWG · mains</button>
<button class="chip mono" type="button" data-awg="14">14 AWG · speaker</button>
<button class="chip mono" type="button" data-awg="18">18 AWG · hookup</button>
<button class="chip mono" type="button" data-awg="22">22 AWG · breadboard</button>
<button class="chip mono" type="button" data-awg="26">26 AWG · wire-wrap</button>
<button class="chip mono" type="button" data-awg="30">30 AWG · magnet</button>
</div>
</div>
</section>
<!-- ============ PANEL 3 : RESISTOR ============ -->
<section class="panel" id="panel-resistor" role="tabpanel" aria-labelledby="tab-resistor" tabindex="0" hidden>
<div class="panel-head">
<h2 class="panel-title">Resistor Color Code</h2>
<span class="mono-badge">4-BAND</span>
</div>
<div class="res-stage">
<svg class="res-svg" viewBox="0 0 460 120" role="group" aria-label="Interactive four-band resistor. Click a band to cycle its color.">
<!-- leads -->
<rect x="0" y="56" width="80" height="8" rx="4" fill="#9aa7b2" />
<rect x="380" y="56" width="80" height="8" rx="4" fill="#9aa7b2" />
<!-- body -->
<rect x="78" y="30" width="304" height="60" rx="28" fill="#e8d9b8" stroke="#c9b58c" stroke-width="2" />
<ellipse cx="106" cy="60" rx="26" ry="30" fill="#e0cfa9" opacity="0.6" />
<ellipse cx="354" cy="60" rx="26" ry="30" fill="#e0cfa9" opacity="0.6" />
<!-- bands (interactive) -->
<rect class="band" id="band-0" x="128" y="30" width="24" height="60" fill="#ffd60a" tabindex="0" role="button" aria-label="Band 1: first digit, yellow" />
<rect class="band" id="band-1" x="176" y="30" width="24" height="60" fill="#8f5bbd" tabindex="0" role="button" aria-label="Band 2: second digit, violet" />
<rect class="band" id="band-2" x="224" y="30" width="24" height="60" fill="#d4503e" tabindex="0" role="button" aria-label="Band 3: multiplier, red" />
<rect class="band band-tol" id="band-3" x="312" y="30" width="24" height="60" fill="#c9a227" tabindex="0" role="button" aria-label="Band 4: tolerance, gold" />
</svg>
<div class="res-band-labels mono" aria-hidden="true">
<span>1st</span><span>2nd</span><span>×MULT</span><span>TOL</span>
</div>
</div>
<div class="res-readout" aria-live="polite">
<span class="readout-label">Decoded value</span>
<span class="res-value mono" id="res-value">4.7 kΩ ±5%</span>
<span class="res-range mono" id="res-range">4.465 kΩ – 4.935 kΩ</span>
<button class="copy-btn" id="copy-resistor" type="button">
<span aria-hidden="true">⧉</span> Copy value
</button>
</div>
<div class="res-columns">
<div class="swatch-panel">
<h3 class="chip-title">Pick band colors</h3>
<div class="swatch-group" data-band="0">
<span class="swatch-label mono">BAND 1 · digit</span>
<div class="swatch-row" role="radiogroup" aria-label="Band 1 color" id="swatches-0"></div>
</div>
<div class="swatch-group" data-band="1">
<span class="swatch-label mono">BAND 2 · digit</span>
<div class="swatch-row" role="radiogroup" aria-label="Band 2 color" id="swatches-1"></div>
</div>
<div class="swatch-group" data-band="2">
<span class="swatch-label mono">BAND 3 · multiplier</span>
<div class="swatch-row" role="radiogroup" aria-label="Band 3 multiplier color" id="swatches-2"></div>
</div>
<div class="swatch-group" data-band="3">
<span class="swatch-label mono">BAND 4 · tolerance</span>
<div class="swatch-row" role="radiogroup" aria-label="Band 4 tolerance color" id="swatches-3"></div>
</div>
</div>
<div class="reverse-panel">
<h3 class="chip-title">Reverse lookup</h3>
<label class="field-label" for="ohms-input">Enter resistance</label>
<div class="field-row">
<input class="big-input mono" id="ohms-input" type="text" inputmode="text" placeholder="e.g. 4.7k, 220, 1M" autocomplete="off" spellcheck="false" aria-describedby="ohms-hint" />
<button class="set-btn" id="ohms-apply" type="button">Set bands</button>
</div>
<p class="field-hint" id="ohms-hint">Accepts <span class="mono">Ω</span> values with <span class="mono">k</span>/<span class="mono">M</span> suffix. Snaps to nearest 2-digit code.</p>
<div class="chip-section">
<h3 class="chip-title">Drawer staples</h3>
<div class="chip-row">
<button class="chip mono" type="button" data-ohms="220">220 Ω</button>
<button class="chip mono" type="button" data-ohms="330">330 Ω</button>
<button class="chip mono" type="button" data-ohms="1000">1 kΩ</button>
<button class="chip mono" type="button" data-ohms="4700">4.7 kΩ</button>
<button class="chip mono" type="button" data-ohms="10000">10 kΩ</button>
<button class="chip mono" type="button" data-ohms="100000">100 kΩ</button>
<button class="chip mono" type="button" data-ohms="1000000">1 MΩ</button>
</div>
</div>
</div>
</div>
<div class="note-strip">
<span class="note-icon" aria-hidden="true">⚡</span>
<p>Click or focus a band and press <span class="mono">Enter</span> / <span class="mono">Space</span> to cycle its color, or pick directly from the swatch rows. The tolerance band only accepts gold, silver, brown and red.</p>
</div>
</section>
<footer class="shop-footer">
<span class="mono">STEALTHIS WORKSHOP · FICTIONAL DEMO DATA</span>
<span class="mono">DWG NO. MUC-047-B</span>
</footer>
</main>
<div class="toast mono" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Maker Unit Converter
Three workshop calculators behind one blueprint-styled tab bar. The Length tab converts millimeters and inches in both directions as you type — the inches field accepts decimals, plain fractions (3/8) and mixed numbers (1 1/2), while a navy readout shows the nearest 1/64″ fraction plus how many millimeters of drift that rounding introduces. Quick chips load common shop sizes like 1/4″, 3 mm and 18 mm plywood.
The Wire Gauge tab maps AWG 0–30 to diameter and cross-section using the standard geometric formula, with an illustrative chassis amp rating and typical bench uses per range. A blueprint-framed cross-section preview scales the copper disc live as you drag the slider, so 30 AWG magnet wire visibly shrinks next to 10 AWG mains cable.
The Resistor Code tab renders a 4-band resistor as inline SVG: click or keyboard-cycle each band, or pick from the swatch radiogroups, and the decoded value (4.7 kΩ ±5%) plus its tolerance range update instantly in mono type. A reverse-lookup field parses values like 4.7k or 1M and snaps the bands to the nearest two-digit code. Every tool has a copy-result button with a toast confirmation, and the tabs use proper roving-tabindex arrow-key navigation.
Illustrative UI only — always follow real safety guidance when working with tools, electronics, or repairs.