DIY — Tool Encyclopedia
A workshop-styled tool wiki page with an A–Z alphabet index bar, live search with match highlighting, and category filters across hand, power, electronics and measuring tools. Eighteen entry cards pair SVG line-art icons with one-line definitions, used-for chips, amber safety strips and budget-alternative suggestions, while a sticky blueprint side panel rotates a Tool of the Day with mono quick facts for weight, price range and skill level.
MCP
Code
:root {
--blueprint: #0f2a4a;
--blueprint-d: #0a1e36;
--blueprint-l: #16385f;
--grid: rgba(140, 180, 220, 0.14);
--grid-major: rgba(140, 180, 220, 0.26);
--orange: #ff6b35;
--orange-d: #e0521f;
--orange-soft: #ffe3d6;
--kraft: #d9c7a7;
--kraft-l: #efe6d4;
--ink: #1c2733;
--ink-2: #3d4a57;
--muted: #6b7885;
--bg: #f4f1ea;
--white: #ffffff;
--line: rgba(28, 39, 51, 0.12);
--line-2: rgba(28, 39, 51, 0.2);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--r-sm: 6px;
--r-md: 12px;
--r-lg: 18px;
--shadow: 0 2px 6px rgba(28, 39, 51, 0.06), 0 12px 32px rgba(28, 39, 51, 0.08);
--shadow-sm: 0 1px 3px rgba(28, 39, 51, 0.08);
}
* { 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;
}
.mono { font-family: "JetBrains Mono", ui-monospace, monospace; }
button { font-family: inherit; cursor: pointer; }
/* ============ HERO ============ */
.hero {
background-color: var(--blueprint);
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px),
linear-gradient(var(--grid-major) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-major) 1px, transparent 1px);
background-size: 8px 8px, 8px 8px, 80px 80px, 80px 80px;
color: var(--kraft-l);
border-bottom: 4px solid var(--orange);
}
.hero-inner {
max-width: 1160px;
margin: 0 auto;
padding: 40px 24px 32px;
}
.hero-top {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 22px;
}
.brand-badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: var(--r-sm);
background: var(--orange);
color: var(--blueprint-d);
box-shadow: 0 4px 12px rgba(255, 107, 53, 0.35);
}
.brand-name {
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.14em;
font-size: 0.85rem;
}
.mono-chip {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 0.68rem;
letter-spacing: 0.08em;
padding: 4px 9px;
border: 1px solid rgba(217, 199, 167, 0.4);
border-radius: 999px;
color: var(--kraft);
margin-left: auto;
}
.hero-title {
margin: 0 0 10px;
font-size: clamp(2rem, 5vw, 3.2rem);
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.06em;
line-height: 1.08;
color: var(--white);
}
.hero-sub {
margin: 0 0 26px;
max-width: 560px;
color: var(--kraft);
font-size: 1rem;
}
.hero-controls {
display: flex;
flex-wrap: wrap;
gap: 14px;
align-items: center;
}
.search-wrap {
position: relative;
flex: 1 1 300px;
max-width: 460px;
}
.search-ico {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
color: var(--muted);
pointer-events: none;
}
#search {
width: 100%;
padding: 13px 42px 13px 42px;
border: 2px solid transparent;
border-radius: var(--r-md);
background: var(--white);
color: var(--ink);
font: inherit;
font-size: 0.95rem;
box-shadow: var(--shadow-sm);
transition: border-color 0.15s, box-shadow 0.15s;
}
#search:focus {
outline: none;
border-color: var(--orange);
box-shadow: 0 0 0 4px rgba(255, 107, 53, 0.25);
}
#search::-webkit-search-cancel-button { display: none; }
.clear-btn {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
width: 28px;
height: 28px;
border: none;
border-radius: 50%;
background: var(--kraft-l);
color: var(--ink-2);
font-size: 1rem;
line-height: 1;
transition: background 0.15s;
}
.clear-btn:hover { background: var(--kraft); }
.cat-filters {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.cat-btn {
padding: 9px 15px;
border: 1px solid rgba(217, 199, 167, 0.45);
border-radius: 999px;
background: transparent;
color: var(--kraft-l);
font-size: 0.8rem;
font-weight: 600;
letter-spacing: 0.03em;
transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.12s;
}
.cat-btn:hover { border-color: var(--orange); color: var(--white); }
.cat-btn:active { transform: scale(0.96); }
.cat-btn.is-on {
background: var(--orange);
border-color: var(--orange);
color: var(--blueprint-d);
}
.cat-btn:focus-visible,
.clear-btn:focus-visible {
outline: 2px solid var(--orange);
outline-offset: 2px;
}
.hero-stats {
display: flex;
align-items: center;
gap: 14px;
margin-top: 24px;
font-size: 0.72rem;
letter-spacing: 0.12em;
color: var(--kraft);
}
.hero-stats strong { color: var(--orange); font-weight: 700; }
.hero-stats .dot {
width: 4px;
height: 4px;
border-radius: 50%;
background: rgba(217, 199, 167, 0.5);
}
/* ============ ALPHA BAR ============ */
.alpha-bar {
position: sticky;
top: 0;
z-index: 30;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 3px;
padding: 10px 12px;
background: rgba(244, 241, 234, 0.92);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--line);
}
.alpha-btn {
min-width: 32px;
height: 32px;
padding: 0 4px;
border: none;
border-radius: var(--r-sm);
background: transparent;
color: var(--ink-2);
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 0.82rem;
font-weight: 600;
transition: background 0.12s, color 0.12s, transform 0.12s;
}
.alpha-btn:not(:disabled):hover {
background: var(--orange-soft);
color: var(--orange-d);
transform: translateY(-1px);
}
.alpha-btn:disabled {
color: rgba(107, 120, 133, 0.35);
cursor: default;
}
.alpha-btn.is-current {
background: var(--blueprint);
color: var(--white);
}
.alpha-btn:focus-visible {
outline: 2px solid var(--orange);
outline-offset: 1px;
}
/* ============ LAYOUT ============ */
.layout {
max-width: 1160px;
margin: 0 auto;
padding: 34px 24px 60px;
display: grid;
grid-template-columns: minmax(0, 1fr) 320px;
gap: 34px;
align-items: start;
}
/* ============ LETTER SECTIONS ============ */
.letter-section { margin-bottom: 40px; scroll-margin-top: 74px; }
.letter-head {
display: flex;
align-items: baseline;
gap: 14px;
margin-bottom: 16px;
}
.letter-glyph {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 2rem;
font-weight: 700;
color: var(--orange);
line-height: 1;
}
.letter-rule {
flex: 1;
height: 0;
border-top: 2px dashed var(--line-2);
}
.letter-count {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 0.7rem;
letter-spacing: 0.1em;
color: var(--muted);
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 18px;
}
/* ============ TOOL CARD ============ */
.tool-card {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 18px;
display: flex;
flex-direction: column;
gap: 12px;
box-shadow: var(--shadow-sm);
transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s;
scroll-margin-top: 84px;
}
.tool-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow);
border-color: var(--line-2);
}
.tool-card.is-flash {
border-color: var(--orange);
box-shadow: 0 0 0 4px rgba(255, 107, 53, 0.28), var(--shadow);
}
.tool-card-top {
display: flex;
gap: 14px;
align-items: flex-start;
}
.tool-art {
flex: 0 0 auto;
width: 64px;
height: 64px;
display: grid;
place-items: center;
border-radius: var(--r-md);
background: var(--kraft-l);
color: var(--blueprint);
border: 1px solid var(--line);
}
.tool-art svg { width: 44px; height: 44px; }
.tool-title-wrap { min-width: 0; }
.tool-badges {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px;
}
.tool-ref {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 0.62rem;
font-weight: 700;
letter-spacing: 0.08em;
padding: 2px 7px;
border-radius: 4px;
background: var(--blueprint);
color: var(--kraft-l);
}
.tool-cat {
font-size: 0.62rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 2px 8px;
border-radius: 999px;
}
.tool-cat[data-cat="hand"] { background: var(--kraft-l); color: var(--ink-2); border: 1px solid var(--kraft); }
.tool-cat[data-cat="power"] { background: var(--orange-soft); color: var(--orange-d); }
.tool-cat[data-cat="electronics"] { background: #dcebf7; color: #1e5a8a; }
.tool-cat[data-cat="measuring"] { background: #dcf2e7; color: #1f7a52; }
.tool-name {
margin: 0;
font-size: 1.05rem;
font-weight: 700;
letter-spacing: 0.01em;
}
.tool-def {
margin: 0;
font-size: 0.86rem;
color: var(--ink-2);
}
.use-chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 0;
padding: 0;
list-style: none;
}
.use-chips li {
font-size: 0.72rem;
font-weight: 600;
color: var(--ink-2);
background: var(--bg);
border: 1px solid var(--line);
padding: 3px 9px;
border-radius: 999px;
}
.safety-strip {
display: flex;
gap: 9px;
align-items: flex-start;
font-size: 0.76rem;
color: #7a4d10;
background: #fdf3e3;
border: 1px solid rgba(217, 138, 43, 0.4);
border-left: 4px solid var(--warn);
border-radius: var(--r-sm);
padding: 8px 10px;
}
.safety-strip svg { flex: 0 0 auto; margin-top: 1px; color: var(--warn); }
.alt-line {
font-size: 0.76rem;
color: var(--muted);
border-top: 1px dashed var(--line-2);
padding-top: 10px;
margin-top: auto;
}
.alt-line strong { color: var(--ink-2); font-weight: 600; }
.alt-line .alt-arrow { color: var(--orange-d); font-weight: 700; }
mark {
background: rgba(255, 107, 53, 0.3);
color: inherit;
border-radius: 3px;
padding: 0 1px;
}
/* ============ NO RESULTS ============ */
.no-results {
text-align: center;
padding: 60px 20px;
border: 2px dashed var(--line-2);
border-radius: var(--r-lg);
background: var(--kraft-l);
}
.no-results-art {
font-size: 1.6rem;
color: var(--muted);
margin-bottom: 10px;
}
.no-results h3 {
margin: 0 0 6px;
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 1rem;
}
.no-results p { margin: 0 0 18px; color: var(--muted); font-size: 0.9rem; }
.btn-orange {
border: none;
background: var(--orange);
color: var(--blueprint-d);
font-weight: 700;
font-size: 0.85rem;
letter-spacing: 0.03em;
padding: 11px 20px;
border-radius: var(--r-md);
box-shadow: 0 4px 12px rgba(255, 107, 53, 0.35);
transition: background 0.15s, transform 0.12s;
}
.btn-orange:hover { background: var(--orange-d); }
.btn-orange:active { transform: scale(0.97); }
.btn-orange:focus-visible { outline: 2px solid var(--blueprint); outline-offset: 2px; }
/* ============ SIDE PANEL ============ */
.side {
position: sticky;
top: 74px;
display: flex;
flex-direction: column;
gap: 20px;
}
.totd {
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-lg);
padding: 20px;
color: var(--kraft-l);
box-shadow: var(--shadow);
}
.totd-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 14px;
}
.totd-eyebrow {
font-size: 0.66rem;
font-weight: 700;
letter-spacing: 0.18em;
color: var(--orange);
}
.shuffle-btn {
width: 30px;
height: 30px;
display: grid;
place-items: center;
border: 1px solid rgba(217, 199, 167, 0.4);
border-radius: var(--r-sm);
background: transparent;
color: var(--kraft);
transition: border-color 0.15s, color 0.15s, transform 0.3s ease;
}
.shuffle-btn:hover { border-color: var(--orange); color: var(--orange); }
.shuffle-btn:active { transform: rotate(180deg); }
.shuffle-btn:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.totd-art {
display: grid;
place-items: center;
height: 150px;
margin-bottom: 14px;
border: 1px dashed rgba(217, 199, 167, 0.4);
border-radius: var(--r-md);
color: var(--kraft-l);
}
.totd-art svg { width: 110px; height: 110px; }
.totd-name {
margin: 0 0 6px;
font-size: 1.2rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--white);
}
.totd-def {
margin: 0 0 16px;
font-size: 0.83rem;
color: var(--kraft);
}
.totd-facts {
margin: 0 0 16px;
display: grid;
gap: 8px;
}
.totd-facts > div {
display: flex;
justify-content: space-between;
gap: 12px;
border-bottom: 1px dashed rgba(217, 199, 167, 0.25);
padding-bottom: 7px;
}
.totd-facts dt {
font-size: 0.66rem;
letter-spacing: 0.14em;
color: var(--kraft);
}
.totd-facts dd {
margin: 0;
font-size: 0.74rem;
font-weight: 600;
color: var(--white);
text-align: right;
}
.totd-jump { width: 100%; }
.side-note {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 18px 20px;
box-shadow: var(--shadow-sm);
}
.side-note-title {
margin: 0 0 12px;
font-size: 0.78rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--ink-2);
}
.legend {
margin: 0;
padding: 0;
list-style: none;
display: grid;
gap: 10px;
font-size: 0.8rem;
color: var(--ink-2);
}
.legend li { display: flex; align-items: center; gap: 10px; }
.legend-swatch {
flex: 0 0 auto;
width: 22px;
height: 14px;
border-radius: 3px;
}
.swatch-safety { background: #fdf3e3; border-left: 4px solid var(--warn); border-top: 1px solid rgba(217,138,43,0.4); border-right: 1px solid rgba(217,138,43,0.4); border-bottom: 1px solid rgba(217,138,43,0.4); }
.swatch-alt { border-top: 2px dashed var(--line-2); height: 2px; border-radius: 0; }
.swatch-mono {
width: auto;
height: auto;
font-size: 0.58rem;
font-weight: 700;
padding: 2px 6px;
background: var(--blueprint);
color: var(--kraft-l);
border-radius: 4px;
}
/* ============ FOOTER ============ */
.foot {
border-top: 2px dashed var(--line-2);
max-width: 1160px;
margin: 0 auto;
padding: 22px 24px 40px;
display: flex;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
font-size: 0.72rem;
color: var(--muted);
letter-spacing: 0.06em;
}
/* ============ TOAST ============ */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 16px);
background: var(--ink);
color: var(--kraft-l);
font-size: 0.74rem;
letter-spacing: 0.05em;
padding: 10px 18px;
border-radius: 999px;
border-left: 4px solid var(--orange);
box-shadow: var(--shadow);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 60;
max-width: calc(100vw - 40px);
}
.toast.is-show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ============ RESPONSIVE ============ */
@media (max-width: 900px) {
.layout { grid-template-columns: 1fr; }
.side { position: static; order: -1; }
.side-note { display: none; }
}
@media (max-width: 520px) {
.hero-inner { padding: 28px 16px 24px; }
.hero-sub { font-size: 0.9rem; }
.layout { padding: 24px 14px 48px; gap: 24px; }
.card-grid { grid-template-columns: 1fr; }
.alpha-btn { min-width: 26px; height: 28px; font-size: 0.74rem; }
.hero-stats { flex-wrap: wrap; gap: 8px; }
.foot { flex-direction: column; }
.tool-art { width: 54px; height: 54px; }
.tool-art svg { width: 38px; height: 38px; }
}
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; scroll-behavior: auto !important; }
}/* DIY — Tool Encyclopedia
Vanilla JS: renders the A–Z tool wiki, live search with highlight,
category filters, alphabet scrollspy and a Tool-of-the-day panel. */
(function () {
"use strict";
/* ---------- SVG line-art icons (48x48, stroke-based) ---------- */
const svg = (inner) =>
'<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
inner +
"</svg>";
const ICONS = {
grinder: svg(
'<circle cx="15" cy="24" r="9"/><circle cx="15" cy="24" r="3"/><path d="M23 20h16a3 3 0 0 1 3 3v2a3 3 0 0 1-3 3H23"/><path d="M34 20v-4M38 20v-4"/>'
),
vise: svg(
'<path d="M8 16h32M8 30h32"/><path d="M12 16v14M36 16v14"/><path d="M8 23h8M32 23h8"/><circle cx="24" cy="23" r="3"/><path d="M16 36h16M20 30v6M28 30v6"/>'
),
breadboard: svg(
'<rect x="8" y="10" width="32" height="28" rx="3"/><path d="M14 16h.01M20 16h.01M26 16h.01M32 16h.01M14 22h.01M20 22h.01M26 22h.01M32 22h.01M14 28h.01M20 28h.01M26 28h.01M32 28h.01M14 34h.01M20 34h.01M26 34h.01M32 34h.01"/>'
),
clamp: svg(
'<path d="M14 8v32"/><path d="M14 10h20v8h-8M14 38h20v-8h-8"/><path d="M26 18v12"/><path d="M34 12h6M34 36h6"/>'
),
caliper: svg(
'<path d="M6 14h36v8H6z"/><path d="M12 22v16M12 38h6v-10M32 22v10h6"/><path d="M14 18h.01M20 18h.01M26 18h.01M32 18h.01"/>'
),
circsaw: svg(
'<circle cx="20" cy="28" r="11"/><circle cx="20" cy="28" r="3"/><path d="M20 17l3-3 3 3 3-2 2 3"/><path d="M28 14h10v8h-6"/><path d="M6 39h36"/>'
),
drillpress: svg(
'<path d="M12 8h20"/><path d="M16 8v6h12V8"/><path d="M22 14h4v10h-4z"/><path d="M24 24v6"/><path d="M14 34h20v4H14z"/><path d="M24 8V4"/><path d="M34 14l6 4"/>'
),
heatgun: svg(
'<rect x="8" y="16" width="22" height="10" rx="4"/><path d="M30 18h6M30 24h6"/><path d="M14 26v12h8V26"/><path d="M40 16v10M44 18v6"/>'
),
hacksaw: svg(
'<path d="M8 14h30a4 4 0 0 1 4 4v6"/><path d="M8 14v10a4 4 0 0 0 4 4h4"/><path d="M16 28v6h6"/><path d="M20 24h22"/><path d="M24 24l2 3 3-3 3 3 3-3 3 3"/>'
),
jigsaw: svg(
'<path d="M10 14h20a6 6 0 0 1 6 6v4H14a4 4 0 0 1-4-4z"/><path d="M14 14v-4h10v4"/><path d="M22 24v14"/><path d="M22 30l3 2-3 2 3 2"/><path d="M8 40h24"/>'
),
laserlevel: svg(
'<rect x="14" y="14" width="20" height="16" rx="3"/><circle cx="24" cy="22" r="4"/><path d="M2 22h8M38 22h8"/><path d="M20 30l-4 10M28 30l4 10"/>'
),
multimeter: svg(
'<rect x="12" y="6" width="24" height="34" rx="4"/><rect x="16" y="10" width="16" height="8" rx="1"/><circle cx="24" cy="28" r="6"/><path d="M24 28l3-4"/><path d="M18 40v4M30 40v4"/>'
),
oscilloscope: svg(
'<rect x="6" y="10" width="36" height="24" rx="3"/><path d="M10 24h5l3-8 4 14 4-10 3 4h9"/><path d="M16 40h16M20 34v6M28 34v6"/>'
),
soldering: svg(
'<path d="M6 42l10-10"/><path d="M16 32l6-6a4 4 0 0 1 5-1l3-3"/><path d="M28 24l8-8"/><path d="M34 8l6 6-4 4-6-6z"/><path d="M40 30c0-3 3-3 3-6"/>'
),
studfinder: svg(
'<rect x="16" y="6" width="16" height="34" rx="6"/><circle cx="24" cy="16" r="4"/><path d="M24 26v8"/><path d="M20 30h8"/><path d="M8 14v20M40 14v20"/>'
),
tape: svg(
'<rect x="8" y="12" width="26" height="22" rx="6"/><circle cx="21" cy="23" r="6"/><circle cx="21" cy="23" r="1.5"/><path d="M34 30h8v6h-10"/><path d="M42 30v-3"/>'
),
torque: svg(
'<circle cx="12" cy="16" r="7"/><circle cx="12" cy="16" r="2.5"/><path d="M17 21l22 18"/><path d="M36 36l4 3-2 4"/><path d="M22 22l3-3"/>'
),
wirestripper: svg(
'<path d="M24 10a6 6 0 1 1-6 8"/><path d="M20 20L10 40"/><path d="M28 20l10 20"/><path d="M22 14h4"/><path d="M14 32h6M28 32h6"/>'
),
};
/* ---------- Tool database (fictional shelf refs & pricing) ---------- */
const TOOLS = [
{
ref: "T-01", name: "Angle Grinder", cat: "power", icon: "grinder",
def: "Handheld spinning disc that cuts, grinds and polishes metal, tile and masonry depending on the wheel you mount.",
uses: ["cutting rebar", "rust removal", "sharpening"],
safety: "Sparks fly far — full face shield, gloves, and never remove the guard.",
alt: "For light rust, a wire-brush drill attachment gets you 80% there.",
facts: { weight: "1.8 kg", price: "$35–120", skill: "INTERMEDIATE" },
},
{
ref: "T-02", name: "Bar Clamp", cat: "hand", icon: "clamp",
def: "Sliding-jaw clamp on a steel bar that squeezes glue-ups and holds workpieces still while you cut or drill.",
uses: ["glue-ups", "holding stock", "assembly"],
alt: "Ratchet straps or heavy books can stand in for panel glue-ups.",
facts: { weight: "0.6 kg", price: "$8–30", skill: "BEGINNER" },
},
{
ref: "T-03", name: "Bench Vise", cat: "hand", icon: "vise",
def: "Jaw-and-screw workholder bolted to the bench; the third hand every filing, sawing and bending job wants.",
uses: ["filing", "metal bending", "sawing support"],
alt: "A pair of bar clamps to the bench edge covers most light jobs.",
facts: { weight: "9.5 kg", price: "$40–150", skill: "BEGINNER" },
},
{
ref: "T-04", name: "Breadboard", cat: "electronics", icon: "breadboard",
def: "Solderless prototyping board — push components and jumper wires into spring clips to test a circuit in minutes.",
uses: ["prototyping", "learning circuits", "sensor tests"],
alt: "Alligator-clip leads work for very small circuits (3–4 parts).",
facts: { weight: "85 g", price: "$4–12", skill: "BEGINNER" },
},
{
ref: "T-05", name: "Caliper (Digital)", cat: "measuring", icon: "caliper",
def: "Sliding jaws with an LCD readout that measure outside, inside and depth dimensions to 0.01 mm.",
uses: ["part fitting", "3D-print checks", "bolt sizing"],
alt: "A steel rule plus patience — accept ±0.5 mm instead of ±0.01.",
facts: { weight: "180 g", price: "$15–60", skill: "BEGINNER" },
},
{
ref: "T-06", name: "Circular Saw", cat: "power", icon: "circsaw",
def: "Handheld saw with a spinning toothed blade; the fastest way to rip sheet goods and crosscut lumber on site.",
uses: ["ripping plywood", "crosscuts", "deck boards"],
safety: "Set blade depth to material + 5 mm and keep the cord behind the cut line.",
alt: "No track saw? Clamp a straight board as a fence for clean rips.",
facts: { weight: "3.6 kg", price: "$50–180", skill: "INTERMEDIATE" },
},
{
ref: "T-07", name: "Drill Press", cat: "power", icon: "drillpress",
def: "Bench-mounted drill with a lever-fed spindle — perfectly perpendicular holes at repeatable depths.",
uses: ["square holes", "metal drilling", "repeat depth"],
safety: "Clamp the workpiece — spinning bits grab loose stock and whip it around.",
alt: "No drill press? A drill guide jig keeps a hand drill at true 90°.",
facts: { weight: "22 kg", price: "$120–400", skill: "INTERMEDIATE" },
},
{
ref: "T-08", name: "Hacksaw", cat: "hand", icon: "hacksaw",
def: "Fine-toothed frame saw for metal — bolts, pipe, threaded rod and the occasional stubborn zip tie.",
uses: ["cutting bolts", "pipe trimming", "plastic stock"],
alt: "A rotary tool with a cutoff wheel is faster on small bolts.",
facts: { weight: "0.5 kg", price: "$8–25", skill: "BEGINNER" },
},
{
ref: "T-09", name: "Heat Gun", cat: "power", icon: "heatgun",
def: "Electric hot-air blower (up to ~600 °C) for shrinking tube, stripping paint, and softening adhesives.",
uses: ["heat-shrink", "paint stripping", "bending PVC"],
safety: "The nozzle stays scorching for minutes after switch-off — park it on its stand.",
alt: "A hair dryer handles heat-shrink tube, not paint or PVC.",
facts: { weight: "0.9 kg", price: "$20–80", skill: "BEGINNER" },
},
{
ref: "T-10", name: "Jigsaw", cat: "power", icon: "jigsaw",
def: "Reciprocating narrow blade that steers through curves and cutouts a circular saw can't touch.",
uses: ["curved cuts", "sink cutouts", "scrollwork"],
safety: "Let the blade stop fully before lifting it out of the kerf.",
alt: "A coping saw does small curves by hand — slower, tighter radius.",
facts: { weight: "2.2 kg", price: "$40–140", skill: "BEGINNER" },
},
{
ref: "T-11", name: "Laser Level", cat: "measuring", icon: "laserlevel",
def: "Self-leveling laser that throws dead-level horizontal and plumb vertical lines across the room.",
uses: ["shelf rows", "tile layout", "gallery walls"],
alt: "A spirit level + chalk line covers single-wall jobs fine.",
facts: { weight: "0.5 kg", price: "$30–150", skill: "BEGINNER" },
},
{
ref: "T-12", name: "Multimeter", cat: "electronics", icon: "multimeter",
def: "The electrical Swiss army knife — measures voltage, current, resistance and continuity on one dial.",
uses: ["continuity", "battery checks", "fault finding"],
safety: "Never measure resistance on a live circuit — kill the power first.",
alt: "A simple voltage tester pen confirms live/dead, nothing more.",
facts: { weight: "320 g", price: "$15–90", skill: "BEGINNER" },
},
{
ref: "T-13", name: "Oscilloscope", cat: "electronics", icon: "oscilloscope",
def: "Draws voltage over time on a screen, revealing signals, noise and glitches a multimeter averages away.",
uses: ["signal debug", "PWM checks", "audio circuits"],
alt: "A $25 USB logic analyzer covers digital-only debugging.",
facts: { weight: "2.8 kg", price: "$250–900", skill: "ADVANCED" },
},
{
ref: "T-14", name: "Soldering Iron", cat: "electronics", icon: "soldering",
def: "Temperature-controlled heated tip that fuses component leads to boards with molten solder.",
uses: ["PCB assembly", "wire joints", "kit builds"],
safety: "350 °C tip, ventilate fumes, and always return it to the stand — never the bench.",
alt: "For wire-to-wire only, crimp connectors need zero heat.",
facts: { weight: "450 g", price: "$20–110", skill: "INTERMEDIATE" },
},
{
ref: "T-15", name: "Stud Finder", cat: "measuring", icon: "studfinder",
def: "Handheld sensor that locates framing studs (and warns about live wires) behind drywall before you drill.",
uses: ["TV mounts", "shelf anchors", "avoiding wires"],
alt: "Knock test + a strong magnet finds drywall screws in a pinch.",
facts: { weight: "160 g", price: "$12–50", skill: "BEGINNER" },
},
{
ref: "T-16", name: "Tape Measure", cat: "measuring", icon: "tape",
def: "Retracting steel blade with a floating hook that self-corrects for inside and outside measurements.",
uses: ["layout", "lumber shopping", "room plans"],
alt: "A folding rule is stiffer for overhead solo measuring.",
facts: { weight: "280 g", price: "$6–25", skill: "BEGINNER" },
},
{
ref: "T-17", name: "Torque Wrench", cat: "hand", icon: "torque",
def: "Click-stop wrench that tightens fasteners to an exact newton-metre spec — no more snapped bolts.",
uses: ["bike builds", "engine work", "spark plugs"],
safety: "Wind it back to zero after use or the spring loses calibration.",
alt: "Hand-tight plus a quarter turn works for non-critical fasteners.",
facts: { weight: "1.4 kg", price: "$30–130", skill: "INTERMEDIATE" },
},
{
ref: "T-18", name: "Wire Stripper", cat: "electronics", icon: "wirestripper",
def: "Gauged jaws that slice insulation and pull it clean off without nicking the copper strands inside.",
uses: ["harness work", "lamp rewiring", "speaker wire"],
alt: "A careful knife roll works — score, flex, slide. Practice on scrap.",
facts: { weight: "140 g", price: "$8–35", skill: "BEGINNER" },
},
];
/* ---------- State ---------- */
let query = "";
let activeCat = "all";
let totdIndex = new Date().getDate() % TOOLS.length;
/* ---------- DOM refs ---------- */
const $ = (sel) => document.querySelector(sel);
const entriesEl = $("#entries");
const alphaBar = $("#alphaBar");
const noResults = $("#noResults");
const searchInput = $("#search");
const clearBtn = $("#clearSearch");
const toastEl = $("#toast");
/* ---------- Toast helper ---------- */
let toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toastEl.classList.remove("is-show"), 2200);
}
/* ---------- Utils ---------- */
const esc = (s) =>
s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
function highlight(text, q) {
const safe = esc(text);
if (!q) return safe;
const rx = new RegExp("(" + q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + ")", "ig");
return safe.replace(rx, "<mark>$1</mark>");
}
const CAT_LABEL = {
hand: "Hand", power: "Power", electronics: "Electronics", measuring: "Measuring",
};
function matches(tool) {
if (activeCat !== "all" && tool.cat !== activeCat) return false;
if (!query) return true;
const hay = (
tool.name + " " + tool.def + " " + tool.uses.join(" ") + " " +
CAT_LABEL[tool.cat] + " " + tool.ref + " " + (tool.alt || "")
).toLowerCase();
return hay.includes(query);
}
/* ---------- Render entries grouped by letter ---------- */
function cardHTML(tool) {
const q = query;
return (
'<article class="tool-card" id="tool-' + tool.ref + '" data-cat="' + tool.cat + '">' +
'<div class="tool-card-top">' +
'<div class="tool-art">' + ICONS[tool.icon] + "</div>" +
'<div class="tool-title-wrap">' +
'<div class="tool-badges">' +
'<span class="tool-ref">' + tool.ref + "</span>" +
'<span class="tool-cat" data-cat="' + tool.cat + '">' + CAT_LABEL[tool.cat] + "</span>" +
"</div>" +
'<h3 class="tool-name">' + highlight(tool.name, q) + "</h3>" +
"</div></div>" +
'<p class="tool-def">' + highlight(tool.def, q) + "</p>" +
'<ul class="use-chips" aria-label="Used for">' +
tool.uses.map((u) => "<li>" + highlight(u, q) + "</li>").join("") +
"</ul>" +
(tool.safety
? '<div class="safety-strip" role="note">' +
'<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
'<path d="M12 3 2 20h20L12 3z"/><path d="M12 10v4M12 17.5h.01"/></svg>' +
"<span><strong>Safety:</strong> " + esc(tool.safety) + "</span></div>"
: "") +
'<p class="alt-line"><span class="alt-arrow">No tool? →</span> <strong>' +
esc(tool.alt) + "</strong></p>" +
"</article>"
);
}
function render() {
const visible = TOOLS.filter(matches);
const byLetter = new Map();
visible.forEach((t) => {
const L = t.name[0].toUpperCase();
if (!byLetter.has(L)) byLetter.set(L, []);
byLetter.get(L).push(t);
});
// remove old sections
entriesEl.querySelectorAll(".letter-section").forEach((n) => n.remove());
const frag = document.createDocumentFragment();
[...byLetter.keys()].sort().forEach((L) => {
const tools = byLetter.get(L);
const sec = document.createElement("section");
sec.className = "letter-section";
sec.id = "letter-" + L;
sec.setAttribute("aria-label", "Tools starting with " + L);
sec.innerHTML =
'<div class="letter-head">' +
'<span class="letter-glyph">' + L + "</span>" +
'<span class="letter-rule"></span>' +
'<span class="letter-count">' + String(tools.length).padStart(2, "0") + " ENTR" +
(tools.length === 1 ? "Y" : "IES") + "</span></div>" +
'<div class="card-grid">' + tools.map(cardHTML).join("") + "</div>";
frag.appendChild(sec);
});
entriesEl.insertBefore(frag, noResults);
noResults.hidden = visible.length > 0;
// stats
$("#statCount").textContent = visible.length;
$("#statLetters").textContent = byLetter.size;
renderAlphaBar(byLetter);
observeSections();
}
/* ---------- Alphabet index bar ---------- */
function renderAlphaBar(byLetter) {
alphaBar.innerHTML = "";
for (let i = 65; i <= 90; i++) {
const L = String.fromCharCode(i);
const btn = document.createElement("button");
btn.type = "button";
btn.className = "alpha-btn";
btn.textContent = L;
const active = byLetter.has(L);
btn.disabled = !active;
if (active) {
btn.setAttribute("aria-label", "Jump to letter " + L);
btn.addEventListener("click", () => {
document.getElementById("letter-" + L)
.scrollIntoView({ behavior: "smooth", block: "start" });
});
}
alphaBar.appendChild(btn);
}
}
/* ---------- Scrollspy: highlight current letter ---------- */
let observer;
function observeSections() {
if (observer) observer.disconnect();
observer = new IntersectionObserver(
(ents) => {
ents.forEach((e) => {
if (!e.isIntersecting) return;
const L = e.target.id.replace("letter-", "");
alphaBar.querySelectorAll(".alpha-btn").forEach((b) => {
b.classList.toggle("is-current", b.textContent === L);
});
});
},
{ rootMargin: "-15% 0px -70% 0px" }
);
entriesEl.querySelectorAll(".letter-section").forEach((s) => observer.observe(s));
}
/* ---------- Search ---------- */
let debounce;
searchInput.addEventListener("input", () => {
clearTimeout(debounce);
debounce = setTimeout(() => {
query = searchInput.value.trim().toLowerCase();
clearBtn.hidden = !query;
render();
}, 120);
});
function resetSearch() {
searchInput.value = "";
query = "";
clearBtn.hidden = true;
render();
searchInput.focus();
}
clearBtn.addEventListener("click", resetSearch);
$("#resetSearch").addEventListener("click", () => {
resetSearch();
toast("SEARCH CLEARED — FULL SHELF RESTORED");
});
searchInput.addEventListener("keydown", (e) => {
if (e.key === "Escape" && searchInput.value) resetSearch();
});
/* ---------- Category filters ---------- */
document.querySelectorAll(".cat-btn").forEach((btn) => {
btn.addEventListener("click", () => {
document.querySelectorAll(".cat-btn").forEach((b) => b.classList.remove("is-on"));
btn.classList.add("is-on");
activeCat = btn.dataset.cat;
render();
if (activeCat !== "all") {
const n = TOOLS.filter((t) => t.cat === activeCat).length;
toast("FILTER: " + activeCat.toUpperCase() + " TOOLS (" + n + ")");
}
});
});
/* ---------- Tool of the day ---------- */
function renderTotd() {
const t = TOOLS[totdIndex];
$("#totdArt").innerHTML = ICONS[t.icon];
$("#totdName").textContent = t.name;
$("#totdDef").textContent = t.def;
$("#totdWeight").textContent = t.facts.weight;
$("#totdPrice").textContent = t.facts.price;
$("#totdSkill").textContent = t.facts.skill;
$("#totdRef").textContent = t.ref + " / " + t.cat.toUpperCase();
}
$("#shuffleTotd").addEventListener("click", () => {
totdIndex = (totdIndex + 1 + Math.floor(Math.random() * (TOOLS.length - 1))) % TOOLS.length;
renderTotd();
toast("TOOL OF THE DAY → " + TOOLS[totdIndex].name.toUpperCase());
});
$("#totdJump").addEventListener("click", () => {
const t = TOOLS[totdIndex];
// clear filters so the entry is guaranteed visible
if (query || activeCat !== "all") {
searchInput.value = "";
query = "";
clearBtn.hidden = true;
activeCat = "all";
document.querySelectorAll(".cat-btn").forEach((b) =>
b.classList.toggle("is-on", b.dataset.cat === "all"));
render();
}
const card = document.getElementById("tool-" + t.ref);
if (card) {
card.scrollIntoView({ behavior: "smooth", block: "center" });
card.classList.add("is-flash");
setTimeout(() => card.classList.remove("is-flash"), 1600);
}
});
/* ---------- Init ---------- */
render();
renderTotd();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DIY — Tool Encyclopedia</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 ============ -->
<header class="hero" role="banner">
<div class="hero-inner">
<div class="hero-top">
<span class="brand-badge" aria-hidden="true">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.7 6.3a4 4 0 0 0-5.4 5.4L4 17l3 3 5.3-5.3a4 4 0 0 0 5.4-5.4l-2.5 2.5-2.2-2.2 2.5-2.5z"/>
</svg>
</span>
<span class="brand-name">Workbench Wiki</span>
<span class="mono-chip">VOL.03 · REV-B</span>
</div>
<h1 class="hero-title">Tool Encyclopedia</h1>
<p class="hero-sub">Every tool on the pegboard, from Angle Grinder to Wire Stripper — what it is, what it's for, and what to grab when you don't own one.</p>
<div class="hero-controls">
<div class="search-wrap">
<svg class="search-ico" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
<circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/>
</svg>
<input id="search" type="search" placeholder="Search tools, uses, categories…" aria-label="Search the tool encyclopedia" autocomplete="off" />
<button id="clearSearch" class="clear-btn" type="button" aria-label="Clear search" hidden>×</button>
</div>
<div class="cat-filters" role="group" aria-label="Filter by category">
<button class="cat-btn is-on" data-cat="all" type="button">All</button>
<button class="cat-btn" data-cat="hand" type="button">Hand</button>
<button class="cat-btn" data-cat="power" type="button">Power</button>
<button class="cat-btn" data-cat="electronics" type="button">Electronics</button>
<button class="cat-btn" data-cat="measuring" type="button">Measuring</button>
</div>
</div>
<div class="hero-stats mono" id="heroStats" aria-live="polite">
<span><strong id="statCount">0</strong> TOOLS</span>
<span class="dot" aria-hidden="true"></span>
<span><strong id="statLetters">0</strong> SECTIONS</span>
<span class="dot" aria-hidden="true"></span>
<span>4 CATEGORIES</span>
</div>
</div>
</header>
<!-- ============ A–Z INDEX BAR ============ -->
<nav class="alpha-bar" id="alphaBar" aria-label="Alphabet index"></nav>
<!-- ============ MAIN LAYOUT ============ -->
<main class="layout">
<div class="entries" id="entries" aria-live="polite">
<!-- letter sections injected by script.js -->
<div class="no-results" id="noResults" hidden>
<div class="no-results-art mono">[ Ø ]</div>
<h3>Nothing on this shelf</h3>
<p>No tools match your search. Try “saw”, “clamp” or “solder”.</p>
<button class="btn-orange" id="resetSearch" type="button">Reset search</button>
</div>
</div>
<!-- ============ SIDE PANEL ============ -->
<aside class="side" aria-label="Tool of the day">
<section class="totd" id="totd">
<div class="totd-head">
<span class="totd-eyebrow mono">TOOL OF THE DAY</span>
<button class="shuffle-btn" id="shuffleTotd" type="button" title="Show another tool" aria-label="Show another tool of the day">
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M16 3h5v5"/><path d="M21 3 9 15"/><path d="M3 8v8a5 5 0 0 0 5 5h8"/>
</svg>
</button>
</div>
<div class="totd-art" id="totdArt" aria-hidden="true"></div>
<h2 class="totd-name" id="totdName">—</h2>
<p class="totd-def" id="totdDef"></p>
<dl class="totd-facts mono">
<div><dt>WEIGHT</dt><dd id="totdWeight">—</dd></div>
<div><dt>PRICE RANGE</dt><dd id="totdPrice">—</dd></div>
<div><dt>SKILL</dt><dd id="totdSkill">—</dd></div>
<div><dt>REF</dt><dd id="totdRef">—</dd></div>
</dl>
<button class="btn-orange totd-jump" id="totdJump" type="button">View entry ↓</button>
</section>
<section class="side-note">
<h3 class="side-note-title">Reading the entries</h3>
<ul class="legend">
<li><span class="legend-swatch swatch-safety" aria-hidden="true"></span>Amber strip = read before first use</li>
<li><span class="legend-swatch swatch-alt" aria-hidden="true"></span>“No tool? →” = budget stand-in</li>
<li><span class="legend-swatch swatch-mono mono" aria-hidden="true">T-00</span>Mono badge = shelf reference</li>
</ul>
</section>
</aside>
</main>
<footer class="foot">
<span class="mono">WORKBENCH WIKI — MAINTAINED BY THE RIVET & PINE MAKERSPACE (FICTIONAL)</span>
<span>Measure twice. Search once.</span>
</footer>
<div class="toast mono" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Tool Encyclopedia
An encyclopedia-style reference page for a fictional makerspace wiki. A blueprint-grid hero holds the search input, four category filter pills (hand, power, electronics, measuring) and live counters, while a sticky A–Z index bar sits below it — only letters with entries are clickable, each smooth-scrolls to its section, and an IntersectionObserver scrollspy highlights the letter currently in view.
Eighteen tool entries are rendered from a JavaScript data array and grouped under mono letter headings. Each card carries a hand-drawn-style SVG line-art icon, a shelf-reference badge, a category chip, a one-line definition, “used for” chips, an amber safety strip where the tool warrants one, and a dashed “No tool? →” line suggesting a budget alternative. The search input filters every field live with a small debounce and wraps matches in highlighted mark tags; an empty state with a reset button appears when nothing survives the filter.
The side panel features a Tool of the Day picked from the calendar date, showing a larger illustration and quick facts (weight, price range, skill level, shelf ref) in JetBrains Mono. A shuffle button rotates the pick, and “View entry” clears any active filters, scrolls to the matching card and flashes its border. Toast notifications confirm filter changes and shuffles.
Illustrative UI only — always follow real safety guidance when working with tools, electronics, or repairs.