DIY — Learn Guide
A dark electronics-shop learn guide for a fictional maker store, SOLDERBOX Learn, walking through a desk weather lamp build. A sticky sidebar lists seven guide sections and swaps the main pane without a reload while a progress bar and dot row track how far you have read. Panes cover an illustrated overview, a parts list with SVG component plates and add-to-cart rows, a hoverable wiring diagram, numbered assembly steps, hand-highlighted code with copy and download rows, and a troubleshooting accordion.
MCP
Code
:root{
--bg:#101418;
--surface:#1a2028;
--surface-2:#212932;
--plate:#ffffff;
--blue:#2b8cff;
--blue-d:#1b6ed6;
--pink:#ff3d81;
--lime:#b8ff3d;
--txt:#eef2f6;
--txt-2:#b6c2ce;
--muted:#7d8b99;
--line:rgba(238,242,246,0.12);
--line-2:rgba(238,242,246,0.22);
--ok:#3ddc97;
--warn:#ffc14d;
--danger:#ff5c5c;
--r-sm:6px; --r-md:12px; --r-lg:18px;
--sh-1:0 1px 2px rgba(0,0,0,.5);
--sh-2:0 10px 28px rgba(0,0,0,.42);
--sh-3:0 22px 60px rgba(0,0,0,.55);
}
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
margin:0;
font-family:Inter,system-ui,-apple-system,"Segoe UI",sans-serif;
line-height:1.5;
color:var(--txt);
background:
radial-gradient(1100px 620px at 15% -10%, rgba(43,140,255,.16), transparent 62%),
radial-gradient(900px 540px at 92% 4%, rgba(255,61,129,.12), transparent 60%),
var(--bg);
background-attachment:fixed;
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
}
.mono{font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace}
h1,h2,h3{margin:0;line-height:1.2;letter-spacing:-.02em}
p{margin:0}
a{color:inherit}
img,svg{max-width:100%}
button{font:inherit;color:inherit}
:focus-visible{outline:2px solid var(--lime);outline-offset:3px;border-radius:4px}
.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.skip{position:absolute;left:-999px;top:8px;background:var(--lime);color:#101418;padding:10px 16px;border-radius:var(--r-sm);font-weight:700;z-index:99}
.skip:focus{left:12px}
.wrap{width:min(1240px,100% - 40px);margin-inline:auto}
/* ---------- topbar ---------- */
.topbar{
position:sticky;top:0;z-index:40;
background:rgba(16,20,24,.82);
backdrop-filter:blur(12px);
border-bottom:1px solid var(--line);
}
.topbar-in{display:flex;align-items:center;gap:26px;height:64px}
.brand{display:flex;align-items:center;gap:10px;text-decoration:none;font-weight:800;letter-spacing:-.02em}
.brand-mark svg{width:32px;height:32px;display:block}
.brand-txt em{font-style:normal;color:var(--blue)}
.topnav{display:flex;gap:4px;margin-left:8px}
.topnav a{
text-decoration:none;font-size:.9rem;font-weight:600;color:var(--txt-2);
padding:8px 12px;border-radius:999px;transition:background .16s,color .16s;
}
.topnav a:hover{background:var(--surface);color:var(--txt)}
.topnav a.on{background:rgba(43,140,255,.16);color:var(--blue)}
.topbar-right{margin-left:auto}
.cart-chip{
font-size:.82rem;font-weight:600;padding:8px 14px;border-radius:999px;
border:1px solid var(--line-2);background:var(--surface);
font-family:"JetBrains Mono",monospace;
}
.cart-chip b{color:var(--lime)}
.cart-chip.pop{animation:pop .34s ease}
@keyframes pop{0%{transform:scale(1)}40%{transform:scale(1.13)}100%{transform:scale(1)}}
/* ---------- layout ---------- */
.layout{display:grid;grid-template-columns:290px minmax(0,1fr);gap:34px;padding:34px 0 80px;align-items:start}
/* ---------- sidebar ---------- */
.sidebar{position:sticky;top:88px;display:flex;flex-direction:column;gap:16px}
.side-card{
background:var(--surface);border:1px solid var(--line);
border-radius:var(--r-lg);padding:18px;box-shadow:var(--sh-2);
}
.side-kicker{
font-family:"JetBrains Mono",monospace;font-size:.68rem;letter-spacing:.14em;
text-transform:uppercase;color:var(--muted);margin-bottom:12px;
}
.seclist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:3px}
.secbtn{
width:100%;display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:10px;
background:none;border:0;padding:9px 11px;border-radius:var(--r-md);cursor:pointer;
text-align:left;transition:background .16s,color .16s,transform .16s;
border-left:3px solid transparent;
}
.secbtn:hover{background:var(--surface-2)}
.secnum{font-family:"JetBrains Mono",monospace;font-size:.72rem;color:var(--muted)}
.secname{font-size:.92rem;font-weight:600;color:var(--txt-2)}
.secmin{font-family:"JetBrains Mono",monospace;font-size:.68rem;color:var(--muted)}
.secbtn.is-active{background:rgba(43,140,255,.14);border-left-color:var(--blue)}
.secbtn.is-active .secname{color:var(--txt)}
.secbtn.is-active .secnum{color:var(--blue)}
.secbtn.is-read .secnum::after{content:"·";color:var(--lime);margin-left:2px}
.side-foot{margin-top:14px;padding-top:14px;border-top:1px solid var(--line)}
.progress-wrap{display:flex;align-items:center;gap:10px}
.progress-line{flex:1;height:6px;border-radius:999px;background:var(--surface-2);overflow:hidden}
.progress-line i{display:block;height:100%;width:14%;border-radius:999px;background:linear-gradient(90deg,var(--blue),var(--lime));transition:width .38s cubic-bezier(.4,0,.2,1)}
.progress-wrap .mono{font-size:.72rem;color:var(--muted)}
.side-note{font-size:.76rem;color:var(--muted);margin-top:10px}
.side-kit h3{font-size:1.02rem;margin-bottom:4px}
.kit-desc{font-size:.82rem;color:var(--txt-2);margin-bottom:12px}
.kit-row{display:flex;align-items:center;justify-content:space-between;gap:10px}
.price{font-family:"JetBrains Mono",monospace;font-weight:700;font-size:1.1rem;color:var(--lime)}
.kit-sku{font-size:.68rem;color:var(--muted);margin-top:10px}
/* ---------- buttons ---------- */
.btn{
border:1px solid transparent;border-radius:999px;padding:9px 16px;
font-size:.84rem;font-weight:700;cursor:pointer;
transition:transform .14s,background .16s,border-color .16s,box-shadow .16s;
}
.btn:active{transform:translateY(1px) scale(.98)}
.btn-blue{background:var(--blue);color:#fff;box-shadow:0 6px 18px rgba(43,140,255,.32)}
.btn-blue:hover{background:var(--blue-d)}
.btn-lime{background:var(--lime);color:#101418}
.btn-lime:hover{filter:brightness(1.08)}
.btn-ghost{background:transparent;border-color:var(--line-2);color:var(--txt-2)}
.btn-ghost:hover{border-color:var(--blue);color:var(--blue);background:rgba(43,140,255,.1)}
.btn.is-added{background:rgba(61,220,151,.16);border-color:var(--ok);color:var(--ok)}
/* ---------- guide head ---------- */
.content{min-width:0}
.guide-head{margin-bottom:26px}
.crumbs{font-size:.72rem;color:var(--muted);margin-bottom:14px}
.crumbs a{text-decoration:none}
.crumbs a:hover{color:var(--blue)}
h1{font-size:clamp(1.9rem,4vw,2.85rem);font-weight:800;margin-bottom:12px}
.lede{font-size:1.05rem;color:var(--txt-2);max-width:66ch}
.meta{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:20px}
.chip.author{display:flex;align-items:center;gap:9px;background:var(--surface);border:1px solid var(--line);padding:5px 14px 5px 5px;border-radius:999px}
.ava{
width:32px;height:32px;border-radius:50%;display:grid;place-items:center;
font-family:"JetBrains Mono",monospace;font-size:.72rem;font-weight:700;color:#101418;
background:linear-gradient(135deg,var(--lime),var(--blue));
}
.a-txt{display:flex;flex-direction:column;line-height:1.15}
.a-txt b{font-size:.84rem}
.a-txt em{font-style:normal;font-size:.7rem;color:var(--muted)}
.badge{font-size:.74rem;font-weight:700;padding:6px 12px;border-radius:999px;border:1px solid}
.badge-diff{color:var(--warn);border-color:rgba(255,193,77,.4);background:rgba(255,193,77,.1)}
.badge-time{color:var(--blue);border-color:rgba(43,140,255,.4);background:rgba(43,140,255,.1)}
.dot{width:4px;height:4px;border-radius:50%;background:var(--muted)}
.upd{font-size:.74rem;color:var(--muted)}
.fav{
margin-left:auto;display:inline-flex;align-items:center;gap:8px;
background:var(--surface);border:1px solid var(--line-2);border-radius:999px;
padding:8px 15px;font-size:.82rem;font-weight:600;cursor:pointer;transition:.16s;
}
.fav svg{width:16px;height:16px;fill:none;stroke:var(--txt-2);stroke-width:1.8}
.fav:hover{border-color:var(--pink)}
.fav[aria-pressed="true"]{border-color:var(--pink);color:var(--pink);background:rgba(255,61,129,.12)}
.fav[aria-pressed="true"] svg{fill:var(--pink);stroke:var(--pink)}
.dots{display:flex;gap:6px;margin-top:22px}
.dots i{height:4px;flex:1;border-radius:999px;background:var(--surface-2);transition:background .3s,transform .3s}
.dots i.on{background:var(--blue)}
.dots i.cur{background:var(--lime);transform:scaleY(2)}
/* ---------- panes ---------- */
.pane{display:none}
.pane.is-active{display:block;animation:fade .28s ease}
@keyframes fade{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.pane h2{font-size:1.6rem;font-weight:800;margin-bottom:10px}
.pane h3{font-size:1.05rem;font-weight:700;margin:26px 0 8px}
.pane-lede{color:var(--txt-2);max-width:64ch;margin-bottom:22px}
.hero-plate{
background:var(--plate);border-radius:var(--r-lg);padding:14px;margin-bottom:28px;
box-shadow:var(--sh-3);border:1px solid var(--line);
background-image:linear-gradient(#eef2f6 1px,transparent 1px),linear-gradient(90deg,#eef2f6 1px,transparent 1px);
background-size:24px 24px;background-color:#fff;
}
.hero-plate svg{display:block;width:100%;height:auto;background:#151b22;border-radius:var(--r-md)}
.checklist{list-style:none;margin:0 0 26px;padding:0;display:grid;gap:10px}
.checklist li{
position:relative;padding:13px 16px 13px 44px;background:var(--surface);
border:1px solid var(--line);border-radius:var(--r-md);color:var(--txt-2);font-size:.92rem;
transition:border-color .16s,transform .16s;
}
.checklist li:hover{border-color:var(--line-2);transform:translateX(3px)}
.checklist li b{color:var(--txt)}
.checklist li::before{
content:"";position:absolute;left:16px;top:17px;width:15px;height:15px;border-radius:4px;
background:var(--lime);
clip-path:polygon(14% 47%,0 61%,40% 100%,100% 22%,86% 8%,38% 71%);
}
.callout{border-radius:var(--r-md);padding:15px 18px;margin:24px 0;border-left:3px solid;background:var(--surface)}
.callout b{display:block;font-size:.88rem;margin-bottom:4px}
.callout p{font-size:.88rem;color:var(--txt-2)}
.callout-blue{border-color:var(--blue);background:rgba(43,140,255,.1)}
.callout-pink{border-color:var(--pink);background:rgba(255,61,129,.1)}
.callout-lime{border-color:var(--lime);background:rgba(184,255,61,.09)}
.statgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-top:26px}
.stat{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md);padding:16px;text-align:center}
.s-n{display:block;font-size:1.45rem;font-weight:700;color:var(--lime)}
.s-l{font-size:.74rem;color:var(--muted);text-transform:uppercase;letter-spacing:.08em}
/* ---------- parts ---------- */
.partbar{
display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;
background:linear-gradient(90deg,rgba(43,140,255,.16),rgba(255,61,129,.12));
border:1px solid var(--line-2);border-radius:var(--r-md);padding:12px 16px;margin-bottom:16px;
}
.partbar .mono{font-size:.9rem;font-weight:700}
.parts{list-style:none;margin:0;padding:0;display:grid;gap:10px}
.part{
display:grid;grid-template-columns:64px 1fr auto auto;align-items:center;gap:16px;
background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md);padding:12px 16px;
transition:border-color .16s,transform .16s,box-shadow .16s;
}
.part:hover{border-color:rgba(43,140,255,.5);transform:translateY(-2px);box-shadow:var(--sh-2)}
.plate{width:64px;height:64px;border-radius:var(--r-md);background:var(--plate);display:grid;place-items:center;box-shadow:var(--sh-1)}
.plate svg{width:46px;height:46px}
.p-txt{display:flex;flex-direction:column;gap:3px;min-width:0}
.p-txt b{font-size:.95rem}
.p-txt em{font-style:normal;font-size:.72rem;color:var(--muted)}
.p-price{font-weight:700;color:var(--lime)}
/* ---------- wiring ---------- */
.diagram-plate{
background:#fff;border-radius:var(--r-lg);padding:12px;box-shadow:var(--sh-3);margin-bottom:22px;
background-image:linear-gradient(rgba(43,140,255,.1) 1px,transparent 1px),linear-gradient(90deg,rgba(43,140,255,.1) 1px,transparent 1px);
background-size:20px 20px;
}
.diagram-plate svg{display:block;width:100%;height:auto;background:#151b22;border-radius:var(--r-md)}
.wires path{transition:stroke-width .2s,opacity .2s,filter .2s}
#wireSvg.dim .wires path{opacity:.22}
#wireSvg .wires path.hot{opacity:1;stroke-width:7;filter:drop-shadow(0 0 8px currentColor)}
.tablewrap{overflow-x:auto;border-radius:var(--r-md);border:1px solid var(--line)}
table.conn{width:100%;border-collapse:collapse;font-size:.88rem;background:var(--surface);min-width:520px}
table.conn th{
text-align:left;font-size:.7rem;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);
padding:12px 16px;border-bottom:1px solid var(--line);font-family:"JetBrains Mono",monospace;
}
table.conn td{padding:12px 16px;border-bottom:1px solid var(--line);color:var(--txt-2)}
table.conn tbody tr:last-child td{border-bottom:0}
table.conn tbody tr[data-wire]{cursor:pointer;transition:background .16s}
table.conn tbody tr[data-wire]:hover,table.conn tbody tr[data-wire]:focus-visible{background:rgba(43,140,255,.12)}
.sw{display:inline-block;width:12px;height:12px;border-radius:3px;background:var(--sw);margin-right:8px;vertical-align:-1px}
/* ---------- assembly ---------- */
.steps{list-style:none;counter-reset:s;margin:0;padding:0;display:grid;gap:12px}
.steps li{
counter-increment:s;position:relative;padding:16px 18px 16px 62px;
background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md);
}
.steps li::before{
content:counter(s,decimal-leading-zero);position:absolute;left:18px;top:16px;
font-family:"JetBrains Mono",monospace;font-weight:700;font-size:.88rem;
color:#101418;background:var(--blue);width:28px;height:28px;border-radius:8px;display:grid;place-items:center;
}
.steps h3{margin:0 0 4px;font-size:.98rem}
.steps p{font-size:.88rem;color:var(--txt-2)}
/* ---------- code ---------- */
.codecard{border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden;background:#0c1015;box-shadow:var(--sh-2)}
.codebar{display:flex;align-items:center;gap:12px;padding:10px 14px;background:var(--surface);border-bottom:1px solid var(--line)}
.codebar .file{font-size:.82rem;font-weight:600}
.codebar .tagline{font-size:.72rem;color:var(--muted);margin-right:auto}
pre.code{
margin:0;padding:16px 16px 16px 0;overflow-x:auto;
font-family:"JetBrains Mono",monospace;font-size:.8rem;line-height:1.7;
counter-reset:line;
}
pre.code code{display:block;white-space:pre}
.ln::before{
counter-increment:line;content:counter(line);
display:inline-block;width:44px;padding-right:14px;text-align:right;
color:#46525f;user-select:none;
}
.code .k{color:var(--pink);font-weight:600}
.code .n{color:var(--txt)}
.code .f{color:var(--blue)}
.code .num{color:var(--lime)}
.code .s{color:#ffc14d}
.code .c{color:var(--muted);font-style:italic}
.code .b{color:var(--pink)}
.downloads{list-style:none;margin:18px 0 0;padding:0;display:grid;gap:10px}
.downloads li{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:14px;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md);padding:12px 16px;transition:border-color .16s}
.downloads li:hover{border-color:var(--line-2)}
.dl-ico{width:42px;height:42px;border-radius:10px;display:grid;place-items:center;font-size:.68rem;font-weight:700;color:#101418;background:linear-gradient(135deg,var(--blue),var(--lime))}
.dl-t{display:flex;flex-direction:column}
.dl-t b{font-size:.9rem}
.dl-t em{font-style:normal;font-size:.72rem;color:var(--muted)}
/* ---------- usage ---------- */
.legend{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:8px}
.lg{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md);padding:16px}
.lg i{display:block;width:100%;height:8px;border-radius:999px;background:var(--c);box-shadow:0 0 18px var(--c);margin-bottom:12px}
.lg b{display:block;font-size:.92rem;margin-bottom:3px}
.lg span{font-size:.82rem;color:var(--txt-2)}
/* ---------- accordion ---------- */
.acc{display:grid;gap:8px}
.acc-item{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md);overflow:hidden}
.acc-item h3{margin:0;font-size:1rem}
.acc-btn{
width:100%;display:flex;align-items:center;justify-content:space-between;gap:14px;
background:none;border:0;padding:15px 18px;cursor:pointer;text-align:left;
font-size:.93rem;font-weight:600;transition:background .16s;
}
.acc-btn:hover{background:var(--surface-2)}
.acc-i{position:relative;width:14px;height:14px;flex:none}
.acc-i::before,.acc-i::after{content:"";position:absolute;background:var(--blue);border-radius:2px;transition:transform .24s}
.acc-i::before{left:0;top:6px;width:14px;height:2px}
.acc-i::after{left:6px;top:0;width:2px;height:14px}
.acc-btn[aria-expanded="true"]{color:var(--blue)}
.acc-btn[aria-expanded="true"] .acc-i::after{transform:rotate(90deg) scaleX(0)}
.acc-body{max-height:0;overflow:hidden;transition:max-height .3s cubic-bezier(.4,0,.2,1)}
.acc-body p{padding:0 18px 16px;font-size:.88rem;color:var(--txt-2)}
/* ---------- page nav ---------- */
.pagenav{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:40px;padding-top:26px;border-top:1px solid var(--line)}
.pnav{
background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md);
padding:14px 18px;text-align:left;cursor:pointer;display:flex;flex-direction:column;gap:3px;
transition:border-color .16s,transform .16s;
}
.pnav:hover:not(:disabled){border-color:var(--blue);transform:translateY(-2px)}
.pnav:disabled{opacity:.38;cursor:not-allowed}
.pnav-next{text-align:right;align-items:flex-end}
.pn-k{font-size:.68rem;letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}
.pn-t{font-size:.98rem;font-weight:700}
/* ---------- toast ---------- */
.toast{
position:fixed;left:50%;bottom:26px;transform:translate(-50%,140%);
background:var(--surface-2);border:1px solid var(--line-2);color:var(--txt);
padding:12px 20px;border-radius:999px;font-size:.86rem;font-weight:600;
box-shadow:var(--sh-3);z-index:80;transition:transform .32s cubic-bezier(.4,0,.2,1);
max-width:calc(100% - 32px);text-align:center;
}
.toast.show{transform:translate(-50%,0)}
/* ---------- responsive ---------- */
@media (max-width:1040px){
.layout{grid-template-columns:1fr;gap:24px}
.sidebar{position:static}
.seclist{flex-direction:row;flex-wrap:wrap}
.secbtn{width:auto;grid-template-columns:auto auto;border-left:0;border-bottom:3px solid transparent}
.secbtn.is-active{border-left:0;border-bottom-color:var(--blue)}
.secmin{display:none}
.statgrid{grid-template-columns:repeat(2,1fr)}
.legend{grid-template-columns:1fr}
}
@media (max-width:520px){
.wrap{width:calc(100% - 24px)}
.topnav{display:none}
.topbar-in{gap:12px}
.layout{padding:20px 0 56px}
h1{font-size:1.65rem}
.lede{font-size:.95rem}
.fav{margin-left:0;width:100%;justify-content:center}
.part{grid-template-columns:48px 1fr;grid-template-areas:"plate txt" "price btn";row-gap:10px}
.part .plate{grid-area:plate;width:48px;height:48px}
.part .plate svg{width:34px;height:34px}
.p-txt{grid-area:txt}
.p-price{grid-area:price;align-self:center}
.part .add{grid-area:btn;justify-self:end}
.statgrid{grid-template-columns:repeat(2,1fr)}
.downloads li{grid-template-columns:auto 1fr;row-gap:10px}
.downloads li .btn{grid-column:1 / -1}
.pagenav{grid-template-columns:1fr}
.pnav-next{text-align:left;align-items:flex-start}
pre.code{font-size:.72rem}
.ln::before{width:32px;padding-right:10px}
}
@media (prefers-reduced-motion:reduce){
*{animation-duration:.01ms !important;transition-duration:.01ms !important}
}(function () {
"use strict";
var ORDER = ["overview", "parts", "wiring", "assembly", "code", "usage", "trouble"];
var LABEL = {
overview: "Overview",
parts: "Parts",
wiring: "Wiring",
assembly: "Assembly",
code: "Code",
usage: "Usage",
trouble: "Troubleshooting"
};
var $ = function (s, r) { return (r || document).querySelector(s); };
var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
/* ---------- toast ---------- */
var toastEl = $("#toast");
var toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove("show"); }, 2200);
}
/* ---------- section switching ---------- */
var tabs = $$(".secbtn");
var dotsWrap = $("#dots");
var progFill = $("#progFill");
var progLabel = $("#progLabel");
var prevBtn = $("#prevBtn");
var nextBtn = $("#nextBtn");
var prevTxt = $("#prevTxt");
var nextTxt = $("#nextTxt");
var read = { overview: true };
var current = "overview";
ORDER.forEach(function () {
var i = document.createElement("i");
dotsWrap.appendChild(i);
});
var dots = $$("i", dotsWrap);
function show(id, focusPane) {
if (ORDER.indexOf(id) === -1) return;
current = id;
read[id] = true;
tabs.forEach(function (t) {
var on = t.dataset.pane === id;
t.classList.toggle("is-active", on);
t.classList.toggle("is-read", !!read[t.dataset.pane]);
t.setAttribute("aria-selected", on ? "true" : "false");
t.tabIndex = on ? 0 : -1;
});
$$(".pane").forEach(function (p) {
var on = p.id === "pane-" + id;
p.classList.toggle("is-active", on);
p.hidden = !on;
});
var idx = ORDER.indexOf(id);
dots.forEach(function (d, i) {
d.classList.toggle("on", !!read[ORDER[i]]);
d.classList.toggle("cur", i === idx);
});
progFill.style.width = ((idx + 1) / ORDER.length) * 100 + "%";
progLabel.textContent = (idx + 1) + " / " + ORDER.length;
prevBtn.disabled = idx === 0;
nextBtn.disabled = idx === ORDER.length - 1;
prevTxt.textContent = idx === 0 ? "—" : LABEL[ORDER[idx - 1]];
nextTxt.textContent = idx === ORDER.length - 1 ? "—" : LABEL[ORDER[idx + 1]];
if (focusPane) {
var pane = $("#pane-" + id);
if (pane) pane.focus({ preventScroll: true });
window.scrollTo({ top: 0, behavior: "smooth" });
}
}
tabs.forEach(function (t) {
t.addEventListener("click", function () { show(t.dataset.pane, true); });
t.addEventListener("keydown", function (e) {
var i = ORDER.indexOf(t.dataset.pane);
var n = null;
if (e.key === "ArrowDown" || e.key === "ArrowRight") n = (i + 1) % ORDER.length;
else if (e.key === "ArrowUp" || e.key === "ArrowLeft") n = (i - 1 + ORDER.length) % ORDER.length;
else if (e.key === "Home") n = 0;
else if (e.key === "End") n = ORDER.length - 1;
if (n === null) return;
e.preventDefault();
show(ORDER[n], true);
tabs[n].focus();
});
});
prevBtn.addEventListener("click", function () {
var i = ORDER.indexOf(current);
if (i > 0) show(ORDER[i - 1], true);
});
nextBtn.addEventListener("click", function () {
var i = ORDER.indexOf(current);
if (i < ORDER.length - 1) show(ORDER[i + 1], true);
});
show("overview", false);
/* ---------- favourite ---------- */
var favBtn = $("#favBtn");
var favTxt = $("#favTxt");
favBtn.addEventListener("click", function () {
var on = favBtn.getAttribute("aria-pressed") === "true";
favBtn.setAttribute("aria-pressed", on ? "false" : "true");
favTxt.textContent = on ? "Save guide" : "Saved";
toast(on ? "Removed from your saved guides" : "Saved to your guide list");
});
/* ---------- cart ---------- */
var cartCount = $("#cartCount");
var cartChip = $("#cartChip");
var count = 0;
function bump(n) {
count += n;
cartCount.textContent = String(count);
cartChip.classList.remove("pop");
void cartChip.offsetWidth;
cartChip.classList.add("pop");
}
$$(".part .add").forEach(function (btn) {
btn.addEventListener("click", function () {
var row = btn.closest(".part");
if (btn.classList.contains("is-added")) {
btn.classList.remove("is-added");
btn.textContent = "Add";
bump(-1);
toast(row.dataset.name + " removed");
return;
}
btn.classList.add("is-added");
btn.textContent = "In cart";
bump(1);
toast(row.dataset.name + " added — $" + row.dataset.price);
});
});
$$("[data-addall]").forEach(function (b) {
b.addEventListener("click", function () {
var added = 0;
$$(".part .add").forEach(function (btn) {
if (!btn.classList.contains("is-added")) {
btn.classList.add("is-added");
btn.textContent = "In cart";
added++;
}
});
if (added) bump(added);
toast(added ? "Added " + added + " part" + (added > 1 ? "s" : "") + " to your cart" : "Everything is already in your cart");
});
});
/* running subtotal label */
var partTotal = $("#partTotal");
if (partTotal) {
var sum = $$(".part").reduce(function (a, p) { return a + parseFloat(p.dataset.price); }, 0);
partTotal.textContent = "Subtotal $" + sum.toFixed(2);
}
/* ---------- wiring highlight ---------- */
var wireSvg = $("#wireSvg");
var connBody = $("#connBody");
if (wireSvg && connBody) {
var setWire = function (id) {
wireSvg.classList.toggle("dim", !!id);
$$(".wires path", wireSvg).forEach(function (p) {
p.classList.toggle("hot", p.id === id);
});
};
$$("tr[data-wire]", connBody).forEach(function (tr) {
tr.addEventListener("mouseenter", function () { setWire(tr.dataset.wire); });
tr.addEventListener("mouseleave", function () { setWire(null); });
tr.addEventListener("focus", function () { setWire(tr.dataset.wire); });
tr.addEventListener("blur", function () { setWire(null); });
});
}
/* ---------- copy code ---------- */
var copyBtn = $("#copyBtn");
var codeBlock = $("#codeBlock");
copyBtn.addEventListener("click", function () {
var text = codeBlock.innerText;
var done = function () {
copyBtn.textContent = "Copied";
toast("weatherlamp.py copied to your clipboard");
setTimeout(function () { copyBtn.textContent = "Copy"; }, 1600);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(done, function () { toast("Clipboard blocked — select the code manually"); });
} else {
var ta = document.createElement("textarea");
ta.value = text;
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
try { document.execCommand("copy"); done(); } catch (e) { toast("Clipboard blocked — select the code manually"); }
document.body.removeChild(ta);
}
});
$$("[data-dl]").forEach(function (b) {
b.addEventListener("click", function () {
toast("Preparing " + b.dataset.dl + " …");
b.disabled = true;
setTimeout(function () {
b.disabled = false;
toast(b.dataset.dl + " ready (demo only)");
}, 900);
});
});
/* ---------- accordion ---------- */
$$("#acc .acc-btn").forEach(function (btn) {
var body = btn.parentElement.nextElementSibling;
btn.addEventListener("click", function () {
var open = btn.getAttribute("aria-expanded") === "true";
$$("#acc .acc-btn").forEach(function (o) {
o.setAttribute("aria-expanded", "false");
o.parentElement.nextElementSibling.style.maxHeight = "";
});
if (!open) {
btn.setAttribute("aria-expanded", "true");
body.style.maxHeight = body.scrollHeight + "px";
}
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SOLDERBOX Learn — Build a Desk Weather Lamp</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>
<a class="skip" href="#guide-main">Skip to guide content</a>
<header class="topbar">
<div class="wrap topbar-in">
<a class="brand" href="#" aria-label="SOLDERBOX Learn home">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 32 32" role="img" aria-hidden="true">
<rect x="2" y="6" width="28" height="20" rx="5" fill="#2b8cff"/>
<rect x="7" y="11" width="18" height="10" rx="3" fill="#101418"/>
<circle cx="12" cy="16" r="2.1" fill="#b8ff3d"/>
<circle cx="20" cy="16" r="2.1" fill="#ff3d81"/>
</svg>
</span>
<span class="brand-txt">SOLDERBOX <em>Learn</em></span>
</a>
<nav class="topnav" aria-label="Primary">
<a href="#" class="on">Guides</a>
<a href="#">Shop</a>
<a href="#">Forums</a>
<a href="#">Datasheets</a>
</nav>
<div class="topbar-right">
<span class="cart-chip" id="cartChip" aria-live="polite">Cart <b id="cartCount">0</b></span>
</div>
</div>
</header>
<main class="wrap layout" id="guide-main">
<!-- ============ SIDEBAR ============ -->
<aside class="sidebar" aria-label="Guide sections">
<div class="side-card">
<p class="side-kicker">Guide contents</p>
<ol class="seclist" id="secList" role="tablist" aria-orientation="vertical">
<li><button role="tab" class="secbtn is-active" data-pane="overview" aria-selected="true" aria-controls="pane-overview" id="tab-overview"><span class="secnum">01</span><span class="secname">Overview</span><span class="secmin">2 min</span></button></li>
<li><button role="tab" class="secbtn" data-pane="parts" aria-selected="false" aria-controls="pane-parts" id="tab-parts"><span class="secnum">02</span><span class="secname">Parts</span><span class="secmin">3 min</span></button></li>
<li><button role="tab" class="secbtn" data-pane="wiring" aria-selected="false" aria-controls="pane-wiring" id="tab-wiring"><span class="secnum">03</span><span class="secname">Wiring</span><span class="secmin">4 min</span></button></li>
<li><button role="tab" class="secbtn" data-pane="assembly" aria-selected="false" aria-controls="pane-assembly" id="tab-assembly"><span class="secnum">04</span><span class="secname">Assembly</span><span class="secmin">6 min</span></button></li>
<li><button role="tab" class="secbtn" data-pane="code" aria-selected="false" aria-controls="pane-code" id="tab-code"><span class="secnum">05</span><span class="secname">Code</span><span class="secmin">5 min</span></button></li>
<li><button role="tab" class="secbtn" data-pane="usage" aria-selected="false" aria-controls="pane-usage" id="tab-usage"><span class="secnum">06</span><span class="secname">Usage</span><span class="secmin">2 min</span></button></li>
<li><button role="tab" class="secbtn" data-pane="trouble" aria-selected="false" aria-controls="pane-trouble" id="tab-trouble"><span class="secnum">07</span><span class="secname">Troubleshooting</span><span class="secmin">3 min</span></button></li>
</ol>
<div class="side-foot">
<div class="progress-wrap">
<div class="progress-line"><i id="progFill"></i></div>
<span class="mono" id="progLabel">1 / 7</span>
</div>
<p class="side-note">Sections you open are marked as read on this device.</p>
</div>
</div>
<div class="side-card side-kit">
<p class="side-kicker">Everything in one box</p>
<h3>Weather Lamp Kit</h3>
<p class="kit-desc">All 6 parts, pre-cut wire, and the diffuser shell.</p>
<div class="kit-row"><span class="price">$46.50</span><button class="btn btn-lime" data-addall>Add kit</button></div>
<p class="mono kit-sku">SKU SBX-KIT-0417</p>
</div>
</aside>
<!-- ============ CONTENT ============ -->
<div class="content">
<div class="guide-head">
<div class="crumbs mono"><a href="#">Learn</a> / <a href="#">Sensors</a> / <span>Desk Weather Lamp</span></div>
<h1>Build a desk weather lamp</h1>
<p class="lede">A palm-sized lamp that reads temperature, humidity and pressure, then glows a colour that tells you what the next few hours look like — blue for cooling, amber for warming, magenta when the pressure drops fast.</p>
<div class="meta">
<span class="chip author">
<span class="ava" aria-hidden="true">RM</span>
<span class="a-txt"><b>Rin Marlowe</b><em>Hardware writer</em></span>
</span>
<span class="badge badge-diff">Intermediate</span>
<span class="badge badge-time">~2 h build</span>
<span class="dot" aria-hidden="true"></span>
<span class="mono upd">Updated 2026-07-24</span>
<span class="mono upd">25 min read</span>
<button class="fav" id="favBtn" aria-pressed="false">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 20.4 4.6 13a4.6 4.6 0 1 1 6.5-6.5l.9.9.9-.9A4.6 4.6 0 1 1 19.4 13Z"/></svg>
<span id="favTxt">Save guide</span>
</button>
</div>
<div class="dots" id="dots" aria-hidden="true"></div>
</div>
<!-- OVERVIEW -->
<section class="pane is-active" id="pane-overview" role="tabpanel" aria-labelledby="tab-overview" tabindex="0">
<div class="hero-plate">
<svg viewBox="0 0 520 260" role="img" aria-label="Illustration of the finished weather lamp: a frosted dome on a hexagonal base">
<defs>
<radialGradient id="glow" cx="50%" cy="45%" r="55%">
<stop offset="0%" stop-color="#b8ff3d" stop-opacity=".95"/>
<stop offset="55%" stop-color="#2b8cff" stop-opacity=".7"/>
<stop offset="100%" stop-color="#ff3d81" stop-opacity=".25"/>
</radialGradient>
<linearGradient id="base" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#2a323d"/><stop offset="100%" stop-color="#141a21"/>
</linearGradient>
</defs>
<g opacity=".5" stroke="#2b8cff" stroke-width="1">
<path d="M40 220h440M40 180h440M40 140h440"/>
</g>
<ellipse cx="260" cy="120" rx="120" ry="105" fill="url(#glow)" opacity=".55"/>
<path d="M180 170a80 80 0 0 1 160 0Z" fill="#eef2f6" opacity=".92"/>
<path d="M180 170a80 80 0 0 1 160 0Z" fill="none" stroke="#2b8cff" stroke-width="3"/>
<path d="M150 170h220l-22 46H172Z" fill="url(#base)" stroke="#2b8cff" stroke-width="2.5" stroke-linejoin="round"/>
<rect x="196" y="184" width="128" height="16" rx="8" fill="#101418"/>
<circle cx="216" cy="192" r="4" fill="#b8ff3d"/><circle cx="236" cy="192" r="4" fill="#2b8cff"/><circle cx="256" cy="192" r="4" fill="#ff3d81"/>
<g stroke="#ff3d81" stroke-width="2.5" stroke-linecap="round" opacity=".9">
<path d="M392 74c14-10 28-2 30 12"/><path d="M96 84c-14-10-26 0-26 14"/>
</g>
<text x="404" y="120" font-family="JetBrains Mono, monospace" font-size="13" fill="#b8ff3d">23.4°C</text>
<text x="404" y="140" font-family="JetBrains Mono, monospace" font-size="13" fill="#2b8cff">1013 hPa</text>
<text x="68" y="130" font-family="JetBrains Mono, monospace" font-size="13" fill="#ff3d81">48 %RH</text>
</svg>
</div>
<h2>What you will build</h2>
<ul class="checklist">
<li><b>A three-sensor weather core.</b> One I²C breakout reports temperature, humidity and barometric pressure once per second.</li>
<li><b>A colour forecast.</b> Twelve addressable LEDs blend between blue, amber and magenta based on a 3-hour pressure trend.</li>
<li><b>A tap-to-read mode.</b> Touch the metal ring and the lamp pulses out the current temperature in short flashes.</li>
<li><b>No soldering iron required</b> for the base build — every connection uses the included spring terminals.</li>
</ul>
<div class="callout callout-blue">
<b>Before you start</b>
<p>Flash the board with the SOLDERBOX bootloader (v4.2 or newer). Older bootloaders do not expose the NeoPixel timing fix this sketch relies on.</p>
</div>
<div class="statgrid">
<div class="stat"><span class="mono s-n">6</span><span class="s-l">Parts</span></div>
<div class="stat"><span class="mono s-n">2 h</span><span class="s-l">Build time</span></div>
<div class="stat"><span class="mono s-n">$46</span><span class="s-l">Total cost</span></div>
<div class="stat"><span class="mono s-n">5 V</span><span class="s-l">USB-C power</span></div>
</div>
</section>
<!-- PARTS -->
<section class="pane" id="pane-parts" role="tabpanel" aria-labelledby="tab-parts" tabindex="0" hidden>
<h2>Parts list</h2>
<p class="pane-lede">Six parts, all in stock. Prices are per-unit; the kit above bundles them with the diffuser shell.</p>
<div class="partbar">
<span class="mono" id="partTotal">Subtotal $51.15</span>
<button class="btn btn-blue" data-addall>Add all 6 to cart</button>
</div>
<ul class="parts" id="partsList">
<li class="part" data-price="14.95" data-name="Feathercore M4 board">
<span class="plate">
<svg viewBox="0 0 72 72" aria-hidden="true">
<rect x="8" y="16" width="56" height="40" rx="6" fill="#2b8cff"/>
<rect x="18" y="26" width="20" height="20" rx="3" fill="#101418"/>
<g fill="#b8ff3d"><circle cx="50" cy="30" r="3"/><circle cx="50" cy="42" r="3"/></g>
<g stroke="#eef2f6" stroke-width="2"><path d="M12 20v32M60 20v32"/></g>
</svg>
</span>
<span class="p-txt"><b>Feathercore M4 board</b><em class="mono">SBX-1402 · USB-C · CircuitScript ready</em></span>
<span class="mono p-price">$14.95</span>
<button class="btn btn-ghost add">Add</button>
</li>
<li class="part" data-price="9.50" data-name="TriSense THP breakout">
<span class="plate">
<svg viewBox="0 0 72 72" aria-hidden="true">
<rect x="14" y="20" width="44" height="32" rx="5" fill="#ff3d81"/>
<rect x="24" y="28" width="24" height="16" rx="4" fill="#eef2f6"/>
<circle cx="36" cy="36" r="5" fill="#101418"/>
<g fill="#101418"><rect x="18" y="52" width="4" height="6"/><rect x="30" y="52" width="4" height="6"/><rect x="42" y="52" width="4" height="6"/><rect x="52" y="52" width="4" height="6"/></g>
</svg>
</span>
<span class="p-txt"><b>TriSense THP breakout</b><em class="mono">SBX-2288 · I²C 0x76 · ±0.3 °C</em></span>
<span class="mono p-price">$9.50</span>
<button class="btn btn-ghost add">Add</button>
</li>
<li class="part" data-price="7.25" data-name="GlowRing 12 LED disc">
<span class="plate">
<svg viewBox="0 0 72 72" aria-hidden="true">
<circle cx="36" cy="36" r="24" fill="#1a2028" stroke="#b8ff3d" stroke-width="2"/>
<g fill="#b8ff3d"><circle cx="36" cy="16" r="3.2"/><circle cx="50" cy="22" r="3.2"/><circle cx="56" cy="36" r="3.2"/><circle cx="50" cy="50" r="3.2"/></g>
<g fill="#2b8cff"><circle cx="36" cy="56" r="3.2"/><circle cx="22" cy="50" r="3.2"/></g>
<g fill="#ff3d81"><circle cx="16" cy="36" r="3.2"/><circle cx="22" cy="22" r="3.2"/></g>
</svg>
</span>
<span class="p-txt"><b>GlowRing 12 LED disc</b><em class="mono">SBX-3310 · WS-style · 5 V data</em></span>
<span class="mono p-price">$7.25</span>
<button class="btn btn-ghost add">Add</button>
</li>
<li class="part" data-price="4.95" data-name="Spring terminal wing">
<span class="plate">
<svg viewBox="0 0 72 72" aria-hidden="true">
<rect x="10" y="26" width="52" height="22" rx="4" fill="#eef2f6"/>
<g fill="#2b8cff"><rect x="16" y="31" width="9" height="12" rx="2"/><rect x="31" y="31" width="9" height="12" rx="2"/><rect x="46" y="31" width="9" height="12" rx="2"/></g>
<path d="M10 48h52v4H10z" fill="#101418"/>
</svg>
</span>
<span class="p-txt"><b>Spring terminal wing</b><em class="mono">SBX-4071 · solderless · 12 way</em></span>
<span class="mono p-price">$4.95</span>
<button class="btn btn-ghost add">Add</button>
</li>
<li class="part" data-price="8.50" data-name="Frosted dome diffuser">
<span class="plate">
<svg viewBox="0 0 72 72" aria-hidden="true">
<path d="M14 48a22 22 0 0 1 44 0Z" fill="#eef2f6" opacity=".9"/>
<path d="M14 48a22 22 0 0 1 44 0Z" fill="none" stroke="#2b8cff" stroke-width="2.5"/>
<rect x="10" y="48" width="52" height="8" rx="4" fill="#1a2028"/>
<path d="M28 40a10 10 0 0 1 16 0" stroke="#ff3d81" stroke-width="2.5" fill="none"/>
</svg>
</span>
<span class="p-txt"><b>Frosted dome diffuser</b><em class="mono">SBX-5520 · 70 mm · PETG</em></span>
<span class="mono p-price">$8.50</span>
<button class="btn btn-ghost add">Add</button>
</li>
<li class="part" data-price="6.00" data-name="USB-C cable, 1 m braided">
<span class="plate">
<svg viewBox="0 0 72 72" aria-hidden="true">
<path d="M16 44c10-14 30-14 40 0" stroke="#b8ff3d" stroke-width="4" fill="none" stroke-linecap="round"/>
<rect x="8" y="40" width="14" height="10" rx="4" fill="#eef2f6"/>
<rect x="50" y="40" width="14" height="10" rx="4" fill="#2b8cff"/>
</svg>
</span>
<span class="p-txt"><b>USB-C cable, 1 m braided</b><em class="mono">SBX-6001 · 3 A · data + power</em></span>
<span class="mono p-price">$6.00</span>
<button class="btn btn-ghost add">Add</button>
</li>
</ul>
</section>
<!-- WIRING -->
<section class="pane" id="pane-wiring" role="tabpanel" aria-labelledby="tab-wiring" tabindex="0" hidden>
<h2>Wiring</h2>
<p class="pane-lede">Five connections total. Hover or focus a row in the table to highlight that wire in the diagram.</p>
<div class="diagram-plate">
<svg viewBox="0 0 620 300" id="wireSvg" role="img" aria-label="Wiring diagram between the board, the sensor breakout and the LED disc">
<rect x="0" y="0" width="620" height="300" fill="none"/>
<!-- board -->
<g>
<rect x="40" y="90" width="140" height="120" rx="12" fill="#2b8cff"/>
<rect x="62" y="118" width="60" height="60" rx="8" fill="#101418"/>
<text x="110" y="80" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="12" fill="#eef2f6">FEATHERCORE M4</text>
<g font-family="JetBrains Mono, monospace" font-size="10" fill="#101418">
<text x="150" y="114">3V3</text><text x="150" y="138">GND</text>
<text x="150" y="162">SDA</text><text x="150" y="186">SCL</text>
<text x="150" y="206">D6</text>
</g>
</g>
<!-- sensor -->
<g>
<rect x="420" y="40" width="150" height="90" rx="12" fill="#ff3d81"/>
<rect x="452" y="62" width="86" height="46" rx="8" fill="#eef2f6"/>
<text x="495" y="30" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="12" fill="#eef2f6">TRISENSE THP</text>
</g>
<!-- led ring -->
<g>
<circle cx="495" cy="222" r="52" fill="#1a2028" stroke="#b8ff3d" stroke-width="3"/>
<g fill="#b8ff3d"><circle cx="495" cy="180" r="5"/><circle cx="527" cy="196" r="5"/><circle cx="537" cy="222" r="5"/><circle cx="527" cy="248" r="5"/><circle cx="495" cy="264" r="5"/><circle cx="463" cy="248" r="5"/><circle cx="453" cy="222" r="5"/><circle cx="463" cy="196" r="5"/></g>
<text x="495" y="292" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="12" fill="#eef2f6">GLOWRING 12</text>
</g>
<!-- wires -->
<g fill="none" stroke-width="4" stroke-linecap="round" class="wires">
<path id="w-3v3" d="M182 110 C 300 110, 320 62, 420 62" stroke="#ff3d81"/>
<path id="w-gnd" d="M182 134 C 300 134, 320 84, 420 84" stroke="#6b7885"/>
<path id="w-sda" d="M182 158 C 310 158, 330 104, 420 104" stroke="#2b8cff"/>
<path id="w-scl" d="M182 182 C 320 182, 340 124, 420 124" stroke="#b8ff3d"/>
<path id="w-d6" d="M182 204 C 300 236, 360 200, 443 214" stroke="#eef2f6"/>
</g>
</svg>
</div>
<div class="tablewrap">
<table class="conn">
<caption class="sr-only">Connections between board pins and modules</caption>
<thead><tr><th>From (board)</th><th>To</th><th>Wire</th><th>Note</th></tr></thead>
<tbody id="connBody">
<tr tabindex="0" data-wire="w-3v3"><td class="mono">3V3</td><td>TriSense VIN</td><td><span class="sw" style="--sw:#ff3d81"></span>Magenta</td><td>Never feed the sensor 5 V.</td></tr>
<tr tabindex="0" data-wire="w-gnd"><td class="mono">GND</td><td>TriSense GND</td><td><span class="sw" style="--sw:#6b7885"></span>Grey</td><td>Shared ground for both modules.</td></tr>
<tr tabindex="0" data-wire="w-sda"><td class="mono">SDA</td><td>TriSense SDA</td><td><span class="sw" style="--sw:#2b8cff"></span>Blue</td><td>Pull-ups are on the breakout.</td></tr>
<tr tabindex="0" data-wire="w-scl"><td class="mono">SCL</td><td>TriSense SCL</td><td><span class="sw" style="--sw:#b8ff3d"></span>Lime</td><td>Keep under 20 cm.</td></tr>
<tr tabindex="0" data-wire="w-d6"><td class="mono">D6</td><td>GlowRing DIN</td><td><span class="sw" style="--sw:#eef2f6"></span>White</td><td>Data in, not data out.</td></tr>
</tbody>
</table>
</div>
<div class="callout callout-pink">
<b>Polarity check</b>
<p>Power the lamp only after all five wires are seated. A reversed VIN/GND pair on the LED disc will pop the first pixel instantly.</p>
</div>
</section>
<!-- ASSEMBLY -->
<section class="pane" id="pane-assembly" role="tabpanel" aria-labelledby="tab-assembly" tabindex="0" hidden>
<h2>Assembly</h2>
<p class="pane-lede">Work on a non-conductive surface. Each step takes a few minutes; nothing here is glued until step 5.</p>
<ol class="steps">
<li><h3>Seat the terminal wing</h3><p>Press the spring terminal wing onto the board's female headers until the plastic bottoms out. It only fits one way — the USB-C cutout faces the same edge as the board's port.</p></li>
<li><h3>Mount the sensor away from the LEDs</h3><p>Stand the TriSense breakout on the short standoffs at the rear of the base. Keeping 25 mm between the sensor and the LED disc stops the pixels' own heat from skewing the temperature reading by a degree or two.</p></li>
<li><h3>Land the five wires</h3><p>Strip 7 mm, press the orange lever, insert, release. Tug each wire gently — a good spring terminal will not let go.</p></li>
<li><h3>Drop in the LED disc</h3><p>The disc sits face-up in the base ring with the DIN arrow pointing at the front seam. Route its wire through the side channel so the dome can sit flat.</p></li>
<li><h3>Twist on the dome</h3><p>Align the two lugs, push down 2 mm and rotate clockwise until it clicks. Do not force it past the stop.</p></li>
<li><h3>First power-up</h3><p>Plug in USB-C. You should get one white flash, then a slow blue breathe while the sensor takes its first three readings.</p></li>
</ol>
<div class="callout callout-lime"><b>Tip</b><p>If the dome rattles, add the thin foam ring from the kit bag between the base lip and the diffuser.</p></div>
</section>
<!-- CODE -->
<section class="pane" id="pane-code" role="tabpanel" aria-labelledby="tab-code" tabindex="0" hidden>
<h2>Code</h2>
<p class="pane-lede">Drop <span class="mono">weatherlamp.py</span> onto the board's USB drive. It runs on boot; no build step.</p>
<div class="codecard">
<div class="codebar">
<span class="mono file">weatherlamp.py</span>
<span class="tagline mono">CircuitScript · 41 lines</span>
<button class="btn btn-ghost" id="copyBtn">Copy</button>
</div>
<pre class="code" id="codeBlock"><code><span class="ln"></span><span class="k">import</span> <span class="n">time</span>, <span class="n">board</span>, <span class="n">trisense</span>, <span class="n">glowring</span>
<span class="ln"></span>
<span class="ln"></span><span class="n">SENSOR</span> = <span class="n">trisense</span>.<span class="f">THP</span>(<span class="n">board</span>.<span class="n">I2C</span>(), <span class="n">address</span>=<span class="num">0x76</span>)
<span class="ln"></span><span class="n">RING</span> = <span class="n">glowring</span>.<span class="f">Disc</span>(<span class="n">board</span>.<span class="n">D6</span>, <span class="num">12</span>, <span class="n">brightness</span>=<span class="num">0.35</span>)
<span class="ln"></span>
<span class="ln"></span><span class="c"># hue targets: cooling / steady / warming</span>
<span class="ln"></span><span class="n">COOL</span> = (<span class="num">43</span>, <span class="num">140</span>, <span class="num">255</span>)
<span class="ln"></span><span class="n">STEADY</span> = (<span class="num">184</span>, <span class="num">255</span>, <span class="num">61</span>)
<span class="ln"></span><span class="n">FALLING</span> = (<span class="num">255</span>, <span class="num">61</span>, <span class="num">129</span>)
<span class="ln"></span>
<span class="ln"></span><span class="n">history</span> = []
<span class="ln"></span>
<span class="ln"></span><span class="k">def</span> <span class="f">blend</span>(<span class="n">a</span>, <span class="n">b</span>, <span class="n">t</span>):
<span class="ln"></span> <span class="k">return</span> <span class="f">tuple</span>(<span class="f">int</span>(<span class="n">x</span> + (<span class="n">y</span> - <span class="n">x</span>) * <span class="n">t</span>) <span class="k">for</span> <span class="n">x</span>, <span class="n">y</span> <span class="k">in</span> <span class="f">zip</span>(<span class="n">a</span>, <span class="n">b</span>))
<span class="ln"></span>
<span class="ln"></span><span class="k">def</span> <span class="f">trend</span>(<span class="n">samples</span>):
<span class="ln"></span> <span class="c"># hPa change across the last three hours</span>
<span class="ln"></span> <span class="k">if</span> <span class="f">len</span>(<span class="n">samples</span>) < <span class="num">2</span>:
<span class="ln"></span> <span class="k">return</span> <span class="num">0.0</span>
<span class="ln"></span> <span class="k">return</span> <span class="n">samples</span>[-<span class="num">1</span>] - <span class="n">samples</span>[<span class="num">0</span>]
<span class="ln"></span>
<span class="ln"></span><span class="k">while</span> <span class="b">True</span>:
<span class="ln"></span> <span class="n">temp</span> = <span class="n">SENSOR</span>.<span class="n">temperature</span>
<span class="ln"></span> <span class="n">hum</span> = <span class="n">SENSOR</span>.<span class="n">humidity</span>
<span class="ln"></span> <span class="n">pres</span> = <span class="n">SENSOR</span>.<span class="n">pressure</span>
<span class="ln"></span>
<span class="ln"></span> <span class="n">history</span>.<span class="f">append</span>(<span class="n">pres</span>)
<span class="ln"></span> <span class="k">if</span> <span class="f">len</span>(<span class="n">history</span>) > <span class="num">180</span>:
<span class="ln"></span> <span class="n">history</span>.<span class="f">pop</span>(<span class="num">0</span>)
<span class="ln"></span>
<span class="ln"></span> <span class="n">delta</span> = <span class="f">trend</span>(<span class="n">history</span>)
<span class="ln"></span> <span class="k">if</span> <span class="n">delta</span> < -<span class="num">1.5</span>:
<span class="ln"></span> <span class="n">colour</span> = <span class="n">FALLING</span>
<span class="ln"></span> <span class="k">elif</span> <span class="n">delta</span> > <span class="num">1.5</span>:
<span class="ln"></span> <span class="n">colour</span> = <span class="f">blend</span>(<span class="n">STEADY</span>, <span class="n">COOL</span>, <span class="num">0.4</span>)
<span class="ln"></span> <span class="k">else</span>:
<span class="ln"></span> <span class="n">colour</span> = <span class="n">STEADY</span>
<span class="ln"></span>
<span class="ln"></span> <span class="n">RING</span>.<span class="f">fade_to</span>(<span class="n">colour</span>, <span class="n">seconds</span>=<span class="num">1.2</span>)
<span class="ln"></span> <span class="f">print</span>(<span class="s">"%.1fC %.0f%%RH %.1fhPa"</span> % (<span class="n">temp</span>, <span class="n">hum</span>, <span class="n">pres</span>))
<span class="ln"></span> <span class="n">time</span>.<span class="f">sleep</span>(<span class="num">1.0</span>)
</code></pre>
</div>
<ul class="downloads">
<li><span class="dl-ico mono">PY</span><span class="dl-t"><b>weatherlamp.py</b><em class="mono">2.1 KB · main sketch</em></span><button class="btn btn-ghost" data-dl="weatherlamp.py">Download</button></li>
<li><span class="dl-ico mono">ZIP</span><span class="dl-t"><b>libraries-bundle.zip</b><em class="mono">148 KB · trisense + glowring</em></span><button class="btn btn-ghost" data-dl="libraries-bundle.zip">Download</button></li>
<li><span class="dl-ico mono">STL</span><span class="dl-t"><b>base-hex.stl</b><em class="mono">96 KB · printable base, 0.2 mm</em></span><button class="btn btn-ghost" data-dl="base-hex.stl">Download</button></li>
</ul>
</section>
<!-- USAGE -->
<section class="pane" id="pane-usage" role="tabpanel" aria-labelledby="tab-usage" tabindex="0" hidden>
<h2>Using the lamp</h2>
<p class="pane-lede">The colour is a three-hour pressure trend, not a temperature read-out. Give it three hours after the first power-up to learn your room.</p>
<div class="legend">
<div class="lg"><i style="--c:#2b8cff"></i><b>Steady blue</b><span>Pressure rising — clear and cooling.</span></div>
<div class="lg"><i style="--c:#b8ff3d"></i><b>Lime</b><span>Flat pressure — the next few hours look like the last few.</span></div>
<div class="lg"><i style="--c:#ff3d81"></i><b>Magenta</b><span>Pressure dropping fast — expect a change, likely rain.</span></div>
</div>
<h3>Tap gestures</h3>
<table class="conn">
<thead><tr><th>Gesture</th><th>Result</th></tr></thead>
<tbody>
<tr><td class="mono">1 tap</td><td>Flash the temperature: one long flash per 10 °C, one short per 1 °C.</td></tr>
<tr><td class="mono">2 taps</td><td>Flash humidity in the same pattern, in magenta.</td></tr>
<tr><td class="mono">Hold 3 s</td><td>Cycle brightness: 35 % → 70 % → 10 %.</td></tr>
<tr><td class="mono">Hold 10 s</td><td>Clear the pressure history and re-learn.</td></tr>
</tbody>
</table>
</section>
<!-- TROUBLESHOOTING -->
<section class="pane" id="pane-trouble" role="tabpanel" aria-labelledby="tab-trouble" tabindex="0" hidden>
<h2>Troubleshooting</h2>
<p class="pane-lede">The five things that go wrong most often, in the order our forum sees them.</p>
<div class="acc" id="acc">
<div class="acc-item">
<h3><button class="acc-btn" aria-expanded="false">Nothing lights up at all<span class="acc-i" aria-hidden="true"></span></button></h3>
<div class="acc-body"><p>Nine times in ten this is a charge-only USB-C cable. Swap for the braided cable in the kit, then check the board's own green power LED under the disc. If that LED is dark, the cable or the port is the problem, not your wiring.</p></div>
</div>
<div class="acc-item">
<h3><button class="acc-btn" aria-expanded="false">Only the first pixel is lit, and it is white<span class="acc-i" aria-hidden="true"></span></button></h3>
<div class="acc-body"><p>The data wire is on DOUT instead of DIN, or it is on the wrong board pin. Data enters the disc at the arrow marked <span class="mono">DIN</span>. Move the white wire to <span class="mono">D6</span> and re-seat.</p></div>
</div>
<div class="acc-item">
<h3><button class="acc-btn" aria-expanded="false">The temperature reads 2–3 °C high<span class="acc-i" aria-hidden="true"></span></button></h3>
<div class="acc-body"><p>The sensor is picking up heat from the LEDs or the regulator. Raise it onto the tall standoffs, drop brightness to 20 %, and set <span class="mono">SENSOR.offset = -2.0</span> once the reading stabilises for an hour.</p></div>
</div>
<div class="acc-item">
<h3><button class="acc-btn" aria-expanded="false">Colour never leaves lime<span class="acc-i" aria-hidden="true"></span></button></h3>
<div class="acc-body"><p>Expected for the first three hours — the history buffer is still filling. After that, a permanently lime lamp usually means the pressure reading is frozen: check for a loose SDA wire and confirm the board sees address <span class="mono">0x76</span> on the I²C scan.</p></div>
</div>
<div class="acc-item">
<h3><button class="acc-btn" aria-expanded="false">The board disappears from my computer<span class="acc-i" aria-hidden="true"></span></button></h3>
<div class="acc-body"><p>A crashed sketch can lock the USB drive. Double-tap reset to enter bootloader mode, delete <span class="mono">weatherlamp.py</span>, then copy a fresh download and reset once.</p></div>
</div>
</div>
</section>
<!-- footer nav -->
<nav class="pagenav" aria-label="Guide pagination">
<button class="pnav" id="prevBtn"><span class="pn-k mono">Previous</span><span class="pn-t" id="prevTxt">—</span></button>
<button class="pnav pnav-next" id="nextBtn"><span class="pn-k mono">Next</span><span class="pn-t" id="nextTxt">Parts</span></button>
</nav>
</div>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Learn Guide
A full multi-page tutorial layout rendered as a single document. The left rail is a sticky
table of contents — seven numbered sections with per-section reading times, an active marker,
and a progress bar that fills as you move through the guide. Selecting a section swaps the
main pane instantly with a short fade, updates the dot row under the header, marks the
section as read, and rewrites the prev/next footer buttons. Arrow keys, Home and End move
between sections from the keyboard, and the tab list carries proper role="tab" semantics.
The header carries the guide identity: title, author chip with a gradient avatar, difficulty
and build-time badges, updated date, reading estimate, and a save-to-favorites toggle backed
by aria-pressed. Each pane has its own character. Parts rows pair bright SVG component art
on white rounded plates with mono product codes, per-unit prices, individual add buttons and
an “add all” bar that keeps a live cart counter in the top bar. The wiring pane pairs an SVG
schematic with a connections table — hovering or focusing a row dims every wire except the
matching one, which thickens and picks up a glow.
The code pane uses hand-rolled span colouring with CSS-counter line numbers, a clipboard copy
with an execCommand fallback, and a download row of simulated file assets. Troubleshooting is
a single-open accordion animated by measured scrollHeight, and every interaction reports
through one small toast() helper.
How this look is built
The information architecture nods to Adafruit-style learn guides — section rail, parts list,
wiring, code, troubleshooting — but everything here is drawn from scratch. CSS Grid handles
both the two-column shell and the parts rows, which re-flow into a named-area stack under
520px instead of needing a separate mobile markup path. position: sticky keeps the section
rail parked without any scroll listener, the blueprint and plate textures are pure layered
linear-gradient background images rather than tiled PNGs, and the wire highlight is a single
dim class on the SVG root plus a hot class on one path — the cascade does the work, so no
per-element style writing or canvas redraw is involved. Line numbers come from a CSS counter
on the <pre>, which keeps the copyable text free of number noise, something a two-column
table-based numbering scheme could never do cleanly.
Illustrative UI only — always follow real safety guidance when working with tools, electronics, or repairs.