DIY — Product Teardown
Blueprint-styled teardown report for a fictional smart speaker, the Soundbyte Puck Mini. An animated repairability dial (7/10) with per-criterion bars leads into an interactive exploded view: five layered SVG parts — grille, driver, PCB, battery, shell — separated by a range slider, with leader-line labels and click-to-open detail cards listing chips, connectors and adhesive warnings. Step-by-step chapters with tool badges and hazard callouts, a mono IC table, and a pros/cons verdict with a stamped final score complete the page.
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 6px 24px rgba(28, 39, 51, 0.1);
--shadow-lg: 0 16px 48px rgba(10, 30, 54, 0.35);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
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; }
h1, h2, h3, h4 { margin: 0; line-height: 1.15; }
/* ---------- Top bar ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 40;
background: rgba(244, 241, 234, 0.92);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--line);
}
.topbar-inner {
max-width: 1120px;
margin: 0 auto;
padding: 0.7rem 1.25rem;
display: flex;
align-items: center;
gap: 1.25rem;
}
.brand {
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
color: var(--ink);
}
.brand-mark {
display: grid;
place-items: center;
width: 30px;
height: 30px;
border-radius: var(--r-sm);
background: var(--blueprint);
color: var(--orange);
font-size: 0.95rem;
}
.brand-name {
font-weight: 800;
letter-spacing: 0.12em;
font-size: 0.85rem;
}
.brand-name em { font-style: normal; color: var(--orange-d); }
.topnav {
display: flex;
gap: 0.25rem;
margin-left: auto;
}
.topnav a {
text-decoration: none;
color: var(--ink-2);
font-size: 0.82rem;
font-weight: 600;
padding: 0.4rem 0.65rem;
border-radius: var(--r-sm);
transition: background 0.15s, color 0.15s;
}
.topnav a:hover, .topnav a:focus-visible { background: var(--kraft-l); color: var(--ink); }
.file-no {
font-size: 0.68rem;
color: var(--muted);
border: 1px dashed var(--line-2);
border-radius: var(--r-sm);
padding: 0.3rem 0.55rem;
white-space: nowrap;
}
/* ---------- 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);
padding: 4rem 1.25rem 4.5rem;
}
.hero-inner {
max-width: 1120px;
margin: 0 auto;
display: grid;
grid-template-columns: 1.25fr 0.9fr;
gap: 3rem;
align-items: center;
}
.kicker {
font-size: 0.72rem;
letter-spacing: 0.18em;
color: var(--orange);
margin: 0 0 1rem;
}
.hero-title {
font-size: clamp(2.4rem, 5.5vw, 4rem);
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--white);
}
.hero-title span { color: var(--orange); }
.hero-sub {
max-width: 46ch;
color: var(--kraft);
margin: 1.1rem 0 1.4rem;
font-size: 1rem;
}
.hero-meta { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1.6rem; }
.meta-chip {
font-size: 0.68rem;
letter-spacing: 0.06em;
color: var(--kraft-l);
border: 1px solid rgba(217, 199, 167, 0.35);
border-radius: var(--r-sm);
padding: 0.3rem 0.55rem;
background: rgba(10, 30, 54, 0.5);
}
.hero-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
font: inherit;
font-weight: 700;
font-size: 0.88rem;
letter-spacing: 0.05em;
text-transform: uppercase;
text-decoration: none;
border-radius: var(--r-sm);
padding: 0.75rem 1.3rem;
cursor: pointer;
border: 1px solid transparent;
transition: transform 0.12s, background 0.15s, box-shadow 0.15s;
}
.btn:active { transform: translateY(1px) scale(0.99); }
.btn-orange {
background: var(--orange);
color: var(--blueprint-d);
box-shadow: 0 6px 18px rgba(255, 107, 53, 0.3);
}
.btn-orange:hover, .btn-orange:focus-visible { background: var(--orange-d); color: var(--white); }
.btn-ghost {
background: transparent;
color: var(--kraft-l);
border-color: rgba(217, 199, 167, 0.45);
}
.btn-ghost:hover, .btn-ghost:focus-visible { background: rgba(239, 230, 212, 0.1); }
/* Score card */
.score-card {
background: var(--blueprint-d);
border: 1px solid rgba(140, 180, 220, 0.22);
border-radius: var(--r-lg);
padding: 1.5rem 1.5rem 1.25rem;
box-shadow: var(--shadow-lg);
}
.score-dial-wrap {
position: relative;
width: 160px;
margin: 0 auto 1rem;
}
.score-dial { display: block; transform: rotate(-90deg); }
.dial-track {
fill: none;
stroke: rgba(140, 180, 220, 0.18);
stroke-width: 12;
}
.dial-value {
fill: none;
stroke: var(--orange);
stroke-width: 12;
stroke-linecap: round;
stroke-dasharray: 414.7;
stroke-dashoffset: 414.7;
transition: stroke-dashoffset 1.1s cubic-bezier(0.22, 1, 0.36, 1);
}
.dial-readout {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.dial-num { font-size: 2.6rem; font-weight: 700; color: var(--white); line-height: 1; }
.dial-den { font-size: 0.85rem; color: var(--kraft); }
.dial-label {
margin-top: 0.35rem;
font-size: 0.6rem;
letter-spacing: 0.22em;
color: var(--orange);
font-weight: 700;
}
.criteria { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.7rem; }
.crit-head {
display: flex;
justify-content: space-between;
font-size: 0.78rem;
font-weight: 600;
color: var(--kraft-l);
margin-bottom: 0.3rem;
}
.crit-val { color: var(--kraft); font-size: 0.7rem; }
.crit-bar {
height: 8px;
border-radius: 99px;
background: rgba(140, 180, 220, 0.15);
overflow: hidden;
}
.crit-fill {
display: block;
height: 100%;
width: 0;
border-radius: 99px;
background: var(--tone, var(--orange));
transition: width 0.9s cubic-bezier(0.22, 1, 0.36, 1);
}
/* ---------- Sections common ---------- */
section { scroll-margin-top: 64px; }
.section-head {
max-width: 1120px;
margin: 0 auto 2rem;
padding: 0 1.25rem;
}
.section-no {
display: inline-block;
font-size: 0.7rem;
letter-spacing: 0.2em;
color: var(--orange-d);
border: 1px solid currentColor;
border-radius: var(--r-sm);
padding: 0.2rem 0.5rem;
margin-bottom: 0.7rem;
}
.section-head h2 {
font-size: clamp(1.5rem, 3vw, 2.1rem);
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 800;
}
.section-head p { color: var(--muted); margin: 0.5rem 0 0; max-width: 60ch; }
.section-head.light h2 { color: var(--white); }
.section-head.light p { color: var(--kraft); }
.section-head.light .section-no { color: var(--orange); }
/* ---------- Exploded ---------- */
.exploded {
background-color: var(--blueprint);
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px),
linear-gradient(var(--grid-major) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-major) 1px, transparent 1px);
background-size: 8px 8px, 8px 8px, 80px 80px, 80px 80px;
padding: 3.5rem 0 4rem;
}
.exploded-grid {
max-width: 1120px;
margin: 0 auto;
padding: 0 1.25rem;
display: grid;
grid-template-columns: 1.2fr 0.85fr;
gap: 2rem;
align-items: start;
}
.stage {
border: 1px dashed rgba(140, 180, 220, 0.35);
border-radius: var(--r-lg);
padding: 0.5rem;
background: rgba(10, 30, 54, 0.4);
}
#exploded-svg { display: block; width: 100%; height: auto; }
.layer { cursor: pointer; outline: none; }
.layer .shape { transition: filter 0.15s; }
.layer:hover .shape, .layer:focus-visible .shape { filter: brightness(1.18); }
.layer:focus-visible { outline: 2px dashed var(--orange); outline-offset: 4px; }
.layer.selected .shape { filter: brightness(1.22) drop-shadow(0 0 6px rgba(255, 107, 53, 0.55)); }
/* SVG shape fills */
.shell-body { fill: #1d3a5e; stroke: #6f9cc9; stroke-width: 1.5; }
.shell-top { fill: #2a4a72; stroke: #6f9cc9; stroke-width: 1.5; }
.shell-port { fill: #14304f; stroke: #58809f; stroke-width: 1; }
.batt-body { fill: #8d8f96; stroke: #c9ccd4; stroke-width: 1.5; }
.batt-top { fill: #a9adb6; stroke: #c9ccd4; stroke-width: 1.5; }
.batt-label { fill: #efe6d4; stroke: #b9ab8c; stroke-width: 1; }
.svg-mono { font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 9px; fill: #1c2733; font-weight: 600; }
.pcb-shadow { fill: #123020; }
.pcb-board { fill: #1d5c3a; stroke: #57b184; stroke-width: 1.5; }
.chip { fill: #10141a; stroke: #4a5462; stroke-width: 1; }
.chip-2 { fill: #171d26; }
.chip-3 { fill: #232b36; }
.mic-hole { fill: #0c1218; stroke: #4a5462; stroke-width: 1; }
.drv-body { fill: #3d4a57; stroke: #8fa2b5; stroke-width: 1.5; }
.drv-top { fill: #55636f; stroke: #8fa2b5; stroke-width: 1.5; }
.drv-cone { fill: #2c3742; stroke: #8fa2b5; stroke-width: 1; }
.drv-cap { fill: #6b7885; }
.grille-body { fill: #c8b593; stroke: #e9ddc4; stroke-width: 1.5; }
.grille-top { fill: #d9c7a7; stroke: #e9ddc4; stroke-width: 1.5; }
.grille-mesh {
fill: none;
stroke: rgba(28, 39, 51, 0.28);
stroke-width: 1;
stroke-dasharray: 2 3;
}
.grille-ring { fill: none; stroke: var(--orange); stroke-width: 2.5; opacity: 0.9; }
/* Leader lines */
.leader { opacity: 0; transition: opacity 0.3s; pointer-events: none; }
.leader line { stroke: var(--orange); stroke-width: 1.25; stroke-dasharray: 4 3; }
.leader circle { fill: var(--orange); }
.leader-tag { font-size: 11px; fill: var(--orange); font-weight: 700; }
.leader-txt { font-size: 11px; fill: var(--kraft-l); font-family: "Inter", system-ui, sans-serif; }
.stage.exploded-on .leader { opacity: 1; }
.slider-row {
display: flex;
align-items: center;
gap: 0.9rem;
margin-top: 1.1rem;
}
.slider-cap { font-size: 0.62rem; letter-spacing: 0.14em; color: var(--kraft); }
#explode-range {
flex: 1;
appearance: none;
-webkit-appearance: none;
height: 8px;
border-radius: 99px;
background: rgba(140, 180, 220, 0.25);
outline-offset: 4px;
cursor: pointer;
}
#explode-range::-webkit-slider-thumb {
appearance: none;
-webkit-appearance: none;
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--orange);
border: 3px solid var(--blueprint-d);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
transition: transform 0.12s;
}
#explode-range::-webkit-slider-thumb:hover { transform: scale(1.12); }
#explode-range::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--orange);
border: 3px solid var(--blueprint-d);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
.slider-readout {
margin-top: 0.45rem;
text-align: center;
font-size: 0.66rem;
letter-spacing: 0.16em;
color: var(--orange);
}
/* Detail card */
.detail-card {
background: var(--white);
border-radius: var(--r-lg);
box-shadow: var(--shadow-lg);
padding: 1.4rem;
position: sticky;
top: 76px;
}
.detail-head { display: flex; align-items: center; gap: 0.7rem; margin-bottom: 0.6rem; }
.detail-tag {
background: var(--blueprint);
color: var(--orange);
font-size: 0.72rem;
font-weight: 700;
border-radius: var(--r-sm);
padding: 0.3rem 0.5rem;
}
.detail-head h3 { font-size: 1.15rem; text-transform: uppercase; letter-spacing: 0.04em; }
.detail-desc { color: var(--muted); font-size: 0.88rem; margin: 0 0 0.9rem; }
.detail-sub {
font-size: 0.62rem;
letter-spacing: 0.18em;
color: var(--orange-d);
margin: 0.9rem 0 0.45rem;
}
.part-list, .conn-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.4rem; }
.part-list li, .conn-list li {
display: flex;
align-items: baseline;
gap: 0.6rem;
font-size: 0.84rem;
padding: 0.45rem 0.6rem;
background: var(--kraft-l);
border-radius: var(--r-sm);
border: 1px solid var(--line);
}
.part-no {
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 0.72rem;
font-weight: 700;
color: var(--blueprint);
white-space: nowrap;
}
.part-desc { color: var(--ink-2); }
.adhesive-note {
margin-top: 0.9rem;
border-left: 4px solid var(--warn);
background: #fdf3e3;
border-radius: 0 var(--r-sm) var(--r-sm) 0;
padding: 0.6rem 0.8rem;
font-size: 0.82rem;
color: var(--ink-2);
}
.adhesive-note.danger { border-left-color: var(--danger); background: #fdeae7; }
.adhesive-note strong { color: var(--ink); }
.layer-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 1.1rem; }
.layer-chip {
font-size: 0.64rem;
letter-spacing: 0.08em;
font-weight: 700;
border: 1px solid var(--line-2);
background: var(--white);
color: var(--ink-2);
border-radius: 99px;
padding: 0.4rem 0.7rem;
cursor: pointer;
transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.layer-chip:hover, .layer-chip:focus-visible { border-color: var(--orange); color: var(--orange-d); }
.layer-chip.active {
background: var(--orange);
border-color: var(--orange);
color: var(--blueprint-d);
}
/* ---------- Chapters ---------- */
.chapters { padding: 3.5rem 0 2rem; }
.chapter-list {
list-style: none;
margin: 0 auto;
padding: 0 1.25rem;
max-width: 900px;
display: grid;
gap: 1.1rem;
counter-reset: step;
}
.chapter {
display: grid;
grid-template-columns: 92px 1fr;
gap: 1.2rem;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 1.3rem 1.4rem;
box-shadow: var(--shadow);
transition: transform 0.18s, box-shadow 0.18s;
}
.chapter:hover { transform: translateY(-3px); box-shadow: 0 12px 32px rgba(28, 39, 51, 0.14); }
.chapter-num {
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.12em;
color: var(--orange-d);
border-right: 2px dashed var(--line-2);
padding-top: 0.25rem;
}
.chapter-body h3 { font-size: 1.1rem; text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: 0.4rem; }
.chapter-body p { margin: 0 0 0.8rem; color: var(--ink-2); font-size: 0.92rem; }
.tool-row { display: flex; flex-wrap: wrap; gap: 0.45rem; }
.tool-badge {
font-size: 0.64rem;
letter-spacing: 0.06em;
font-weight: 600;
background: var(--kraft-l);
border: 1px solid var(--line-2);
color: var(--ink-2);
border-radius: var(--r-sm);
padding: 0.3rem 0.55rem;
}
.caution {
margin-top: 0.9rem;
display: flex;
gap: 0.7rem;
align-items: flex-start;
border: 1px dashed var(--warn);
background: #fdf3e3;
border-radius: var(--r-sm);
padding: 0.65rem 0.8rem;
font-size: 0.84rem;
color: var(--ink-2);
}
.caution.danger { border-color: var(--danger); background: #fdeae7; }
.caution-tag {
flex-shrink: 0;
font-size: 0.6rem;
font-weight: 700;
letter-spacing: 0.12em;
color: var(--white);
background: var(--warn);
border-radius: 3px;
padding: 0.2rem 0.4rem;
margin-top: 0.1rem;
}
.caution.danger .caution-tag { background: var(--danger); }
/* ---------- IC table ---------- */
.ics { padding: 2.5rem 0 3rem; }
.table-wrap {
max-width: 900px;
margin: 0 auto;
padding: 0 1.25rem;
overflow-x: auto;
}
.ic-table {
width: 100%;
min-width: 620px;
border-collapse: collapse;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
overflow: hidden;
box-shadow: var(--shadow);
font-size: 0.78rem;
}
.ic-table th {
text-align: left;
background: var(--blueprint);
color: var(--kraft-l);
font-weight: 600;
letter-spacing: 0.08em;
font-size: 0.66rem;
padding: 0.7rem 0.9rem;
}
.ic-table td { padding: 0.62rem 0.9rem; border-top: 1px solid var(--line); color: var(--ink-2); }
.ic-table td:first-child { color: var(--orange-d); font-weight: 700; }
.ic-table td:nth-child(2) { font-weight: 600; color: var(--ink); }
.ic-table tbody tr { transition: background 0.12s; }
.ic-table tbody tr:hover { background: var(--kraft-l); }
/* ---------- Verdict ---------- */
.verdict {
background-color: var(--blueprint);
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px),
linear-gradient(var(--grid-major) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-major) 1px, transparent 1px);
background-size: 8px 8px, 8px 8px, 80px 80px, 80px 80px;
padding: 3.5rem 0 4rem;
}
.verdict-grid {
max-width: 1120px;
margin: 0 auto;
padding: 0 1.25rem;
display: grid;
grid-template-columns: 1fr 1fr 220px;
gap: 1.25rem;
align-items: start;
}
.verdict-col {
background: var(--white);
border-radius: var(--r-md);
padding: 1.3rem 1.4rem;
box-shadow: var(--shadow-lg);
}
.verdict-col h3 {
font-size: 0.95rem;
text-transform: uppercase;
letter-spacing: 0.06em;
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.8rem;
}
.v-mark {
display: grid;
place-items: center;
width: 26px;
height: 26px;
border-radius: 50%;
font-size: 0.9rem;
color: var(--white);
}
.pros .v-mark { background: var(--ok); }
.cons .v-mark { background: var(--danger); }
.verdict-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.55rem; }
.verdict-col li {
position: relative;
padding-left: 1.1rem;
font-size: 0.88rem;
color: var(--ink-2);
}
.verdict-col li::before {
content: "";
position: absolute;
left: 0;
top: 0.5em;
width: 7px;
height: 7px;
border-radius: 2px;
}
.pros li::before { background: var(--ok); }
.cons li::before { background: var(--danger); }
.verdict-stamp {
align-self: center;
justify-self: center;
text-align: center;
border: 3px dashed var(--orange);
border-radius: var(--r-lg);
padding: 1.4rem 1.6rem;
transform: rotate(-4deg);
background: rgba(10, 30, 54, 0.6);
transition: transform 0.2s;
}
.verdict-stamp:hover { transform: rotate(0deg) scale(1.03); }
.stamp-num { display: block; font-size: 3rem; font-weight: 700; color: var(--orange); line-height: 1; }
.stamp-num span { font-size: 1.2rem; color: var(--kraft); }
.stamp-txt {
display: block;
margin-top: 0.5rem;
font-size: 0.68rem;
font-weight: 800;
letter-spacing: 0.2em;
color: var(--kraft-l);
}
/* ---------- Footer ---------- */
.foot {
background: var(--blueprint-d);
color: var(--kraft);
text-align: center;
padding: 1.8rem 1.25rem 2.2rem;
font-size: 0.8rem;
}
.foot .mono { font-size: 0.66rem; letter-spacing: 0.12em; color: var(--muted); margin: 0 0 0.5rem; }
.foot p:last-child { margin: 0; }
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 1.5rem;
transform: translate(-50%, 16px);
background: var(--ink);
color: var(--kraft-l);
font-size: 0.72rem;
letter-spacing: 0.06em;
padding: 0.65rem 1.1rem;
border-radius: 99px;
border: 1px solid var(--orange);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s, transform 0.25s;
z-index: 60;
max-width: calc(100vw - 2rem);
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
/* ---------- Responsive ---------- */
@media (max-width: 900px) {
.hero-inner { grid-template-columns: 1fr; gap: 2.25rem; }
.exploded-grid { grid-template-columns: 1fr; }
.detail-card { position: static; }
.verdict-grid { grid-template-columns: 1fr 1fr; }
.verdict-stamp { grid-column: 1 / -1; }
.topnav { display: none; }
}
@media (max-width: 520px) {
.topbar-inner { padding: 0.6rem 1rem; }
.file-no { display: none; }
.hero { padding: 2.75rem 1rem 3rem; }
.hero-actions .btn { width: 100%; }
.chapter { grid-template-columns: 1fr; gap: 0.6rem; padding: 1.1rem 1rem; }
.chapter-num { border-right: 0; border-bottom: 2px dashed var(--line-2); padding-bottom: 0.4rem; }
.verdict-grid { grid-template-columns: 1fr; }
.slider-row { gap: 0.55rem; }
.slider-cap { font-size: 0.55rem; }
.score-card { padding: 1.2rem 1rem 1rem; }
}/* ---------- Toast helper ---------- */
const toastEl = document.getElementById("toast");
let toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toastEl.classList.remove("show"), 2200);
}
/* ---------- Repairability dial + criteria bars ---------- */
const SCORE = 7;
const CIRC = 2 * Math.PI * 66; // dial circumference (~414.7)
const dialValue = document.getElementById("dial-value");
const dialNum = document.getElementById("dial-num");
function animateDial() {
dialValue.style.strokeDashoffset = String(CIRC * (1 - SCORE / 10));
const start = performance.now();
const DUR = 1100;
function tick(now) {
const t = Math.min((now - start) / DUR, 1);
const eased = 1 - Math.pow(1 - t, 3);
dialNum.textContent = String(Math.round(eased * SCORE));
if (t < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
document.querySelectorAll(".crit-fill").forEach((bar, i) => {
const score = Number(bar.dataset.score) || 0;
setTimeout(() => { bar.style.width = score * 10 + "%"; }, 150 + i * 120);
});
}
const scoreCard = document.querySelector(".score-card");
const dialObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
animateDial();
dialObserver.disconnect();
}
});
}, { threshold: 0.4 });
dialObserver.observe(scoreCard);
/* ---------- Exploded view ---------- */
const LAYERS = [
{ id: "grille", el: document.getElementById("layer-grille"), collapsed: 128, exploded: 60 },
{ id: "driver", el: document.getElementById("layer-driver"), collapsed: 160, exploded: 180 },
{ id: "pcb", el: document.getElementById("layer-pcb"), collapsed: 190, exploded: 300 },
{ id: "battery", el: document.getElementById("layer-battery"), collapsed: 222, exploded: 420 },
{ id: "shell", el: document.getElementById("layer-shell"), collapsed: 256, exploded: 540 },
];
const DETAILS = {
grille: {
tag: "E1",
title: "Fabric grille + LED ring",
desc: "Acoustic fabric sleeve over a polycarbonate frame, with the halo LED diffuser seated on four rubber posts. No fasteners at this stage.",
parts: [
{ no: "GR-88F", desc: "Acoustic fabric sleeve, charcoal weave" },
{ no: "DF-HALO2", desc: "LED diffuser ring, milky PC" },
{ no: "LED-24RGB", desc: "24× side-fire RGB LEDs on flex" },
],
conns: [
{ no: "ZIF-12", desc: "12-pin ZIF, halo flex → main PCB" },
],
adhesive: { level: "warn", text: "<strong>No adhesive.</strong> The sleeve is friction-fit — roll it off from the bottom hem. Pulling straight up shears the diffuser posts." },
},
driver: {
tag: "E2",
title: "40 mm full-range driver",
desc: "Neodymium full-range driver in a sealed basket, sharing its bracket with the 3-mic far-field array. Held by four T6 Torx screws.",
parts: [
{ no: "DRV-40N", desc: "40 mm neodymium driver, 4 Ω / 5 W" },
{ no: "BRK-A1", desc: "Glass-filled nylon bracket" },
{ no: "MEMS-VA4 ×3", desc: "Sonaria digital MEMS mic array" },
],
conns: [
{ no: "JST-SH 2P", desc: "Voice-coil leads → amp output" },
{ no: "FLEX-MIC3", desc: "0.3 mm mic flex, folded twice" },
],
adhesive: { level: "warn", text: "<strong>Light tack only.</strong> A foam gasket seals the basket rim — it survives about three openings before it stops sealing." },
},
pcb: {
tag: "E3",
title: "Main logic board",
desc: "Single 6-layer board carrying the SoC, radio, amp and power stages. Three brass standoffs, one T6 at the antenna corner, RF shield over U1/U2.",
parts: [
{ no: "KMX-4410A", desc: "Quad-core SoC under soldered shield" },
{ no: "WFB-6E", desc: "Wi-Fi 6 / BT 5.3 combo module" },
{ no: "AMP-D23L", desc: "Class-D amplifier, 5 W" },
{ no: "PMC-330", desc: "PMIC + linear charger" },
],
conns: [
{ no: "JST-PH 2P", desc: "Battery input — disconnect FIRST" },
{ no: "ZIF-12", desc: "Halo LED flex latch" },
{ no: "USB-C 16P", desc: "Board-mounted power inlet" },
],
adhesive: { level: "danger", text: "<strong>Live circuits.</strong> No glue here, but the board is powered until the battery lead is pulled. Ground yourself; the RF shield edges are sharp." },
},
battery: {
tag: "E4",
title: "Li-Po battery pack",
desc: "Custom 3.7 V / 1.4 Ah pouch cell with an integrated protection board, seated in a kraft-lined tray on two stretch-release tabs.",
parts: [
{ no: "BAT-14LP", desc: "Li-Po pouch, 3.7 V 1.4 Ah / 5.2 Wh" },
{ no: "BMS-1S2A", desc: "1S protection PCB, 2 A cutoff" },
],
conns: [
{ no: "JST-PH 2P", desc: "Pack lead → main board" },
{ no: "NTC-10K", desc: "Thermistor bonded to pouch face" },
],
adhesive: { level: "danger", text: "<strong>Stretch-release tabs ×2.</strong> Pull slowly at a shallow angle (~15 cm of stretch each). If one snaps: 90% isopropyl, plastic card, patience. Never pry with metal." },
},
shell: {
tag: "E5",
title: "Base shell + port block",
desc: "Injection-moulded ABS base with a silicone foot, USB-C cutout and the passive radiator chamber moulded into the floor.",
parts: [
{ no: "SHL-ABS7", desc: "ABS lower housing, textured" },
{ no: "RAD-P52", desc: "52 mm passive radiator, glued" },
{ no: "FT-SIL1", desc: "Silicone anti-slip foot ring" },
],
conns: [
{ no: "NONE", desc: "No electrical connections in the shell" },
],
adhesive: { level: "warn", text: "<strong>Perimeter glue on the radiator.</strong> The passive radiator is bonded in — heat mat at 70 °C for 3 minutes before picking around the rim." },
},
};
const stage = document.getElementById("stage");
const range = document.getElementById("explode-range");
const readout = document.getElementById("explode-readout");
function applyExplode(pct) {
const t = pct / 100;
LAYERS.forEach((layer) => {
const y = layer.collapsed + (layer.exploded - layer.collapsed) * t;
layer.el.setAttribute("transform", `translate(0 ${y.toFixed(1)})`);
});
stage.classList.toggle("exploded-on", t > 0.3);
readout.textContent = `SEPARATION ${pct}%`;
}
range.addEventListener("input", () => applyExplode(Number(range.value)));
applyExplode(0);
/* Auto-explode gently the first time the stage scrolls into view */
let autoPlayed = false;
const stageObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !autoPlayed) {
autoPlayed = true;
stageObserver.disconnect();
const start = performance.now();
const DUR = 1400;
function step(now) {
const t = Math.min((now - start) / DUR, 1);
const eased = 1 - Math.pow(1 - t, 3);
const v = Math.round(eased * 65);
range.value = String(v);
applyExplode(v);
if (t < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
});
}, { threshold: 0.5 });
stageObserver.observe(stage);
/* ---------- Detail card ---------- */
const detailTag = document.getElementById("detail-tag");
const detailTitle = document.getElementById("detail-title");
const detailDesc = document.getElementById("detail-desc");
const detailBody = document.getElementById("detail-body");
const partList = document.getElementById("part-list");
const connList = document.getElementById("conn-list");
const adhesiveNote = document.getElementById("adhesive-note");
const chips = Array.from(document.querySelectorAll(".layer-chip"));
function renderList(ul, items) {
ul.innerHTML = "";
items.forEach((item) => {
const li = document.createElement("li");
const no = document.createElement("span");
no.className = "part-no";
no.textContent = item.no;
const desc = document.createElement("span");
desc.className = "part-desc";
desc.textContent = item.desc;
li.append(no, desc);
ul.appendChild(li);
});
}
function selectLayer(id, opts = {}) {
const data = DETAILS[id];
if (!data) return;
detailTag.textContent = data.tag;
detailTitle.textContent = data.title;
detailDesc.textContent = data.desc;
renderList(partList, data.parts);
renderList(connList, data.conns);
adhesiveNote.className = "adhesive-note" + (data.adhesive.level === "danger" ? " danger" : "");
adhesiveNote.innerHTML = data.adhesive.text;
detailBody.hidden = false;
LAYERS.forEach((l) => l.el.classList.toggle("selected", l.id === id));
chips.forEach((c) => c.classList.toggle("active", c.dataset.layer === id));
if (Number(range.value) < 25) {
range.value = "60";
applyExplode(60);
}
if (!opts.silent) toast(`${data.tag} — ${data.title}`);
}
LAYERS.forEach((layer) => {
layer.el.addEventListener("click", () => selectLayer(layer.id));
layer.el.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
selectLayer(layer.id);
}
});
});
chips.forEach((chip) => {
chip.addEventListener("click", () => selectLayer(chip.dataset.layer));
});
/* ---------- Print button ---------- */
document.getElementById("print-btn").addEventListener("click", () => {
toast("BENCH SHEET QUEUED — CHECK THE PLOTTER");
});
/* ---------- Chapter reveal on scroll ---------- */
const chapterEls = document.querySelectorAll(".chapter");
chapterEls.forEach((el) => {
el.style.opacity = "0";
el.style.transform = "translateY(16px)";
el.style.transition = "opacity 0.5s ease, transform 0.5s ease";
});
const chapterObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.style.opacity = "1";
entry.target.style.transform = "translateY(0)";
chapterObserver.unobserve(entry.target);
}
});
}, { threshold: 0.2 });
chapterEls.forEach((el) => chapterObserver.observe(el));<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Teardown — Soundbyte Puck Mini</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>
<!-- Top bar -->
<header class="topbar">
<div class="topbar-inner">
<a class="brand" href="#top" aria-label="Bench Report home">
<span class="brand-mark" aria-hidden="true">▙</span>
<span class="brand-name">BENCH<em>REPORT</em></span>
</a>
<nav class="topnav" aria-label="Page sections">
<a href="#exploded">Exploded view</a>
<a href="#chapters">Teardown</a>
<a href="#ics">IC table</a>
<a href="#verdict">Verdict</a>
</nav>
<span class="file-no mono">FILE TD-0147</span>
</div>
</header>
<!-- Hero -->
<section class="hero" id="top">
<div class="hero-inner">
<div class="hero-copy">
<p class="kicker mono">TEARDOWN REPORT · 30 JUL 2026 · BENCH 04</p>
<h1 class="hero-title">Soundbyte<br /><span>Puck Mini</span></h1>
<p class="hero-sub">
A 68 mm smart speaker puck with a far-field mic array and an LED halo.
We opened it with a heat mat, three picks and mild regret. Here is everything inside.
</p>
<div class="hero-meta">
<span class="meta-chip mono">MODEL SB-PM2</span>
<span class="meta-chip mono">FCC ID 2AXQ-PM2</span>
<span class="meta-chip mono">MASS 187 g</span>
<span class="meta-chip mono">TORX T6 ×4</span>
</div>
<div class="hero-actions">
<a class="btn btn-orange" href="#exploded">Explode the device</a>
<button class="btn btn-ghost" type="button" id="print-btn">Print bench sheet</button>
</div>
</div>
<!-- Repairability score card -->
<aside class="score-card" aria-label="Repairability score: 7 out of 10">
<div class="score-dial-wrap">
<svg class="score-dial" viewBox="0 0 160 160" role="img" aria-hidden="true">
<circle class="dial-track" cx="80" cy="80" r="66" />
<circle class="dial-value" id="dial-value" cx="80" cy="80" r="66" />
</svg>
<div class="dial-readout">
<span class="dial-num mono" id="dial-num">0</span>
<span class="dial-den mono">/10</span>
<span class="dial-label">REPAIRABILITY</span>
</div>
</div>
<ul class="criteria" id="criteria">
<li class="crit">
<div class="crit-head"><span>Opening ease</span><span class="mono crit-val">6/10</span></div>
<div class="crit-bar"><span class="crit-fill" data-score="6" style="--tone:var(--warn)"></span></div>
</li>
<li class="crit">
<div class="crit-head"><span>Part availability</span><span class="mono crit-val">5/10</span></div>
<div class="crit-bar"><span class="crit-fill" data-score="5" style="--tone:var(--warn)"></span></div>
</li>
<li class="crit">
<div class="crit-head"><span>Modularity</span><span class="mono crit-val">8/10</span></div>
<div class="crit-bar"><span class="crit-fill" data-score="8" style="--tone:var(--ok)"></span></div>
</li>
<li class="crit">
<div class="crit-head"><span>Documentation</span><span class="mono crit-val">9/10</span></div>
<div class="crit-bar"><span class="crit-fill" data-score="9" style="--tone:var(--ok)"></span></div>
</li>
</ul>
</aside>
</div>
</section>
<!-- Exploded view -->
<section class="exploded" id="exploded">
<div class="section-head light">
<span class="section-no mono">01</span>
<h2>Exploded view</h2>
<p>Drag the slider to separate the five layers. Click any layer for the parts card.</p>
</div>
<div class="exploded-grid">
<div class="stage-col">
<div class="stage" id="stage">
<svg id="exploded-svg" viewBox="0 0 440 620" aria-label="Exploded diagram of the Puck Mini, five layers">
<!-- Layer 5: shell (bottom) -->
<g class="layer" id="layer-shell" data-layer="shell" tabindex="0" role="button" aria-label="Base shell layer — open detail card">
<path d="M100 0 a110 26 0 0 1 220 0 v46 a110 26 0 0 1 -220 0 z" class="shape shell-body"/>
<ellipse cx="210" cy="0" rx="110" ry="26" class="shape shell-top"/>
<ellipse cx="210" cy="8" rx="72" ry="14" class="shape shell-port"/>
<g class="leader leader-right">
<line x1="322" y1="18" x2="392" y2="18" />
<circle cx="322" cy="18" r="3" />
<text x="396" y="14" class="leader-tag mono">E5</text>
<text x="396" y="30" class="leader-txt">Base shell</text>
</g>
</g>
<!-- Layer 4: battery -->
<g class="layer" id="layer-battery" data-layer="battery" tabindex="0" role="button" aria-label="Battery layer — open detail card">
<path d="M128 0 a82 20 0 0 1 164 0 v30 a82 20 0 0 1 -164 0 z" class="shape batt-body"/>
<ellipse cx="210" cy="0" rx="82" ry="20" class="shape batt-top"/>
<rect x="176" y="-8" width="68" height="14" rx="3" class="shape batt-label"/>
<text x="210" y="3" text-anchor="middle" class="svg-mono">3.7V 1.4Ah</text>
<g class="leader leader-left">
<line x1="126" y1="10" x2="48" y2="10" />
<circle cx="126" cy="10" r="3" />
<text x="44" y="6" text-anchor="end" class="leader-tag mono">E4</text>
<text x="44" y="22" text-anchor="end" class="leader-txt">Battery pack</text>
</g>
</g>
<!-- Layer 3: PCB -->
<g class="layer" id="layer-pcb" data-layer="pcb" tabindex="0" role="button" aria-label="Main PCB layer — open detail card">
<ellipse cx="210" cy="10" rx="98" ry="23" class="shape pcb-shadow"/>
<ellipse cx="210" cy="0" rx="98" ry="23" class="shape pcb-board"/>
<rect x="168" y="-12" width="34" height="16" rx="2" class="shape chip"/>
<rect x="212" y="-10" width="22" height="12" rx="2" class="shape chip chip-2"/>
<rect x="244" y="-6" width="16" height="9" rx="2" class="shape chip chip-3"/>
<circle cx="150" cy="-2" r="4" class="shape mic-hole"/>
<circle cx="270" cy="-2" r="4" class="shape mic-hole"/>
<g class="leader leader-right">
<line x1="310" y1="4" x2="392" y2="4" />
<circle cx="310" cy="4" r="3" />
<text x="396" y="0" class="leader-tag mono">E3</text>
<text x="396" y="16" class="leader-txt">Main PCB</text>
</g>
</g>
<!-- Layer 2: driver -->
<g class="layer" id="layer-driver" data-layer="driver" tabindex="0" role="button" aria-label="Speaker driver layer — open detail card">
<path d="M138 0 a72 18 0 0 1 144 0 v22 a72 18 0 0 1 -144 0 z" class="shape drv-body"/>
<ellipse cx="210" cy="0" rx="72" ry="18" class="shape drv-top"/>
<ellipse cx="210" cy="0" rx="44" ry="11" class="shape drv-cone"/>
<ellipse cx="210" cy="0" rx="16" ry="4.5" class="shape drv-cap"/>
<g class="leader leader-left">
<line x1="136" y1="8" x2="48" y2="8" />
<circle cx="136" cy="8" r="3" />
<text x="44" y="4" text-anchor="end" class="leader-tag mono">E2</text>
<text x="44" y="20" text-anchor="end" class="leader-txt">40 mm driver</text>
</g>
</g>
<!-- Layer 1: grille (top) -->
<g class="layer" id="layer-grille" data-layer="grille" tabindex="0" role="button" aria-label="Top grille layer — open detail card">
<path d="M104 0 a106 25 0 0 1 212 0 v18 a106 25 0 0 1 -212 0 z" class="shape grille-body"/>
<ellipse cx="210" cy="0" rx="106" ry="25" class="shape grille-top"/>
<ellipse cx="210" cy="0" rx="106" ry="25" class="shape grille-mesh"/>
<ellipse cx="210" cy="0" rx="60" ry="14" class="shape grille-ring"/>
<g class="leader leader-right">
<line x1="318" y1="6" x2="392" y2="6" />
<circle cx="318" cy="6" r="3" />
<text x="396" y="2" class="leader-tag mono">E1</text>
<text x="396" y="18" class="leader-txt">Fabric grille</text>
</g>
</g>
</svg>
</div>
<div class="slider-row">
<span class="mono slider-cap">ASSEMBLED</span>
<input type="range" id="explode-range" min="0" max="100" value="0" step="1"
aria-label="Explode amount, 0 assembled to 100 fully exploded" />
<span class="mono slider-cap">EXPLODED</span>
</div>
<div class="slider-readout mono" id="explode-readout">SEPARATION 0%</div>
</div>
<!-- Detail card -->
<aside class="detail-card" id="detail-card" aria-live="polite">
<div class="detail-head">
<span class="detail-tag mono" id="detail-tag">E0</span>
<h3 id="detail-title">Pick a layer</h3>
</div>
<p class="detail-desc" id="detail-desc">
Click or focus a layer in the diagram (or use the chips below) to inspect the parts,
connectors and adhesive notes for that stage of the teardown.
</p>
<div class="detail-body" id="detail-body" hidden>
<h4 class="detail-sub mono">IDENTIFIED PARTS</h4>
<ul class="part-list" id="part-list"></ul>
<h4 class="detail-sub mono">CONNECTORS</h4>
<ul class="conn-list" id="conn-list"></ul>
<div class="adhesive-note" id="adhesive-note"></div>
</div>
<div class="layer-chips" role="tablist" aria-label="Layers">
<button class="layer-chip mono" data-layer="grille" type="button">E1 GRILLE</button>
<button class="layer-chip mono" data-layer="driver" type="button">E2 DRIVER</button>
<button class="layer-chip mono" data-layer="pcb" type="button">E3 PCB</button>
<button class="layer-chip mono" data-layer="battery" type="button">E4 BATTERY</button>
<button class="layer-chip mono" data-layer="shell" type="button">E5 SHELL</button>
</div>
</aside>
</div>
</section>
<!-- Chapters -->
<section class="chapters" id="chapters">
<div class="section-head">
<span class="section-no mono">02</span>
<h2>Step-by-step teardown</h2>
<p>Four chapters, bench-tested twice. Total time on the clock: 22 minutes.</p>
</div>
<ol class="chapter-list">
<li class="chapter">
<div class="chapter-num mono">STEP 01</div>
<div class="chapter-body">
<h3>Peel the fabric grille</h3>
<p>
The grille sleeve is tensioned, not glued. Work a plastic pick under the bottom hem and
roll it upward like a sock. The LED diffuser ring underneath lifts off four rubber posts.
</p>
<div class="tool-row">
<span class="tool-badge mono">⛏ PLASTIC PICK</span>
<span class="tool-badge mono">⏱ 2 MIN</span>
</div>
</div>
</li>
<li class="chapter">
<div class="chapter-num mono">STEP 02</div>
<div class="chapter-body">
<h3>Free the driver assembly</h3>
<p>
Four T6 Torx screws hold the 40 mm driver basket. The voice-coil leads terminate in a
2-pin JST-SH — pull the connector, never the wires. The mic array flex rides on the same bracket.
</p>
<div class="tool-row">
<span class="tool-badge mono">🔧 TORX T6</span>
<span class="tool-badge mono">🔩 TWEEZERS</span>
<span class="tool-badge mono">⏱ 5 MIN</span>
</div>
<div class="caution">
<span class="caution-tag mono">CAUTION</span>
The mic flex cable is 0.3 mm thick and folded twice. Creasing it kills far-field pickup.
</div>
</div>
</li>
<li class="chapter">
<div class="chapter-num mono">STEP 03</div>
<div class="chapter-body">
<h3>Lift the main board</h3>
<p>
The PCB sits on three brass standoffs with a single T6 at the antenna corner. Disconnect the
battery FIRST (2-pin JST-PH), then the halo LED flex (12-pin ZIF — flip the black latch up).
</p>
<div class="tool-row">
<span class="tool-badge mono">🔧 TORX T6</span>
<span class="tool-badge mono">⚡ ESD STRAP</span>
<span class="tool-badge mono">⏱ 6 MIN</span>
</div>
<div class="caution danger">
<span class="caution-tag mono">HAZARD</span>
Board is live until the battery lead is pulled. Shorting the exposed test pads against the
shielding can cook the PMIC.
</div>
</div>
</li>
<li class="chapter">
<div class="chapter-num mono">STEP 04</div>
<div class="chapter-body">
<h3>Extract the battery</h3>
<p>
The 1.4 Ah pouch cell is held by two stretch-release adhesive tabs. Pull each tab slowly at
a shallow angle — around 15 cm of stretch each. If a tab snaps, switch to isopropyl and a card.
</p>
<div class="tool-row">
<span class="tool-badge mono">💧 ISOPROPYL 90%</span>
<span class="tool-badge mono">🃏 PLASTIC CARD</span>
<span class="tool-badge mono">⏱ 9 MIN</span>
</div>
<div class="caution danger">
<span class="caution-tag mono">HAZARD</span>
Never pry a pouch cell with metal. A punctured cell vents hot electrolyte. Work over a
fire-safe mat with the cell below 30% charge.
</div>
</div>
</li>
</ol>
</section>
<!-- IC table -->
<section class="ics" id="ics">
<div class="section-head">
<span class="section-no mono">03</span>
<h2>Identified silicon</h2>
<p>Every marked package on the main board, decoded at the microscope.</p>
</div>
<div class="table-wrap">
<table class="ic-table mono">
<thead>
<tr><th>REF</th><th>PART NO.</th><th>VENDOR</th><th>FUNCTION</th><th>PKG</th></tr>
</thead>
<tbody>
<tr><td>U1</td><td>KMX-4410A</td><td>Kelvinmax</td><td>Quad-core SoC, 1.2 GHz</td><td>BGA-256</td></tr>
<tr><td>U2</td><td>NVR-512Q</td><td>Nevara</td><td>512 MB LPDDR4</td><td>BGA-200</td></tr>
<tr><td>U3</td><td>FSH-8GTe</td><td>Flashet</td><td>8 GB eMMC flash</td><td>BGA-153</td></tr>
<tr><td>U4</td><td>AMP-D23L</td><td>Voxtron</td><td>Class-D amp, 5 W</td><td>QFN-24</td></tr>
<tr><td>U5</td><td>WFB-6E</td><td>Airlume</td><td>Wi-Fi 6 + BT 5.3 combo</td><td>LGA-40</td></tr>
<tr><td>U6</td><td>PMC-330</td><td>Kelvinmax</td><td>PMIC + charger</td><td>QFN-32</td></tr>
<tr><td>U7</td><td>MEMS-VA4 ×3</td><td>Sonaria</td><td>Digital MEMS microphones</td><td>LGA-5</td></tr>
</tbody>
</table>
</div>
</section>
<!-- Verdict -->
<section class="verdict" id="verdict">
<div class="section-head light">
<span class="section-no mono">04</span>
<h2>Verdict</h2>
<p>Where the Puck Mini earns its 7 — and where it drops points.</p>
</div>
<div class="verdict-grid">
<div class="verdict-col pros">
<h3><span class="v-mark">+</span> What we liked</h3>
<ul>
<li>Standard Torx T6 fasteners throughout — zero proprietary screws.</li>
<li>Modular driver, board and battery separate in under ten minutes.</li>
<li>Stretch-release battery tabs instead of hard glue.</li>
<li>Every connector is keyed; wrong reassembly is nearly impossible.</li>
<li>Silkscreened test pads are labelled on the board itself.</li>
</ul>
</div>
<div class="verdict-col cons">
<h3><span class="v-mark">-</span> What hurts repair</h3>
<ul>
<li>Fabric grille stretches out after 2–3 removals; no spare sold.</li>
<li>Battery is a custom pouch — no listed replacement part yet.</li>
<li>Mic flex is fragile and routed under the driver bracket.</li>
<li>LED diffuser posts shear off if the grille is pulled straight up.</li>
</ul>
</div>
<div class="verdict-stamp" aria-label="Final score seven out of ten">
<span class="stamp-num mono">7<span>/10</span></span>
<span class="stamp-txt">REPAIRABLE<br />WITH CARE</span>
</div>
</div>
</section>
<footer class="foot">
<p class="mono">BENCH REPORT · TD-0147 · SOUNDBYTE PUCK MINI — fictional device, fictional silicon.</p>
<p>Illustrative UI only — always follow real safety guidance when working with tools, electronics, or repairs.</p>
</footer>
<div class="toast mono" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Product Teardown
A full teardown report page in the workshop/blueprint aesthetic, documenting a fictional smart speaker (the Soundbyte Puck Mini). The hero pairs stencil-style headings and mono spec chips with a repairability score card: an SVG dial that animates to 7/10 on scroll, plus four criterion bars (opening ease, part availability, modularity, documentation) that fill with color-coded tones.
The centerpiece is an interactive exploded view on a blueprint grid. Five layered SVG groups — fabric grille, 40 mm driver, main PCB, battery pack and base shell — separate vertically as you drag a range slider, revealing dashed leader lines and E1–E5 labels. Clicking (or keyboard-activating) any layer, or its chip in the sidebar, populates a sticky detail card with mono part numbers, connector types and an adhesive/hazard note; the view auto-explodes gently the first time it scrolls into view.
Below, four step-by-step chapters carry tool badges, timing chips and caution/hazard callouts, followed by a mono table of identified ICs with fictional part numbers, and a verdict section with pros/cons columns and a rotated dashed “7/10” stamp. A toast helper confirms interactions throughout.
Illustrative UI only — always follow real safety guidance when working with tools, electronics, or repairs.