DIY — Device Hub Page
A dense, utilitarian device hub page for a fictional laptop in the FIXWELL repair-manual community — breadcrumb trail, an SVG device render with model-identification tips in a mono callout, a repairability score chip, an animated stat strip, guides grouped into component accordions with difficulty badges and a dimming filter, a symptom-to-guide troubleshooting list, a sticky parts-and-tools rail with price chips and add-to-cart toasts, plus a related-models row.
MCP
Code
:root {
--white: #ffffff;
--bg: #f7f9fa;
--surface: #f2f5f7;
--surface-2: #e8eef1;
--ink: #12212b;
--ink-2: #33454f;
--muted: #667884;
--teal: #0f7fa8;
--teal-d: #0b6488;
--teal-soft: #e3f1f7;
--green: #3fa34d;
--green-d: #2f8a3d;
--green-soft: #e6f4e8;
--amber: #c07a12;
--amber-soft: #fbf0dc;
--red: #c2453a;
--red-soft: #fbe7e5;
--line: #dbe3e8;
--line-2: #c3d0d8;
--r-sm: 4px;
--r-md: 8px;
--r-lg: 12px;
--sh-1: 0 1px 2px rgba(18, 33, 43, .06);
--sh-2: 0 4px 14px rgba(18, 33, 43, .08);
--wrap: 1180px;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
font-family: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
img, svg { max-width: 100%; }
a { color: inherit; text-decoration: none; }
h1, h2, h3 { margin: 0; line-height: 1.2; letter-spacing: -.015em; }
ul, ol { margin: 0; padding: 0; list-style: none; }
button { font: inherit; color: inherit; }
.mono, .mono-block, code { font-family: "JetBrains Mono", ui-monospace, "SFMono-Regular", monospace; }
:focus-visible {
outline: 2px solid var(--teal);
outline-offset: 2px;
border-radius: var(--r-sm);
}
.skip {
position: absolute; left: -9999px; top: 8px; z-index: 100;
background: var(--teal); color: #fff; padding: 8px 14px; border-radius: var(--r-md);
}
.skip:focus { left: 12px; }
.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 20px; }
/* ---------- topbar ---------- */
.topbar {
position: sticky; top: 0; z-index: 40;
background: rgba(255, 255, 255, .92);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--line);
}
.topbar-in { display: flex; align-items: center; gap: 22px; height: 58px; }
.brand { display: inline-flex; align-items: center; gap: 9px; }
.brand-mark { width: 26px; height: 26px; border-radius: 7px; }
.brand-name { font-weight: 800; letter-spacing: .1em; font-size: 15px; }
.topnav { display: flex; gap: 20px; margin-left: 6px; font-size: 14px; font-weight: 500; color: var(--ink-2); }
.topnav a { padding: 4px 0; border-bottom: 2px solid transparent; transition: color .15s, border-color .15s; }
.topnav a:hover { color: var(--teal); border-bottom-color: var(--teal); }
.topbar-actions { margin-left: auto; }
.btn {
border: 1px solid var(--line-2); background: var(--white);
border-radius: var(--r-md); padding: 7px 14px;
font-size: 13.5px; font-weight: 600; cursor: pointer;
transition: background .15s, border-color .15s, transform .1s, box-shadow .15s;
}
.btn:hover { border-color: var(--teal); color: var(--teal); }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--teal); border-color: var(--teal); color: #fff; box-shadow: var(--sh-1); }
.btn-primary:hover { background: var(--teal-d); border-color: var(--teal-d); color: #fff; }
.btn-ghost { background: transparent; }
.btn-block { display: block; width: 100%; margin-top: 10px; }
.cart-count {
display: inline-block; min-width: 19px; padding: 0 5px;
background: var(--green); color: #fff; border-radius: 999px;
font-size: 11.5px; font-weight: 700; text-align: center; margin-left: 4px;
font-family: "JetBrains Mono", monospace;
}
/* ---------- breadcrumb ---------- */
.crumbs { padding: 16px 0 12px; font-size: 13px; color: var(--muted); }
.crumbs ol { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.crumbs li + li::before { content: "/"; margin-right: 6px; color: var(--line-2); }
.crumbs a:hover { color: var(--teal); text-decoration: underline; }
.crumbs [aria-current] { color: var(--ink); font-weight: 600; }
/* ---------- card ---------- */
.card {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-1);
}
/* ---------- hero ---------- */
.hero {
display: grid; grid-template-columns: minmax(0, 400px) minmax(0, 1fr);
gap: 28px; padding: 24px; align-items: start;
}
.hero-render {
position: relative;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px;
background-image:
linear-gradient(rgba(15, 127, 168, .07) 1px, transparent 1px),
linear-gradient(90deg, rgba(15, 127, 168, .07) 1px, transparent 1px);
background-size: 16px 16px;
}
.hero-render svg { display: block; width: 100%; height: auto; }
.render-tag {
position: absolute; left: 12px; bottom: 12px;
font-family: "JetBrains Mono", monospace; font-size: 11px; font-weight: 600;
background: var(--white); border: 1px solid var(--line-2);
border-radius: var(--r-sm); padding: 3px 7px; color: var(--muted);
}
.eyebrow {
margin: 0 0 6px; font-size: 11.5px; font-weight: 700;
letter-spacing: .12em; text-transform: uppercase; color: var(--teal);
font-family: "JetBrains Mono", monospace;
}
.hero-info h1 { font-size: clamp(26px, 4vw, 36px); font-weight: 800; }
.hero-info h1 .year { color: var(--muted); font-weight: 500; }
.lede { margin: 10px 0 16px; color: var(--ink-2); font-size: 15px; max-width: 62ch; }
.hero-chips { display: flex; flex-wrap: wrap; gap: 10px; align-items: stretch; margin-bottom: 16px; }
.score-chip {
display: inline-flex; align-items: center; gap: 11px;
background: var(--green-soft); border: 1px solid #bfe0c5;
border-radius: var(--r-md); padding: 8px 14px 8px 10px;
transition: box-shadow .15s, transform .12s;
}
.score-chip:hover { box-shadow: var(--sh-2); transform: translateY(-1px); }
.score-num {
font-family: "JetBrains Mono", monospace; font-size: 26px; font-weight: 700;
color: var(--green-d); line-height: 1;
}
.score-num small { font-size: 12px; font-weight: 600; opacity: .65; }
.score-body { display: flex; flex-direction: column; font-size: 12.5px; line-height: 1.35; }
.score-body strong { font-size: 13.5px; }
.score-body span:last-child { color: var(--ink-2); text-decoration: underline; text-underline-offset: 2px; }
.chip {
display: inline-flex; align-items: center; padding: 0 12px;
border: 1px solid var(--line-2); border-radius: var(--r-md);
font-size: 12.5px; font-weight: 600; color: var(--ink-2); background: var(--surface);
}
.chip-ok { background: var(--teal-soft); border-color: #b6dced; color: var(--teal-d); }
.callout {
border: 1px solid var(--line); border-left: 3px solid var(--teal);
background: var(--surface); border-radius: var(--r-md); padding: 14px 16px;
}
.callout-h {
display: flex; align-items: center; gap: 7px; color: var(--teal-d);
font-weight: 700; font-size: 13.5px; margin-bottom: 8px;
}
.callout p { margin: 0 0 8px; font-size: 13.5px; color: var(--ink-2); }
.mono-block {
margin: 0 0 8px; padding: 10px 12px; background: var(--ink); color: #d7e6ee;
border-radius: var(--r-sm); font-size: 12.5px; line-height: 1.7;
white-space: pre; overflow-x: auto;
}
.mono-block b { color: #7fd3a0; }
.callout-note { margin: 0 !important; font-size: 12.5px; color: var(--muted); }
.callout-note code { background: var(--surface-2); padding: 1px 5px; border-radius: 3px; font-size: 12px; }
.callout-note a { color: var(--teal); text-decoration: underline; }
/* ---------- stats ---------- */
.stats {
display: grid; grid-template-columns: repeat(4, 1fr);
gap: 1px; background: var(--line);
border: 1px solid var(--line); border-radius: var(--r-lg);
overflow: hidden; margin: 18px 0 26px;
}
.stat { background: var(--white); padding: 16px 18px; }
.stat-n {
display: block; font-family: "JetBrains Mono", monospace;
font-size: 24px; font-weight: 700; color: var(--teal-d); letter-spacing: -.02em;
}
.stat-l { display: block; font-size: 12.5px; color: var(--muted); margin-top: 2px; }
/* ---------- layout ---------- */
.layout { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 26px; align-items: start; }
.col-main > section { margin-bottom: 34px; }
.sec-head {
display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px 16px;
margin-bottom: 12px; padding-bottom: 10px; border-bottom: 1px solid var(--line);
}
.sec-head h2 { font-size: 19px; font-weight: 700; }
.sec-note { font-size: 12.5px; color: var(--muted); }
/* ---------- filters ---------- */
.filters { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-left: auto; }
.filters-l {
font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
color: var(--muted); font-family: "JetBrains Mono", monospace; margin-right: 2px;
}
.fbtn {
border: 1px solid var(--line-2); background: var(--white); color: var(--ink-2);
border-radius: 999px; padding: 4px 11px; font-size: 12.5px; font-weight: 600;
cursor: pointer; transition: all .15s;
}
.fbtn:hover { border-color: var(--teal); color: var(--teal); }
.fbtn.is-on { background: var(--ink); border-color: var(--ink); color: #fff; }
.filter-status { margin: 0 0 12px; font-size: 12.5px; color: var(--muted); }
/* ---------- accordion ---------- */
.accordions { display: flex; flex-direction: column; gap: 8px; }
.acc { background: var(--white); border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden; }
.acc h3 { margin: 0; font-size: inherit; font-weight: inherit; }
.acc-btn {
display: flex; align-items: center; gap: 11px; width: 100%;
background: none; border: 0; padding: 12px 14px; cursor: pointer; text-align: left;
transition: background .15s;
}
.acc-btn:hover { background: var(--surface); }
.acc-ico {
display: grid; place-items: center; width: 30px; height: 30px; flex: none;
border-radius: var(--r-sm); background: var(--teal-soft); color: var(--teal-d);
}
.acc-ico svg { width: 18px; height: 18px; }
.acc-title { font-size: 15px; font-weight: 700; }
.acc-meta { margin-left: auto; display: inline-flex; align-items: center; gap: 10px; }
.acc-count { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--muted); }
.chev { width: 14px; height: 14px; color: var(--muted); transition: transform .25s ease; }
.acc.is-open .chev { transform: rotate(180deg); }
.acc.is-open .acc-btn { background: var(--surface); border-bottom: 1px solid var(--line); }
.acc-panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .28s ease; }
.acc-panel > * { overflow: hidden; }
.acc.is-open .acc-panel { grid-template-rows: 1fr; }
.glist { padding: 0; }
.grow + .grow { border-top: 1px solid var(--line); }
.grow a {
display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px;
padding: 11px 14px 11px 55px; transition: background .15s, padding-left .15s;
}
.grow a:hover { background: var(--teal-soft); padding-left: 59px; }
.g-name { font-size: 14px; font-weight: 500; }
.grow a:hover .g-name { color: var(--teal-d); }
.g-tags { margin-left: auto; display: inline-flex; align-items: center; gap: 10px; }
.g-t, .g-s { font-family: "JetBrains Mono", monospace; font-size: 11.5px; color: var(--muted); }
.badge {
font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 999px;
border: 1px solid transparent; white-space: nowrap;
}
.b-easy { background: var(--green-soft); color: var(--green-d); border-color: #c3e2c8; }
.b-mod { background: var(--amber-soft); color: var(--amber); border-color: #eddcbb; }
.b-hard { background: var(--red-soft); color: var(--red); border-color: #f0cfcb; }
.grow.is-dim { opacity: .28; filter: saturate(.25); }
.grow.is-dim a { pointer-events: none; }
.grow { transition: opacity .22s ease, filter .22s ease; }
/* ---------- problems ---------- */
.problems { display: flex; flex-direction: column; gap: 6px; }
.problems a {
display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
gap: 8px 16px; align-items: center;
background: var(--white); border: 1px solid var(--line); border-left: 3px solid var(--line-2);
border-radius: var(--r-md); padding: 12px 14px;
transition: border-left-color .15s, box-shadow .15s, transform .12s;
}
.problems a:hover { border-left-color: var(--green); box-shadow: var(--sh-2); transform: translateX(2px); }
.p-sym { font-size: 14px; font-weight: 600; }
.p-fix { font-size: 13px; color: var(--teal-d); }
.p-arrow { display: inline-block; transition: transform .18s ease; }
.problems a:hover .p-arrow { transform: translateX(4px); }
.p-tally { font-family: "JetBrains Mono", monospace; font-size: 11.5px; color: var(--muted); white-space: nowrap; }
/* ---------- score ---------- */
.score-card { padding: 20px; }
.score-card h2 { font-size: 18px; }
.score-card .sec-note { display: block; margin: 4px 0 16px; }
.bars { display: flex; flex-direction: column; gap: 11px; }
.bars li { display: grid; grid-template-columns: 150px minmax(0, 1fr) 40px; gap: 12px; align-items: center; }
.bar-l { font-size: 13px; color: var(--ink-2); }
.bar { height: 8px; background: var(--surface-2); border-radius: 999px; overflow: hidden; }
.bar i {
display: block; height: 100%; width: var(--v);
background: linear-gradient(90deg, var(--teal), var(--green));
border-radius: 999px;
transform-origin: left; transform: scaleX(0);
transition: transform .8s cubic-bezier(.2, .8, .2, 1);
}
.bar.is-in i { transform: scaleX(1); }
.bar-v { font-size: 12.5px; font-weight: 700; text-align: right; color: var(--ink); }
/* ---------- related ---------- */
.rel-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px; }
.rel {
display: block; background: var(--white); border: 1px solid var(--line);
border-radius: var(--r-md); padding: 12px; text-align: center;
transition: border-color .15s, box-shadow .15s, transform .12s;
}
.rel:hover { border-color: var(--teal); box-shadow: var(--sh-2); transform: translateY(-2px); }
.rel-sv { display: block; background: var(--surface); border-radius: var(--r-sm); padding: 10px; margin-bottom: 9px; }
.rel-sv svg { display: block; width: 100%; height: auto; max-height: 56px; }
.rel-n { display: block; font-size: 13.5px; font-weight: 600; }
.rel-y { display: block; font-size: 11.5px; color: var(--muted); margin-top: 2px; }
/* ---------- rail ---------- */
.rail { position: sticky; top: 74px; }
.rail-card { padding: 16px; }
.rail-h { font-size: 15px; font-weight: 700; padding-bottom: 10px; border-bottom: 1px solid var(--line); }
.rail-sub {
font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
color: var(--muted); font-family: "JetBrains Mono", monospace;
margin: 18px 0 8px; padding-top: 14px; border-top: 1px solid var(--line);
}
.parts { display: flex; flex-direction: column; }
.part { display: grid; grid-template-columns: 40px minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 11px 0; }
.part + .part { border-top: 1px solid var(--line); }
.part-sv { background: var(--surface); border-radius: var(--r-sm); padding: 4px; }
.part-sv svg { display: block; width: 100%; height: auto; }
.part-n { display: block; font-size: 13px; font-weight: 600; line-height: 1.3; }
.part-m { display: block; font-size: 10.5px; color: var(--muted); margin-top: 2px; }
.part-r { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }
.price { font-size: 13px; font-weight: 700; }
.add {
border: 1px solid var(--teal); background: var(--white); color: var(--teal-d);
border-radius: var(--r-sm); padding: 3px 10px; font-size: 12px; font-weight: 700;
cursor: pointer; transition: all .15s;
}
.add:hover { background: var(--teal); color: #fff; }
.add:active { transform: scale(.95); }
.add.is-added { background: var(--green); border-color: var(--green); color: #fff; }
.tools { display: flex; flex-direction: column; }
.tool { display: flex; align-items: center; gap: 10px; padding: 8px 0; font-size: 13px; }
.tool + .tool { border-top: 1px dashed var(--line); }
.tool-n { flex: 1; min-width: 0; }
.add-sm { padding: 2px 9px; font-size: 11.5px; }
.rail-total {
display: flex; justify-content: space-between; align-items: center;
margin-top: 14px; padding-top: 12px; border-top: 2px solid var(--ink);
font-size: 13.5px; font-weight: 600;
}
.rail-total strong { font-size: 17px; }
/* ---------- footer ---------- */
.foot { margin-top: 40px; border-top: 1px solid var(--line); background: var(--surface); }
.foot-in {
display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px;
padding-top: 20px; padding-bottom: 24px; font-size: 12.5px; color: var(--muted);
}
/* ---------- toaster ---------- */
.toaster {
position: fixed; right: 16px; bottom: 16px; z-index: 90;
display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
pointer-events: none;
}
.toast {
background: var(--ink); color: #eaf3f7;
border-radius: var(--r-md); padding: 10px 15px;
font-size: 13px; font-weight: 500; box-shadow: var(--sh-2);
display: flex; align-items: center; gap: 9px;
animation: toast-in .26s cubic-bezier(.2, .8, .2, 1);
max-width: min(340px, calc(100vw - 32px));
}
.toast::before {
content: ""; width: 7px; height: 7px; border-radius: 50%;
background: var(--green); flex: none;
}
.toast.is-out { animation: toast-out .22s ease forwards; }
@keyframes toast-in { from { opacity: 0; transform: translateY(10px) scale(.96); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(6px) scale(.97); } }
@media (prefers-reduced-motion: reduce) {
* { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
/* ---------- responsive ---------- */
@media (max-width: 980px) {
.layout { grid-template-columns: 1fr; }
.rail { position: static; }
.hero { grid-template-columns: 1fr; }
.stats { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 700px) {
.topnav { display: none; }
.problems a { grid-template-columns: 1fr; gap: 4px; }
.p-tally { justify-self: start; }
}
@media (max-width: 520px) {
.wrap { padding: 0 14px; }
.hero { padding: 16px; gap: 18px; }
.hero-info h1 { font-size: 24px; }
.lede { font-size: 14px; }
.stats { grid-template-columns: 1fr 1fr; }
.stat { padding: 12px 14px; }
.stat-n { font-size: 20px; }
.sec-head { flex-direction: column; align-items: flex-start; }
.filters { margin-left: 0; }
.grow a { padding: 11px 12px; }
.grow a:hover { padding-left: 12px; }
.g-tags { margin-left: 0; width: 100%; }
.bars li { grid-template-columns: 110px minmax(0, 1fr) 34px; gap: 8px; }
.bar-l { font-size: 12px; }
.rel-row { grid-template-columns: 1fr 1fr; }
.score-chip { width: 100%; }
.toaster { left: 12px; right: 12px; align-items: stretch; }
}(function () {
"use strict";
/* ---------- toast helper ---------- */
var toaster = document.getElementById("toaster");
function toast(msg, ms) {
if (!toaster) return;
var el = document.createElement("div");
el.className = "toast";
el.textContent = msg;
toaster.appendChild(el);
window.setTimeout(function () {
el.classList.add("is-out");
window.setTimeout(function () { el.remove(); }, 240);
}, ms || 2400);
}
/* ---------- accordions ---------- */
var accBtns = document.querySelectorAll(".acc-btn");
Array.prototype.forEach.call(accBtns, function (btn) {
btn.addEventListener("click", function () {
var acc = btn.closest(".acc");
var open = acc.classList.toggle("is-open");
btn.setAttribute("aria-expanded", open ? "true" : "false");
});
});
function openAccordionsWithVisibleRows() {
Array.prototype.forEach.call(document.querySelectorAll(".acc"), function (acc) {
var rows = acc.querySelectorAll(".grow");
var visible = 0;
Array.prototype.forEach.call(rows, function (r) {
if (!r.classList.contains("is-dim")) visible++;
});
var btn = acc.querySelector(".acc-btn");
if (visible > 0 && !acc.classList.contains("is-open")) {
acc.classList.add("is-open");
btn.setAttribute("aria-expanded", "true");
}
});
}
/* ---------- difficulty filter ---------- */
var allRows = document.querySelectorAll(".grow");
var status = document.querySelector("[data-filter-status]");
var fbtns = document.querySelectorAll(".fbtn");
var total = allRows.length;
function applyFilter(value) {
var shown = 0;
Array.prototype.forEach.call(allRows, function (row) {
var match = value === "all" || row.getAttribute("data-difficulty") === value;
row.classList.toggle("is-dim", !match);
if (match) shown++;
});
Array.prototype.forEach.call(fbtns, function (b) {
var on = b.getAttribute("data-diff") === value;
b.classList.toggle("is-on", on);
b.setAttribute("aria-pressed", on ? "true" : "false");
});
if (status) {
status.textContent = value === "all"
? "Showing all " + total + " guides."
: "Highlighting " + shown + " of " + total + " guides rated " + value + ".";
}
if (value !== "all") openAccordionsWithVisibleRows();
}
Array.prototype.forEach.call(fbtns, function (b) {
b.addEventListener("click", function () {
applyFilter(b.getAttribute("data-diff"));
});
});
applyFilter("all");
/* ---------- cart ---------- */
var cart = [];
var countEl = document.querySelector("[data-cart-count]");
var totalEl = document.querySelector("[data-cart-total]");
function renderCart() {
var sum = cart.reduce(function (a, i) { return a + i.price; }, 0);
if (countEl) countEl.textContent = String(cart.length);
if (totalEl) totalEl.textContent = "$" + sum;
}
Array.prototype.forEach.call(document.querySelectorAll("[data-add]"), function (btn) {
btn.addEventListener("click", function () {
var name = btn.getAttribute("data-add");
var price = parseFloat(btn.getAttribute("data-price")) || 0;
cart.push({ name: name, price: price });
renderCart();
btn.classList.add("is-added");
btn.textContent = "Added";
window.setTimeout(function () {
btn.classList.remove("is-added");
btn.textContent = "Add";
}, 1300);
toast(name + " added to your repair kit — $" + price);
});
});
Array.prototype.forEach.call(document.querySelectorAll("[data-cart-open]"), function (btn) {
btn.addEventListener("click", function () {
if (!cart.length) { toast("Your repair kit is empty — add a part or tool."); return; }
var sum = cart.reduce(function (a, i) { return a + i.price; }, 0);
toast(cart.length + " item" + (cart.length > 1 ? "s" : "") + " in the kit · subtotal $" + sum, 3200);
});
});
renderCart();
/* ---------- smooth scroll to score ---------- */
var scoreLink = document.querySelector("[data-score-link]");
if (scoreLink) {
scoreLink.addEventListener("click", function (e) {
var target = document.getElementById("score");
if (!target) return;
e.preventDefault();
target.scrollIntoView({ behavior: "smooth", block: "center" });
target.focus({ preventScroll: true });
});
}
/* ---------- reveal: counters + bars ---------- */
var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
function countUp(el) {
var target = parseInt(el.getAttribute("data-count"), 10) || 0;
var suffix = el.getAttribute("data-suffix") || "";
if (reduce) { el.textContent = target.toLocaleString("en-US") + suffix; return; }
var start = performance.now();
var dur = 900;
function tick(now) {
var p = Math.min(1, (now - start) / dur);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = Math.round(target * eased).toLocaleString("en-US") + suffix;
if (p < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
}
var counters = document.querySelectorAll("[data-count]");
var bars = document.querySelectorAll(".bar");
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
var el = entry.target;
if (el.hasAttribute("data-count")) countUp(el);
else el.classList.add("is-in");
io.unobserve(el);
});
}, { threshold: 0.4 });
Array.prototype.forEach.call(counters, function (c) { io.observe(c); });
Array.prototype.forEach.call(bars, function (b) { io.observe(b); });
} else {
Array.prototype.forEach.call(counters, countUp);
Array.prototype.forEach.call(bars, function (b) { b.classList.add("is-in"); });
}
/* ---------- guide + problem rows are demo links ---------- */
Array.prototype.forEach.call(document.querySelectorAll(".grow a, .problems a"), function (a) {
a.addEventListener("click", function (e) {
e.preventDefault();
var name = a.querySelector(".g-name") || a.querySelector(".p-fix");
toast("Opening guide: " + (name ? name.textContent.replace("→", "").trim() : "guide"));
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>FIXWELL — Volta Book 14 (2023) Repair Hub</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="#main">Skip to content</a>
<header class="topbar">
<div class="wrap topbar-in">
<a class="brand" href="#main" aria-label="FIXWELL home">
<svg class="brand-mark" viewBox="0 0 32 32" aria-hidden="true">
<rect x="1" y="1" width="30" height="30" rx="8" fill="#0f7fa8"/>
<path d="M9 21.5 19.5 11a3 3 0 0 0-4-4L5 17.5z" fill="#fff" opacity=".92"/>
<circle cx="22.5" cy="21.5" r="4.5" fill="none" stroke="#3fa34d" stroke-width="2.6"/>
<path d="M22.5 17v9M18 21.5h9" stroke="#3fa34d" stroke-width="2.6" stroke-linecap="round"/>
</svg>
<span class="brand-name">FIXWELL</span>
</a>
<nav class="topnav" aria-label="Primary">
<a href="#guides">Guides</a>
<a href="#problems">Troubleshoot</a>
<a href="#parts">Parts</a>
<a href="#related">Devices</a>
</nav>
<div class="topbar-actions">
<button class="btn btn-ghost" type="button" data-cart-open>
Cart <span class="cart-count" data-cart-count>0</span>
</button>
</div>
</div>
</header>
<main id="main">
<div class="wrap">
<nav class="crumbs" aria-label="Breadcrumb">
<ol>
<li><a href="#main">Devices</a></li>
<li><a href="#main">Laptops</a></li>
<li><a href="#main">Volta</a></li>
<li><span aria-current="page">Book 14</span></li>
</ol>
</nav>
<!-- HERO -->
<section class="hero card" aria-labelledby="device-title">
<div class="hero-render">
<svg viewBox="0 0 320 220" role="img" aria-label="Illustration of the Volta Book 14 laptop, open, viewed at an angle">
<defs>
<linearGradient id="lidG" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#dfe7ec"/><stop offset="1" stop-color="#b6c4cd"/>
</linearGradient>
<linearGradient id="scrG" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#123b4c"/><stop offset="1" stop-color="#0b2029"/>
</linearGradient>
<linearGradient id="baseG" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#cfdae1"/><stop offset="1" stop-color="#9fb0bb"/>
</linearGradient>
</defs>
<ellipse cx="160" cy="198" rx="126" ry="12" fill="#12212b" opacity=".10"/>
<!-- lid -->
<path d="M64 22h192a6 6 0 0 1 6 6v112H58V28a6 6 0 0 1 6-6z" fill="url(#lidG)"/>
<path d="M70 30h180v104H70z" fill="url(#scrG)"/>
<g opacity=".55">
<rect x="80" y="42" width="70" height="7" rx="3.5" fill="#0f7fa8"/>
<rect x="80" y="56" width="120" height="4" rx="2" fill="#7fb6c9" opacity=".6"/>
<rect x="80" y="66" width="98" height="4" rx="2" fill="#7fb6c9" opacity=".45"/>
<rect x="80" y="86" width="52" height="34" rx="4" fill="#3fa34d" opacity=".45"/>
<rect x="142" y="86" width="52" height="34" rx="4" fill="#0f7fa8" opacity=".4"/>
<rect x="204" y="86" width="38" height="34" rx="4" fill="#7fb6c9" opacity=".28"/>
</g>
<circle cx="160" cy="26" r="1.8" fill="#12212b" opacity=".5"/>
<!-- base -->
<path d="M30 140h260l22 34a5 5 0 0 1-4.3 7.6H12.3A5 5 0 0 1 8 174z" fill="url(#baseG)"/>
<path d="M46 148h228l14 21H32z" fill="#eef3f6"/>
<g fill="#c3d0d8">
<rect x="52" y="151" width="16" height="5" rx="1.6"/><rect x="72" y="151" width="16" height="5" rx="1.6"/>
<rect x="92" y="151" width="16" height="5" rx="1.6"/><rect x="112" y="151" width="16" height="5" rx="1.6"/>
<rect x="132" y="151" width="16" height="5" rx="1.6"/><rect x="152" y="151" width="16" height="5" rx="1.6"/>
<rect x="172" y="151" width="16" height="5" rx="1.6"/><rect x="192" y="151" width="16" height="5" rx="1.6"/>
<rect x="212" y="151" width="16" height="5" rx="1.6"/><rect x="232" y="151" width="16" height="5" rx="1.6"/>
<rect x="48" y="159" width="22" height="5" rx="1.6"/><rect x="74" y="159" width="16" height="5" rx="1.6"/>
<rect x="94" y="159" width="16" height="5" rx="1.6"/><rect x="114" y="159" width="16" height="5" rx="1.6"/>
<rect x="134" y="159" width="16" height="5" rx="1.6"/><rect x="154" y="159" width="16" height="5" rx="1.6"/>
<rect x="174" y="159" width="16" height="5" rx="1.6"/><rect x="194" y="159" width="16" height="5" rx="1.6"/>
<rect x="214" y="159" width="16" height="5" rx="1.6"/><rect x="234" y="159" width="24" height="5" rx="1.6"/>
</g>
<rect x="126" y="168" width="70" height="2.6" rx="1.3" fill="#b3c2cb"/>
</svg>
<span class="render-tag">VB14-2023 · A2317</span>
</div>
<div class="hero-info">
<p class="eyebrow">Laptop repair hub</p>
<h1 id="device-title">Volta Book 14 <span class="year">2023</span></h1>
<p class="lede">Released March 2023. 14-inch ultraportable with a bottom-loading chassis, ten Torx T5 screws and a fully modular battery. Everything the FIXWELL community knows about fixing it, in one place.</p>
<div class="hero-chips">
<a class="score-chip" href="#score" data-score-link>
<span class="score-num">7<small>/10</small></span>
<span class="score-body">
<strong>Repairability</strong>
<span>See the teardown breakdown</span>
</span>
</a>
<span class="chip chip-ok">Parts in stock</span>
<span class="chip">Torx T5 · Phillips #00</span>
</div>
<div class="callout" role="note">
<div class="callout-h">
<svg viewBox="0 0 16 16" aria-hidden="true" width="15" height="15"><circle cx="8" cy="8" r="7" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M8 7.2v4M8 4.6v1.2" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>
Identify your model
</div>
<p>Close the lid and read the etched label on the base, near the hinge:</p>
<pre class="mono-block">Volta Book 14
Model <b>A2317</b> EMC 4180
Order VB14-512-SG 14.0" 2023</pre>
<p class="callout-note">If the label reads <code>A2210</code> you have the 2021 Book 14 — the battery connector and fan are different. <a href="#related">Jump to that model</a>.</p>
</div>
</div>
</section>
<!-- STAT STRIP -->
<section class="stats" aria-label="Device statistics">
<div class="stat"><span class="stat-n" data-count="38">0</span><span class="stat-l">Guides published</span></div>
<div class="stat"><span class="stat-n" data-count="42" data-suffix=" min">0</span><span class="stat-l">Avg. repair time</span></div>
<div class="stat"><span class="stat-n" data-count="1264">0</span><span class="stat-l">Community answers</span></div>
<div class="stat"><span class="stat-n" data-count="19" data-suffix=" parts">0</span><span class="stat-l">Parts catalogued</span></div>
</section>
<div class="layout">
<div class="col-main">
<!-- GUIDES -->
<section id="guides" aria-labelledby="guides-h">
<div class="sec-head">
<h2 id="guides-h">Guides by component</h2>
<div class="filters" role="group" aria-label="Filter guides by difficulty">
<span class="filters-l">Difficulty</span>
<button class="fbtn is-on" type="button" data-diff="all" aria-pressed="true">All</button>
<button class="fbtn" type="button" data-diff="easy" aria-pressed="false">Easy</button>
<button class="fbtn" type="button" data-diff="moderate" aria-pressed="false">Moderate</button>
<button class="fbtn" type="button" data-diff="difficult" aria-pressed="false">Difficult</button>
</div>
</div>
<p class="filter-status" role="status" data-filter-status>Showing all 18 guides.</p>
<div class="accordions" data-accordions>
<section class="acc is-open" data-acc>
<h3>
<button type="button" class="acc-btn" aria-expanded="true" aria-controls="p-display">
<span class="acc-ico" aria-hidden="true">
<svg viewBox="0 0 20 20"><rect x="2" y="3.5" width="16" height="11" rx="2" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M7 17.5h6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
</span>
<span class="acc-title">Display</span>
<span class="acc-meta"><span class="acc-count">4 guides</span><svg class="chev" viewBox="0 0 16 16" aria-hidden="true"><path d="m4 6 4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</button>
</h3>
<div class="acc-panel" id="p-display">
<ul class="glist">
<li class="grow" data-difficulty="difficult"><a href="#guides"><span class="g-name">Display assembly replacement</span><span class="g-tags"><span class="badge b-hard">Difficult</span><span class="g-t">65 min</span><span class="g-s">23 steps</span></span></a></li>
<li class="grow" data-difficulty="moderate"><a href="#guides"><span class="g-name">Bezel and webcam cable</span><span class="g-tags"><span class="badge b-mod">Moderate</span><span class="g-t">35 min</span><span class="g-s">14 steps</span></span></a></li>
<li class="grow" data-difficulty="moderate"><a href="#guides"><span class="g-name">Backlight ribbon reseat</span><span class="g-tags"><span class="badge b-mod">Moderate</span><span class="g-t">28 min</span><span class="g-s">11 steps</span></span></a></li>
<li class="grow" data-difficulty="easy"><a href="#guides"><span class="g-name">Hinge screw re-torque</span><span class="g-tags"><span class="badge b-easy">Easy</span><span class="g-t">10 min</span><span class="g-s">6 steps</span></span></a></li>
</ul>
</div>
</section>
<section class="acc is-open" data-acc>
<h3>
<button type="button" class="acc-btn" aria-expanded="true" aria-controls="p-battery">
<span class="acc-ico" aria-hidden="true">
<svg viewBox="0 0 20 20"><rect x="2" y="6" width="13" height="8" rx="2" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M17 8.5v3" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M4.5 8.5h5v3h-5z" fill="currentColor"/></svg>
</span>
<span class="acc-title">Battery</span>
<span class="acc-meta"><span class="acc-count">3 guides</span><svg class="chev" viewBox="0 0 16 16" aria-hidden="true"><path d="m4 6 4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</button>
</h3>
<div class="acc-panel" id="p-battery">
<ul class="glist">
<li class="grow" data-difficulty="easy"><a href="#guides"><span class="g-name">Battery replacement (VB14-B52)</span><span class="g-tags"><span class="badge b-easy">Easy</span><span class="g-t">22 min</span><span class="g-s">9 steps</span></span></a></li>
<li class="grow" data-difficulty="easy"><a href="#guides"><span class="g-name">Battery connector disconnect (do this first)</span><span class="g-tags"><span class="badge b-easy">Easy</span><span class="g-t">6 min</span><span class="g-s">4 steps</span></span></a></li>
<li class="grow" data-difficulty="moderate"><a href="#guides"><span class="g-name">Swollen cell safe removal</span><span class="g-tags"><span class="badge b-mod">Moderate</span><span class="g-t">40 min</span><span class="g-s">12 steps</span></span></a></li>
</ul>
</div>
</section>
<section class="acc" data-acc>
<h3>
<button type="button" class="acc-btn" aria-expanded="false" aria-controls="p-keyboard">
<span class="acc-ico" aria-hidden="true">
<svg viewBox="0 0 20 20"><rect x="1.5" y="5" width="17" height="10" rx="2" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M5 8h.01M8 8h.01M11 8h.01M14 8h.01M6.5 11.5h7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>
</span>
<span class="acc-title">Keyboard & trackpad</span>
<span class="acc-meta"><span class="acc-count">3 guides</span><svg class="chev" viewBox="0 0 16 16" aria-hidden="true"><path d="m4 6 4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</button>
</h3>
<div class="acc-panel" id="p-keyboard">
<ul class="glist">
<li class="grow" data-difficulty="easy"><a href="#guides"><span class="g-name">Single keycap and scissor clip</span><span class="g-tags"><span class="badge b-easy">Easy</span><span class="g-t">8 min</span><span class="g-s">5 steps</span></span></a></li>
<li class="grow" data-difficulty="difficult"><a href="#guides"><span class="g-name">Full keyboard deck replacement</span><span class="g-tags"><span class="badge b-hard">Difficult</span><span class="g-t">95 min</span><span class="g-s">31 steps</span></span></a></li>
<li class="grow" data-difficulty="moderate"><a href="#guides"><span class="g-name">Trackpad calibration and re-shim</span><span class="g-tags"><span class="badge b-mod">Moderate</span><span class="g-t">30 min</span><span class="g-s">13 steps</span></span></a></li>
</ul>
</div>
</section>
<section class="acc" data-acc>
<h3>
<button type="button" class="acc-btn" aria-expanded="false" aria-controls="p-storage">
<span class="acc-ico" aria-hidden="true">
<svg viewBox="0 0 20 20"><rect x="2.5" y="7" width="15" height="6" rx="1.6" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M6 10h.01M9 10h.01" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/><path d="M14 8.5v3" stroke="currentColor" stroke-width="1.6"/></svg>
</span>
<span class="acc-title">Storage</span>
<span class="acc-meta"><span class="acc-count">3 guides</span><svg class="chev" viewBox="0 0 16 16" aria-hidden="true"><path d="m4 6 4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</button>
</h3>
<div class="acc-panel" id="p-storage">
<ul class="glist">
<li class="grow" data-difficulty="easy"><a href="#guides"><span class="g-name">M.2 2280 SSD upgrade</span><span class="g-tags"><span class="badge b-easy">Easy</span><span class="g-t">18 min</span><span class="g-s">8 steps</span></span></a></li>
<li class="grow" data-difficulty="easy"><a href="#guides"><span class="g-name">Thermal pad swap on the drive shield</span><span class="g-tags"><span class="badge b-easy">Easy</span><span class="g-t">12 min</span><span class="g-s">6 steps</span></span></a></li>
<li class="grow" data-difficulty="moderate"><a href="#guides"><span class="g-name">Clone and restore before a swap</span><span class="g-tags"><span class="badge b-mod">Moderate</span><span class="g-t">55 min</span><span class="g-s">10 steps</span></span></a></li>
</ul>
</div>
</section>
<section class="acc" data-acc>
<h3>
<button type="button" class="acc-btn" aria-expanded="false" aria-controls="p-fan">
<span class="acc-ico" aria-hidden="true">
<svg viewBox="0 0 20 20"><circle cx="10" cy="10" r="7.2" fill="none" stroke="currentColor" stroke-width="1.6"/><circle cx="10" cy="10" r="1.8" fill="currentColor"/><path d="M10 3.4c2.4 2 2.4 4.6 0 4.8M16.6 10c-2 2.4-4.6 2.4-4.8 0M10 16.6c-2.4-2-2.4-4.6 0-4.8M3.4 10c2-2.4 4.6-2.4 4.8 0" fill="none" stroke="currentColor" stroke-width="1.4"/></svg>
</span>
<span class="acc-title">Fan & cooling</span>
<span class="acc-meta"><span class="acc-count">3 guides</span><svg class="chev" viewBox="0 0 16 16" aria-hidden="true"><path d="m4 6 4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</button>
</h3>
<div class="acc-panel" id="p-fan">
<ul class="glist">
<li class="grow" data-difficulty="moderate"><a href="#guides"><span class="g-name">Fan module replacement</span><span class="g-tags"><span class="badge b-mod">Moderate</span><span class="g-t">45 min</span><span class="g-s">16 steps</span></span></a></li>
<li class="grow" data-difficulty="easy"><a href="#guides"><span class="g-name">Dust clearing without full disassembly</span><span class="g-tags"><span class="badge b-easy">Easy</span><span class="g-t">15 min</span><span class="g-s">7 steps</span></span></a></li>
<li class="grow" data-difficulty="difficult"><a href="#guides"><span class="g-name">Heat pipe removal and thermal paste</span><span class="g-tags"><span class="badge b-hard">Difficult</span><span class="g-t">80 min</span><span class="g-s">26 steps</span></span></a></li>
</ul>
</div>
</section>
<section class="acc" data-acc>
<h3>
<button type="button" class="acc-btn" aria-expanded="false" aria-controls="p-ports">
<span class="acc-ico" aria-hidden="true">
<svg viewBox="0 0 20 20"><rect x="2" y="7.5" width="9" height="5" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.6"/><rect x="13" y="8.4" width="5" height="3.2" rx="1.2" fill="none" stroke="currentColor" stroke-width="1.6"/></svg>
</span>
<span class="acc-title">Ports & charging</span>
<span class="acc-meta"><span class="acc-count">2 guides</span><svg class="chev" viewBox="0 0 16 16" aria-hidden="true"><path d="m4 6 4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</button>
</h3>
<div class="acc-panel" id="p-ports">
<ul class="glist">
<li class="grow" data-difficulty="difficult"><a href="#guides"><span class="g-name">USB-C port board replacement</span><span class="g-tags"><span class="badge b-hard">Difficult</span><span class="g-t">70 min</span><span class="g-s">21 steps</span></span></a></li>
<li class="grow" data-difficulty="easy"><a href="#guides"><span class="g-name">Charge port debris and pin test</span><span class="g-tags"><span class="badge b-easy">Easy</span><span class="g-t">9 min</span><span class="g-s">5 steps</span></span></a></li>
</ul>
</div>
</section>
</div>
</section>
<!-- PROBLEMS -->
<section id="problems" aria-labelledby="prob-h">
<div class="sec-head"><h2 id="prob-h">Common problems</h2><span class="sec-note">Symptom → the guide that usually fixes it</span></div>
<ul class="problems">
<li><a href="#guides">
<span class="p-sym">Shuts down at 30 % battery</span>
<span class="p-fix">Battery replacement (VB14-B52) <span class="p-arrow" aria-hidden="true">→</span></span>
<span class="p-tally">218 reports</span>
</a></li>
<li><a href="#guides">
<span class="p-sym">Fan roars while idle</span>
<span class="p-fix">Dust clearing without full disassembly <span class="p-arrow" aria-hidden="true">→</span></span>
<span class="p-tally">176 reports</span>
</a></li>
<li><a href="#guides">
<span class="p-sym">Flickering lower third of the screen</span>
<span class="p-fix">Backlight ribbon reseat <span class="p-arrow" aria-hidden="true">→</span></span>
<span class="p-tally">141 reports</span>
</a></li>
<li><a href="#guides">
<span class="p-sym">Charges only on one side</span>
<span class="p-fix">USB-C port board replacement <span class="p-arrow" aria-hidden="true">→</span></span>
<span class="p-tally">98 reports</span>
</a></li>
<li><a href="#guides">
<span class="p-sym">Trackpad click feels dead in the corners</span>
<span class="p-fix">Trackpad calibration and re-shim <span class="p-arrow" aria-hidden="true">→</span></span>
<span class="p-tally">73 reports</span>
</a></li>
</ul>
</section>
<!-- SCORE -->
<section id="score" class="card score-card" aria-labelledby="score-h" tabindex="-1">
<h2 id="score-h">Repairability breakdown</h2>
<p class="sec-note">How the community scored the Book 14 (2023) after teardown.</p>
<ul class="bars">
<li><span class="bar-l">Battery access</span><span class="bar"><i style="--v:90%"></i></span><span class="bar-v mono">9.0</span></li>
<li><span class="bar-l">Fasteners</span><span class="bar"><i style="--v:80%"></i></span><span class="bar-v mono">8.0</span></li>
<li><span class="bar-l">Part modularity</span><span class="bar"><i style="--v:70%"></i></span><span class="bar-v mono">7.0</span></li>
<li><span class="bar-l">Documentation</span><span class="bar"><i style="--v:60%"></i></span><span class="bar-v mono">6.0</span></li>
<li><span class="bar-l">Adhesive use</span><span class="bar"><i style="--v:50%"></i></span><span class="bar-v mono">5.0</span></li>
</ul>
</section>
<!-- RELATED -->
<section id="related" aria-labelledby="rel-h">
<div class="sec-head"><h2 id="rel-h">Related models</h2></div>
<div class="rel-row">
<a class="rel" href="#main"><span class="rel-sv"><svg viewBox="0 0 60 40" aria-hidden="true"><rect x="10" y="5" width="40" height="24" rx="2" fill="#c9d6dd"/><rect x="13" y="8" width="34" height="18" fill="#12212b" opacity=".7"/><path d="M4 31h52l3 5H1z" fill="#aebdc6"/></svg></span><span class="rel-n">Volta Book 14</span><span class="rel-y mono">2021 · A2210</span></a>
<a class="rel" href="#main"><span class="rel-sv"><svg viewBox="0 0 60 40" aria-hidden="true"><rect x="7" y="4" width="46" height="26" rx="2" fill="#c9d6dd"/><rect x="10" y="7" width="40" height="20" fill="#12212b" opacity=".7"/><path d="M2 32h56l2 5H0z" fill="#aebdc6"/></svg></span><span class="rel-n">Volta Book 16</span><span class="rel-y mono">2023 · A2340</span></a>
<a class="rel" href="#main"><span class="rel-sv"><svg viewBox="0 0 60 40" aria-hidden="true"><rect x="14" y="6" width="32" height="22" rx="2" fill="#c9d6dd"/><rect x="17" y="9" width="26" height="16" fill="#12212b" opacity=".7"/><path d="M7 30h46l3 6H4z" fill="#aebdc6"/></svg></span><span class="rel-n">Volta Air 13</span><span class="rel-y mono">2022 · A2188</span></a>
<a class="rel" href="#main"><span class="rel-sv"><svg viewBox="0 0 60 40" aria-hidden="true"><rect x="12" y="4" width="36" height="30" rx="3" fill="#c9d6dd"/><rect x="15" y="7" width="30" height="24" fill="#12212b" opacity=".7"/></svg></span><span class="rel-n">Volta Slate 11</span><span class="rel-y mono">2023 · A2402</span></a>
</div>
</section>
</div>
<!-- RAIL -->
<aside class="rail" id="parts" aria-labelledby="parts-h">
<div class="card rail-card">
<h2 id="parts-h" class="rail-h">Parts for this model</h2>
<ul class="parts">
<li class="part">
<span class="part-sv" aria-hidden="true"><svg viewBox="0 0 40 30"><rect x="4" y="6" width="32" height="18" rx="3" fill="#dbe6ec" stroke="#b3c4cd"/><rect x="8" y="10" width="14" height="10" rx="1.5" fill="#3fa34d" opacity=".55"/></svg></span>
<span class="part-b">
<span class="part-n">Battery — VB14-B52</span>
<span class="part-m mono">FW-2317-BAT · 58 Wh</span>
</span>
<span class="part-r"><span class="price mono">$74</span><button class="add" type="button" data-add="Battery VB14-B52" data-price="74">Add</button></span>
</li>
<li class="part">
<span class="part-sv" aria-hidden="true"><svg viewBox="0 0 40 30"><circle cx="20" cy="15" r="10" fill="#dbe6ec" stroke="#b3c4cd"/><circle cx="20" cy="15" r="3" fill="#0f7fa8" opacity=".6"/></svg></span>
<span class="part-b"><span class="part-n">Fan module, left</span><span class="part-m mono">FW-2317-FAN-L</span></span>
<span class="part-r"><span class="price mono">$29</span><button class="add" type="button" data-add="Fan module, left" data-price="29">Add</button></span>
</li>
<li class="part">
<span class="part-sv" aria-hidden="true"><svg viewBox="0 0 40 30"><rect x="5" y="11" width="30" height="8" rx="2" fill="#dbe6ec" stroke="#b3c4cd"/><rect x="9" y="14" width="4" height="2" fill="#0f7fa8"/></svg></span>
<span class="part-b"><span class="part-n">USB-C port board</span><span class="part-m mono">FW-2317-IO-C</span></span>
<span class="part-r"><span class="price mono">$41</span><button class="add" type="button" data-add="USB-C port board" data-price="41">Add</button></span>
</li>
<li class="part">
<span class="part-sv" aria-hidden="true"><svg viewBox="0 0 40 30"><rect x="4" y="8" width="32" height="14" rx="2" fill="#dbe6ec" stroke="#b3c4cd"/><g fill="#b3c4cd"><rect x="7" y="11" width="4" height="3"/><rect x="13" y="11" width="4" height="3"/><rect x="19" y="11" width="4" height="3"/><rect x="25" y="11" width="4" height="3"/></g></svg></span>
<span class="part-b"><span class="part-n">Keyboard deck, US layout</span><span class="part-m mono">FW-2317-KBD-US</span></span>
<span class="part-r"><span class="price mono">$118</span><button class="add" type="button" data-add="Keyboard deck, US layout" data-price="118">Add</button></span>
</li>
</ul>
<h3 class="rail-sub">Tools you'll need</h3>
<ul class="tools">
<li class="tool">
<span class="tool-n">Torx T5 driver</span><span class="price mono">$6</span>
<button class="add add-sm" type="button" data-add="Torx T5 driver" data-price="6">Add</button>
</li>
<li class="tool">
<span class="tool-n">Nylon spudger pair</span><span class="price mono">$4</span>
<button class="add add-sm" type="button" data-add="Nylon spudger pair" data-price="4">Add</button>
</li>
<li class="tool">
<span class="tool-n">Anti-static wrist strap</span><span class="price mono">$9</span>
<button class="add add-sm" type="button" data-add="Anti-static wrist strap" data-price="9">Add</button>
</li>
<li class="tool">
<span class="tool-n">Suction handle, 45 mm</span><span class="price mono">$11</span>
<button class="add add-sm" type="button" data-add="Suction handle 45 mm" data-price="11">Add</button>
</li>
</ul>
<div class="rail-total">
<span>Cart subtotal</span>
<strong class="mono" data-cart-total>$0</strong>
</div>
<button class="btn btn-primary btn-block" type="button" data-cart-open>Review kit</button>
</div>
</aside>
</div>
</div>
<footer class="foot">
<div class="wrap foot-in">
<span class="mono">FIXWELL · community repair manuals</span>
<span>Fictional brand and fictional device — illustrative UI only.</span>
</div>
</footer>
</main>
<div class="toaster" id="toaster" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Device Hub Page
This is the landing page a repair community builds for one machine — here the fictional Volta Book 14 (2023) on the fictional FIXWELL wiki. The hero pairs a hand-drawn SVG render of the laptop with the facts you need before you pick up a driver: model year, a repairability score chip that jumps to its breakdown, and a monospace callout showing exactly what the etched base label looks like so you can confirm you are on the right page. A four-cell stat strip counts up guides, average repair time, community answers and catalogued parts as it scrolls into view.
The body is the guide index. Guides are grouped by component — Display, Battery, Keyboard, Storage, Fan, Ports — inside accordion sections; each row carries a difficulty badge, an estimated time and a step count. The difficulty filter does not delete rows, it dims the non-matching ones and auto-opens any collapsed section that still has matches, so you keep a sense of how much of the catalogue a given skill level unlocks. Below it, a symptom-to-guide list maps real-sounding complaints (“shuts down at 30 % battery”) to the fix, with a community report tally.
The right rail is sticky: parts for this exact model with part numbers and prices, then the tools the guides call for, a running subtotal, and add-to-cart buttons that flip to a green “Added” state and fire a toast. A related-models row closes the page for people who landed on the wrong revision.
How this look is built
The information architecture takes its cue from iFixit-style device pages, but everything modern
here is CSS: the accordions animate with grid-template-rows: 0fr → 1fr instead of the old
fixed-max-height hack, so panels expand to their true content height with no magic numbers and no
JS measurement; the two-column shell is CSS grid with a position: sticky rail that collapses to a
single column under 980px; the blueprint-ish hero backdrop is two repeating linear gradients rather
than an image; and the stat counters plus the score bars are driven by a single IntersectionObserver
that unobserves each element after it fires, so the reveal costs nothing on scroll. Everything is
drawn in inline SVG, so the page has zero network requests beyond the font stylesheet.
Illustrative UI only — always follow real safety guidance when working with tools, electronics, or repairs.