/* The whole page is the workbench, so the .wb scope sits on <body>. Every
   --wb-* token is declared on .wb in tokens.css and an undefined custom
   property invalidates the declaration rather than falling back. */
*,*::before,*::after{box-sizing:border-box}
html,body{margin:0;padding:0}
body.wb{background:var(--wb-bg);color:var(--wb-ink);border:0;border-radius:0;
  padding:0;display:block;gap:0;min-height:100vh;
  font:var(--t-base)/var(--lh-normal) var(--font);-webkit-font-smoothing:antialiased}
h1,h2,h3,p{margin:0}
button{font-family:inherit}
:focus-visible{outline:2px solid var(--wb-action);outline-offset:2px;
  border-radius:var(--r-sm)}
.sr{position:absolute;left:-9999px}
.shell{max-width:1640px;margin:0 auto;padding:0 var(--s-7) var(--s-9)}
.mono{font-family:var(--font-mono)}

.illus{background:var(--wb-select-tint);border-bottom:1px solid var(--wb-select);
  color:var(--wb-select);padding:var(--s-4) var(--s-7);font-size:var(--t-xs);
  font-family:var(--font-mono)}

/* ---- Headline row ------------------------------------------------------- */
.top{display:flex;align-items:center;gap:var(--s-6);flex-wrap:wrap;
  padding:var(--s-7) 0 var(--s-6)}
.mark{width:26px;height:26px;border-radius:6px;background:var(--wb-action);
  flex:none;display:grid;place-items:center}
.mark svg{width:74%;height:74%}
h1{font-size:var(--t-2xl);font-weight:750;letter-spacing:-.025em;
  line-height:1.1}
.grow{flex:1}
.link{display:flex;align-items:center;gap:var(--s-3);background:var(--wb-surface);
  border:1px solid var(--wb-line);border-radius:var(--r-pill);
  padding:var(--s-3) var(--s-4) var(--s-3) var(--s-6);white-space:nowrap;
  min-width:0;max-width:100%}
.link code{font:var(--t-xs)/1.6 var(--font-mono);color:var(--wb-muted)}
.ico{width:30px;height:30px;border-radius:50%;border:0;background:transparent;
  color:var(--wb-ink-soft);display:grid;place-items:center;cursor:pointer}
.ico:hover{background:var(--wb-surface-2);color:var(--wb-ink)}
.ico svg{width:15px;height:15px}
.seg{display:inline-flex;background:var(--wb-surface);border-radius:var(--r-md);
  padding:3px;gap:2px;border:1px solid var(--wb-line)}
.seg button{all:unset;cursor:pointer;padding:var(--s-3) var(--s-5);
  border-radius:var(--r-sm);font:700 var(--t-xs)/1.6 var(--font-mono);
  color:var(--wb-muted)}
.seg button[aria-pressed="true"]{background:var(--wb-surface-2);color:var(--wb-ink)}
.seg button:focus-visible{outline:2px solid var(--wb-action);outline-offset:1px}

/* ---- Bands -------------------------------------------------------------- */
.band{display:flex;align-items:center;gap:var(--s-5);flex-wrap:wrap;
  padding:var(--s-5) 0;border-top:1px solid var(--wb-line)}
.band h2{font:700 var(--t-xs)/1.6 var(--font-mono);letter-spacing:.1em;
  text-transform:uppercase;color:var(--wb-muted);width:96px;flex:none}
.chips{display:flex;gap:var(--s-3);flex-wrap:wrap}
.size{background:var(--wb-surface);border:1px solid var(--wb-line);
  border-radius:var(--r-md);color:var(--wb-ink-soft);cursor:pointer;
  padding:var(--s-4) var(--s-6);min-height:var(--control-h-sm);
  font:700 var(--t-base)/1.2 var(--font-mono);white-space:nowrap}
.size:hover{border-color:var(--wb-border);color:var(--wb-ink)}
.size small{display:block;font:700 var(--t-xs)/1.4 var(--font-mono);
  color:var(--wb-block);margin-top:1px}
.size[aria-pressed="true"]{border-color:var(--wb-select);border-width:2px;
  color:var(--wb-ink);padding:calc(var(--s-4) - 1px) calc(var(--s-6) - 1px)}
.ftog{display:inline-flex;align-items:center;gap:var(--s-4);
  background:var(--wb-surface);border:1px solid var(--wb-line);
  border-radius:var(--r-pill);padding:var(--s-3) var(--s-6) var(--s-3) var(--s-4);
  cursor:pointer;color:var(--wb-ink-soft);font-size:var(--t-base);
  min-height:var(--control-h-sm);white-space:nowrap}
.ftog input{position:absolute;opacity:0;width:0;height:0}
.ftog .sw{width:30px;height:17px;border-radius:var(--r-pill);flex:none;
  background:var(--wb-surface-2);border:1px solid var(--wb-border);position:relative}
.ftog .sw::after{content:"";position:absolute;top:2px;left:2px;width:11px;
  height:11px;border-radius:50%;background:var(--wb-muted);transition:transform .15s}
.ftog input:checked + .sw{background:var(--wb-select-tint);border-color:var(--wb-select)}
.ftog input:checked + .sw::after{transform:translateX(13px);background:var(--wb-select)}
.ftog.on{border-color:var(--wb-select);color:var(--wb-ink)}
.ftog input:focus-visible + .sw{outline:2px solid var(--wb-action);outline-offset:2px}
.ftog em{font-style:normal;font:var(--t-xs)/1.6 var(--font-mono);color:var(--wb-muted)}

/* ---- Layout ------------------------------------------------------------- */
.cols{display:grid;grid-template-columns:2.2fr 1fr;gap:var(--s-6);
  align-items:start;margin-top:var(--s-6)}
/* Bug found and fixed in this cluster's own copy of D2's CSS: a grid
   item's default min-width is auto, not 0, so a fr-track column could not
   shrink below its content's min-content width, and that min-content
   width was wider than the viewport under roughly 500px. The visible
   symptom was a real horizontal scrollbar site-wide, the exact thing D2
   Section 12's own verification table checks for at 390px. Flagged for
   D2/D3 maintainers to fix upstream too. */
.cols > .card{min-width:0}
.card{background:var(--wb-surface);border:1px solid var(--wb-line);
  border-radius:var(--r-lg);padding:var(--s-6)}
/* Dylan's request: more of the card should be worktop, less of it
   margin. Tighter than the general .card padding (var(--s-6)), and the
   stage itself gains the room this frees up via a much taller vh cap
   below - the 72vh it had was the actual bottleneck on an ordinary
   desktop window, well under what the 2.2fr column had available. */
.cols > .card:first-child{padding:var(--s-4)}

/* ---- The stage ---------------------------------------------------------- */
/* The worktop stays large (Dylan's call): full column width, tall. The
   drone drawing inside it does NOT scale with it: builder.js sizes the
   art and tiles to the approved D2 board (a square capped at 72vh) and
   centres it here (audit item 4, 2026-09-20). */
.stage{position:relative;width:100%;height:min(92vh,820px);
  margin:0 auto;border-radius:var(--r-md);background-color:var(--wb-well);
  border:1px solid var(--wb-line);overflow:hidden;
  /* Bug found and fixed in this cluster's own copy of D2's CSS (not
     design/d2-builder.html itself): under the sub-760px rule below, tile
     captions move to left:-9999px to stay in the accessibility tree while
     leaving the screen. With no clip on .stage, that offscreen position
     still counted toward the page's scrollable width, producing a real
     horizontal scrollbar at every width - exactly what D2 Section 12's
     verification table checks for and, on this evidence, never actually
     caught. Flagged for D2/D3 maintainers to fix upstream too. */
  background-image:linear-gradient(var(--wb-grid) 1px,transparent 1px),
    linear-gradient(90deg,var(--wb-grid) 1px,transparent 1px);
  background-size:30px 30px}
.stage svg.art{position:absolute;inset:0;width:100%;height:100%}

/* Which frame is selected, visible inside the drone preview itself
   (Dylan's request), not only in the "Frame size" chip band above -
   a real tile there was tried first but the stage has no vertical room
   at the top (already occupied by camera/GPS tiles) without risking a
   clip, so this is a corner badge instead, positioned by plain CSS
   rather than the tile-physics system every other tile uses. Click
   scrolls to and briefly highlights the chip band, since the actual
   frame-switch control lives there, not on this badge itself. */
.frame-badge{position:absolute;top:var(--s-4);left:var(--s-4);z-index:4;
  max-width:calc(100% - var(--s-8));background:var(--wb-surface-2);
  border:1px solid var(--wb-border);border-radius:var(--r-pill);
  padding:var(--s-3) var(--s-5);font:700 var(--t-xs)/1.4 var(--font);
  color:var(--wb-ink);cursor:pointer;text-align:left;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  box-shadow:0 2px 10px rgba(0,0,0,.4)}
.frame-badge:hover,.frame-badge:focus-visible{border-color:var(--wb-select)}

/* Square part tiles. Large, image first, on the frame. */
/* Tile size is a share of the stage, not a fixed pixel count, so the balance
   between the tiles and the drawing holds at every window size. drawStage()
   sets --tile on the stage and every tile inherits it. */
.tile{position:absolute;transform:translate(-50%,-50%);z-index:3;
  width:var(--tile,56px);border:0;background:none;padding:0;cursor:pointer;
  color:inherit}
.tile .box{position:relative;width:var(--tile,56px);height:var(--tile,56px);
  border-radius:var(--r-md);
  background:var(--wb-surface);border:2px solid var(--wb-border);
  display:grid;place-items:center;overflow:hidden;
  box-shadow:0 3px 14px rgba(0,0,0,.55)}
.tile .box svg{width:calc(var(--tile,56px) * .54);
  height:calc(var(--tile,56px) * .54);color:var(--wb-muted)}
/* The caption is never clipped by the tile it sits under: it sizes to its
   own text and centres on the tile. */
.tile .cap{display:inline-block;width:max-content;position:relative;left:50%;
  transform:translateX(-50%);font:700 var(--t-2xs)/1.3 var(--font-mono);
  letter-spacing:.04em;color:var(--wb-ink-soft);margin-top:var(--s-2);
  text-align:center;white-space:nowrap}
.tile .pip{position:absolute;top:4px;right:4px;width:8px;height:8px;
  border-radius:50%;background:var(--wb-muted)}
.tile:hover .box,.tile.d-hover .box{border-color:var(--wb-ink-soft)}
.tile:focus-visible .box{outline:2px solid var(--wb-action);outline-offset:2px}
.tile[data-s="filled"] .box{border-color:var(--wb-info)}
.tile[data-s="filled"] .box svg{color:var(--wb-ink-soft)}
.tile[data-s="filled"] .pip{background:var(--wb-info)}
/* Decision #190 (cluster 3.6, 2026-09-15): this was --wb-block (red, L1)
   until this decision, colliding with a real Does-not-fit compatibility
   verdict now rendered on the same screen for the first time - harmless
   while the compatibility engine didn't exist, live once it does. An
   empty required slot is a completeness signal, not a severity verdict
   (build plan Section 4's own resolved note), closer in kind to "not yet
   known" than to a real fail, so this reuses --wb-unknown rather than
   inventing a third amber-adjacent token; amber itself stays --wb-select's
   alone (decision #27). */
.tile[data-s="blocked"] .box{border-color:var(--wb-unknown);border-style:dashed}
.tile[data-s="blocked"] .pip{background:var(--wb-unknown)}
.tile[data-s="open"] .box{border-color:var(--wb-border);border-style:dashed}
.tile[data-s="unavailable"] .box{border-color:var(--wb-unknown);border-style:dotted}
.tile[data-s="unavailable"] .pip{background:transparent;
  border:1.5px dotted var(--wb-unknown)}
.tile[aria-expanded="true"] .box{border-color:var(--wb-select)}
.tile[aria-expanded="true"] .cap{color:var(--wb-select)}
.tile .plus{position:absolute;bottom:3px;left:0;right:0;text-align:center;
  font:700 13px/1 var(--font-mono);color:var(--wb-action)}

.legend{display:flex;gap:var(--s-6);flex-wrap:wrap;justify-content:center;
  margin-top:var(--s-5);font:700 var(--t-xs)/1.6 var(--font-mono);
  color:var(--wb-muted);letter-spacing:.04em}
.legend span{display:flex;align-items:center;gap:var(--s-3)}
.legend i{width:9px;height:9px;border-radius:2px;display:inline-block}

/* Accessories, beside the airframe, same tile language. */
.beside{display:flex;align-items:center;gap:var(--s-5);flex-wrap:wrap;
  margin-top:var(--s-6);padding-top:var(--s-6);border-top:1px solid var(--wb-line)}
.beside h3{font:700 var(--t-xs)/1.6 var(--font-mono);letter-spacing:.1em;
  text-transform:uppercase;color:var(--wb-muted);width:88px;flex:none}
.btiles{display:flex;gap:var(--s-5);flex-wrap:wrap}
/* Bug found and fixed: a flat tile resets its own position/transform, but
   .tile .cap's centering trick (left:50%, translateX(-50%)) and .tile's
   own fixed width both cascaded in unreset. That trick only works for a
   short category word (MOTOR, SERVO) on a tile positioned absolutely on
   the stage, where a little overflow either side costs nothing. A real
   product name ("DEMO 868MHz Handset") is much wider than the ~70px tile
   it was centering on, so each flat tile's caption spilled sideways into
   its neighbour's. Previously this fix only applied under the sub-760px
   rule below; the bug was present at every width. */
.tile.flat{position:static;transform:none;width:auto;max-width:170px}
.tile.flat .cap{position:static;left:auto;transform:none;
  white-space:normal;width:auto}

/* ---- Popover ------------------------------------------------------------ */
.pop{position:absolute;z-index:20;width:320px;background:var(--wb-surface-2);
  border:1px solid var(--wb-border);border-radius:var(--r-md);
  box-shadow:0 18px 44px rgba(0,0,0,.65);padding:var(--s-5);
  max-height:84%;overflow:auto}
.pop[hidden]{display:none}
.poph{display:flex;align-items:center;gap:var(--s-4);padding:0 var(--s-3) var(--s-4);
  border-bottom:1px solid var(--wb-line);margin-bottom:var(--s-3)}
.poph b{font:700 var(--t-xs)/1.6 var(--font-mono);letter-spacing:.08em;
  text-transform:uppercase;color:var(--wb-muted)}
.opt{display:flex;gap:var(--s-5);align-items:center;width:100%;text-align:left;
  padding:var(--s-4);border-radius:var(--r-sm);border:1.5px solid transparent;
  background:none;color:inherit;cursor:pointer}
.opt:hover,.opt.d-hover{background:var(--wb-surface)}
.opt:focus-visible,.opt.d-focus{outline:2px solid var(--wb-action);outline-offset:-1px}
.opt[aria-pressed="true"]{border-color:var(--wb-select);background:var(--wb-select-tint)}
.shot{width:48px;height:48px;flex:none;border-radius:var(--r-sm);
  background:var(--wb-well);border:1px solid var(--wb-line);display:grid;
  place-items:center;color:var(--wb-border)}
.shot svg{width:28px;height:28px}
.ob{flex:1;min-width:0}
.ob .n{display:block;font-size:var(--t-base);color:var(--wb-ink);line-height:1.25}
.flag{display:block;font:700 var(--t-xs)/1.6 var(--font-mono);margin-top:1px}
.f-rec{color:var(--wb-pass)} .f-alt{color:var(--wb-muted)} .f-bad{color:var(--wb-block)}
.om{text-align:right;flex:none}
.om .p{display:block;font:700 var(--t-base)/1.3 var(--font-mono);color:var(--wb-ink);
  font-variant-numeric:tabular-nums}
.om .w{display:block;font:var(--t-xs)/1.6 var(--font-mono);color:var(--wb-muted)}
.popf{margin-top:var(--s-3);padding-top:var(--s-4);border-top:1px solid var(--wb-line);
  display:flex;gap:var(--s-4);align-items:center}
.txt{background:none;border:0;cursor:pointer;padding:var(--s-3) var(--s-4);
  font:700 var(--t-xs)/1.6 var(--font-mono);color:var(--wb-block);
  border-radius:var(--r-sm)}
.txt:hover{background:var(--wb-block-tint)}
.txt.q{color:var(--wb-muted)} .txt.q:hover{background:var(--wb-surface)}
.pmsg{padding:var(--s-4);font-size:var(--t-sm);color:var(--wb-ink-soft)}

/* ---- Compatibility panel (cluster 3.6, decision #132/#190) -------------- */
.compat-group{margin-top:var(--s-6)}
.compat-group:first-child{margin-top:0}
.compat-group h3{font:700 var(--t-xs)/1.5 var(--font-mono);color:var(--wb-muted);
  text-transform:uppercase;letter-spacing:.04em;margin-bottom:var(--s-3);
  padding:0 var(--s-4)}
.compat-row{display:flex;gap:var(--s-4);align-items:flex-start;
  padding:var(--s-3) var(--s-4)}
.compat-reason{font:var(--t-sm)/1.5 var(--font);color:var(--wb-ink-soft)}
.sevbadge{display:inline-flex;align-items:center;flex:none;white-space:nowrap;
  padding:3px var(--s-4);border-radius:var(--r-sm);
  font:700 var(--t-2xs)/1.4 var(--font-mono);text-transform:uppercase;
  letter-spacing:.04em}
.sevbadge[data-sev="fits"]{background:var(--wb-pass-tint);color:var(--wb-pass)}
.sevbadge[data-sev="not_ideal"]{background:var(--wb-notideal-tint);color:var(--wb-notideal)}
.sevbadge[data-sev="does_not_fit"]{background:var(--wb-block-tint);color:var(--wb-block)}
.sevbadge[data-sev="unknown"]{background:var(--wb-unknown-tint);color:var(--wb-unknown)}

/* Decision #173: mechanical-failure/electrical-overload verdicts render
   more prominently near the checkout CTA, never gating it (decision #28a -
   #cta keeps its own disabled state entirely independently of this). */
.safety-banner{border:2px solid var(--wb-block);background:var(--wb-block-tint);
  border-radius:var(--r-md);padding:var(--s-4) var(--s-5);margin-bottom:var(--s-4)}
.safety-banner strong{display:block;color:var(--wb-block);margin-bottom:var(--s-3);
  font:700 var(--t-xs)/1.4 var(--font-mono);text-transform:uppercase;
  letter-spacing:.04em}
.safety-banner ul{margin:0;padding-left:var(--s-6)}
.safety-banner li{font:var(--t-sm)/1.5 var(--font);color:var(--wb-ink-soft);
  margin-bottom:var(--s-2)}
.safety-banner li:last-child{margin-bottom:0}
.skel{background:linear-gradient(90deg,var(--wb-surface) 25%,var(--wb-line) 37%,
  var(--wb-surface) 63%);background-size:400% 100%;animation:sk 1.4s ease infinite;
  border-radius:var(--r-sm);height:14px;margin:var(--s-4)}
@keyframes sk{0%{background-position:100% 50%}100%{background-position:0 50%}}
@media (prefers-reduced-motion:reduce){.skel{animation:none}}

/* ---- Stats -------------------------------------------------------------- */
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--s-5);
  margin-top:var(--s-6)}
.stat{background:var(--wb-surface-2);border:1px solid var(--wb-line);
  border-radius:var(--r-md);padding:var(--s-5) var(--s-6)}
.stat .k{font:700 var(--t-xs)/1.6 var(--font-mono);letter-spacing:.06em;
  text-transform:uppercase;color:var(--wb-muted)}
.stat .v{font:700 1.5rem/1.2 var(--font-mono);color:var(--wb-ink);
  font-variant-numeric:tabular-nums;margin-top:2px}
.stat .s{font:var(--t-xs)/1.5 var(--font-mono);color:var(--wb-muted);margin-top:2px}
.caabar{margin-top:var(--s-5);padding:var(--s-5) var(--s-6);
  border-radius:var(--r-md);background:var(--wb-info-tint);
  border:1px solid var(--wb-info);font-size:var(--t-sm);color:var(--wb-ink-soft);
  display:flex;gap:var(--s-5);align-items:center}
.caabar[data-u="1"]{background:var(--wb-unknown-tint);border-color:var(--wb-unknown)}
.caabar svg{width:18px;height:18px;flex:none;color:var(--wb-info)}
.caabar[data-u="1"] svg{color:var(--wb-unknown)}
.caabar b{color:var(--wb-ink)}
.unk{display:inline-flex;align-items:center;padding:0 var(--s-3);
  border-radius:var(--r-sm);font:700 var(--t-xs)/1.8 var(--font);
  background:var(--wb-unknown-tint);color:var(--wb-unknown);
  border:1px dotted var(--wb-unknown);cursor:help}

/* ---- Right column ------------------------------------------------------- */
.prog .k{font:700 var(--t-xs)/1.6 var(--font-mono);letter-spacing:.08em;
  text-transform:uppercase;color:var(--wb-muted)}
.prog .v{font:700 1.35rem/1.2 var(--font-mono);color:var(--wb-ink);margin-top:2px}
.bar{height:6px;border-radius:var(--r-pill);background:var(--wb-surface-2);
  margin:var(--s-4) 0;overflow:hidden}
.bar i{display:block;height:100%;background:var(--wb-info)}
.counts{display:flex;gap:var(--s-5);flex-wrap:wrap;
  font:700 var(--t-xs)/1.6 var(--font-mono);color:var(--wb-muted);letter-spacing:.04em}
.counts span{display:flex;align-items:center;gap:var(--s-3)}
.counts i{width:8px;height:8px;border-radius:50%}
.row{display:flex;align-items:center;gap:var(--s-4);width:100%;text-align:left;
  background:none;border:0;color:inherit;cursor:pointer;padding:var(--s-3) var(--s-4);
  border-radius:var(--r-sm);min-height:40px}
.row:hover,.row.d-hover{background:var(--wb-surface-2)}
.row:focus-visible{outline:2px solid var(--wb-action);outline-offset:-2px}
.row[aria-expanded="true"]{background:var(--wb-surface-2);
  box-shadow:inset 2px 0 0 var(--wb-select)}
.row .t{flex:1;min-width:0}
.row .t .k{font:var(--t-xs)/1.5 var(--font-mono);color:var(--wb-muted);display:block}
.row .t .v{font-size:var(--t-base);color:var(--wb-ink-soft);display:block;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.row .t .v.none{color:var(--wb-muted)}
.row .a{font:700 var(--t-xs)/1.5 var(--font-mono);color:var(--wb-muted);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.dot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--wb-muted)}
.row[data-s="filled"] .dot{background:var(--wb-info)}
.row[data-s="blocked"] .dot{background:var(--wb-unknown)}
.row[data-s="unavailable"] .dot{background:transparent;
  border:1.5px dotted var(--wb-unknown)}

/* ---- Your build, full width -------------------------------------------- */
.build{margin-top:var(--s-6);display:grid;grid-template-columns:1.6fr 1fr;
  gap:var(--s-8)}
.build h2{font:700 var(--t-base)/1.3 var(--font-mono);letter-spacing:.08em;
  text-transform:uppercase;color:var(--wb-ink);margin-bottom:var(--s-4)}
.items{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));
  gap:0 var(--s-8)}
.item{display:flex;justify-content:space-between;gap:var(--s-5);
  padding:var(--s-5) 0;border-bottom:1px solid var(--wb-line);font-size:var(--t-base);
  color:var(--wb-ink-soft)}
.item .p{font:700 var(--t-base)/1.3 var(--font-mono);color:var(--wb-ink);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.item.none,.item.none .p{color:var(--wb-muted);font-weight:400}
/* Bug found and fixed: .item .p is nowrap by design for short price
   figures. The "Beside it" accessories row reuses .item for a full
   sentence (why goggles aren't shown yet), and nowrap on that sentence
   was the row's own min-content width, forcing everything up the
   ancestor chain wider than the viewport - the same class of bug as the
   two above, just via text content instead of a bare form control. */
.item .p.reason{white-space:normal;text-align:right;font:var(--t-xs)/1.4 var(--font);
  font-weight:400;max-width:60%}
.line{display:flex;justify-content:space-between;gap:var(--s-5);align-items:baseline;
  padding:var(--s-5) 0;font-size:var(--t-base);color:var(--wb-ink-soft)}
.line .p{font:700 var(--t-base)/1.3 var(--font-mono);color:var(--wb-ink);
  font-variant-numeric:tabular-nums}
.svc{display:flex;gap:var(--s-5);align-items:center;padding:var(--s-4) 0;
  cursor:pointer;border-top:1px solid var(--wb-line)}
.svc input{position:absolute;opacity:0;width:0;height:0}
.svc .sw{width:30px;height:17px;border-radius:var(--r-pill);flex:none;
  background:var(--wb-surface-2);border:1px solid var(--wb-border);position:relative}
.svc .sw::after{content:"";position:absolute;top:2px;left:2px;width:11px;height:11px;
  border-radius:50%;background:var(--wb-muted);transition:transform .15s}
.svc input:checked + .sw{background:var(--wb-action)}
.svc input:checked + .sw::after{transform:translateX(13px);background:var(--wb-action-ink)}
.svc input:focus-visible + .sw{outline:2px solid var(--wb-action);outline-offset:2px}
.svc .t{flex:1;min-width:0;font-size:var(--t-base);color:var(--wb-ink)}
.svc .t small{display:block;font:var(--t-xs)/1.5 var(--font-mono);color:var(--wb-muted)}
.svc .p{font:700 var(--t-base)/1.3 var(--font-mono);color:var(--wb-ink);
  font-variant-numeric:tabular-nums}
.tot{display:flex;justify-content:space-between;align-items:baseline;gap:var(--s-5);
  border-top:1px solid var(--wb-border);padding-top:var(--s-5);margin-top:var(--s-3)}
.tot .l{font-size:var(--t-lg);font-weight:700;color:var(--wb-ink)}
.tot .p{font:700 1.8rem/1.1 var(--font-mono);color:var(--wb-ink);
  font-variant-numeric:tabular-nums}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--s-3);
  height:var(--control-h);padding:0 var(--s-8);width:100%;border-radius:var(--r-md);
  border:1px solid transparent;cursor:pointer;font:700 var(--t-md)/1 var(--font)}
.btn-primary{background:var(--wb-action);color:var(--wb-action-ink)}
.btn-primary:hover{background:var(--brand-hover)}
.btn-secondary{background:transparent;color:var(--wb-ink);border-color:var(--wb-border);
  height:var(--control-h-sm);font-size:var(--t-base)}
.btn-secondary:hover{background:var(--wb-surface-2)}
.btn[disabled]{opacity:.45;cursor:not-allowed}
/* The primary action is the biggest target on the page. Save and start again
   are text links, not buttons competing with it. */
#cta{height:64px;font-size:var(--t-lg)}
.tlinks{display:flex;gap:var(--s-8);justify-content:center;margin-top:var(--s-6)}
.tlink{background:none;border:0;padding:var(--s-3) var(--s-4);cursor:pointer;
  color:var(--wb-action);font:700 var(--t-base)/1.4 var(--font);
  text-decoration:underline;text-underline-offset:3px;border-radius:var(--r-sm)}
.tlink:hover{color:var(--wb-ink)}
/* Decision #190: the readiness summary ("N parts still to choose") is a
   completeness signal, not a severity verdict - same reasoning as the
   .tile[data-s="blocked"] fix above, moved off --wb-block for the same
   reason. */
.why{font:700 var(--t-xs)/1.5 var(--font);color:var(--wb-unknown);margin-top:var(--s-4);
  text-align:center}

.ref{margin-top:var(--s-8);border-top:1px solid var(--wb-line);padding-top:var(--s-5)}
.ref summary{cursor:pointer;font:700 var(--t-2xs)/1.8 var(--font-mono);
  letter-spacing:.08em;text-transform:uppercase;color:var(--wb-muted);padding:var(--s-4)}
.refg{display:grid;gap:var(--s-6);margin-top:var(--s-5);
  grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}
.refb{border:1px dashed var(--wb-border);border-radius:var(--r-md);padding:var(--s-6)}
.refb .l{display:block;font:700 var(--t-2xs)/1.6 var(--font-mono);letter-spacing:.06em;
  text-transform:uppercase;color:var(--wb-muted);margin-bottom:var(--s-5)}

.mob{display:none}

@media (max-width:1180px){
  .cols{grid-template-columns:1fr}
  .build{grid-template-columns:1fr}
}
@media (max-width:760px){
  .shell{padding:0 var(--s-5) var(--s-9)}
  .card{padding:var(--s-5)}
  .band{align-items:flex-start;flex-direction:column;gap:var(--s-4)}
  .band h2,.beside h3{width:auto}
  .chips{width:100%;min-width:0;overflow-x:auto;flex-wrap:nowrap;padding-bottom:var(--s-3)}
  /* Bug found and fixed in this cluster's own copy of D2's CSS: the same
     min-width:auto issue as .cols > .card above. .chips is a flex item of
     .band, and once flex-wrap:nowrap forces one row, its default
     min-width equals the summed width of every chip, which is wider than
     the viewport on a real feature/toggle row (this builder's four real
     toggles run wider than D2's illustrative three). Without min-width:0
     .chips could not shrink to use its own overflow-x:auto scrolling and
     forced .band, .shell and the page itself wider instead. */
  .link{flex:1 1 100%}
  .link code{overflow:hidden;text-overflow:ellipsis;min-width:0}
  .tile .cap{position:absolute;left:-9999px}
  /* .tile.flat's own rule above (higher specificity: three classes vs
     two) already keeps a flat tile's caption visible here - a "Beside it"
     tile has no menu row standing in for it the way a stage tile's does,
     so its label must not disappear under 760px the way stage captions
     deliberately do. No override needed at this width specifically. */
  .stats{grid-template-columns:repeat(2,1fr)}
  .pop{width:min(300px,calc(100vw - 56px))}
  h1{font-size:var(--t-xl)}
  body{padding-bottom:72px}
  .mob{display:flex;position:fixed;left:0;right:0;bottom:0;z-index:40;gap:var(--s-5);
    align-items:center;padding:var(--s-5) var(--s-6);background:var(--wb-surface);
    border-top:1px solid var(--wb-border)}
  .mob .p{font:700 var(--t-md)/1.2 var(--font-mono);color:var(--wb-ink);
    font-variant-numeric:tabular-nums}
  .mob .s{display:block;font:700 var(--t-2xs)/1.4 var(--font);color:var(--wb-unknown)}
  .mob .btn{width:auto;margin-left:auto;height:var(--control-h-sm);
    padding:0 var(--s-7);font-size:var(--t-base)}
}

/* ---- Parts menu (cluster 2.1, added). D2's own right-column "parts
   menu" (D2 Section 1, layout item 5) was never built out as static CSS,
   since D2's mockup populated it with JS-only markup this cluster does
   not reuse. Doubles as the no-JS-safe picker: every row here is a real
   <select> plus submit buttons, working with no client-side script at
   all; builder.js enhances the same forms with fetch. ---------------- */
/* Two disclosures (Essential/Optional) rather than one long list, added
   at Dylan's request: a short Optional group left mostly collapsed keeps
   the menu column closer to the stage's own height instead of trailing
   off under it. Native <details>/<summary>, no JS required to open;
   builder.js only preserves open/closed state across a partial swap. */
.menu-group{border:1px solid var(--wb-line);border-radius:var(--r-md);
  margin-bottom:var(--s-5)}
.menu-group:last-child{margin-bottom:0}
.menu-group-summary{cursor:pointer;list-style:none;display:flex;
  justify-content:space-between;align-items:center;gap:var(--s-4);
  padding:var(--s-4) var(--s-5);font:700 var(--t-sm)/1.5 var(--font);
  color:var(--wb-ink)}
.menu-group-summary::-webkit-details-marker{display:none}
.menu-group-summary::before{content:"\25B8";display:inline-block;
  margin-right:var(--s-3);color:var(--wb-muted);transition:transform .15s ease}
.menu-group[open] > .menu-group-summary::before{transform:rotate(90deg)}
.menu-group-count{font:var(--t-xs)/1.6 var(--font-mono);color:var(--wb-muted);
  white-space:nowrap}
.menu-group .menu{padding:var(--s-5);border-top:1px solid var(--wb-line)}
.menu{display:flex;flex-direction:column;gap:var(--s-5)}
/* Body/Wings/Communications, nested one level inside "Essential parts"
   (Dylan's request, live review: without this the parts menu had no way
   to stay shorter than the stage on page load - only one sub-group, Body,
   opens by default, per group.open from presenters.py). Same accordion,
   toned down so it doesn't read as a second top-level card: no border of
   its own, background instead, tighter padding. */
.menu-group-sub{border:0;border-radius:var(--r-sm);background:var(--wb-surface-2)}
.menu-group-sub .menu-group-summary{padding:var(--s-3) var(--s-4);
  font:700 var(--t-xs)/1.5 var(--font)}
.menu-group-sub .menu{padding:var(--s-4)}
.menurow{padding-bottom:var(--s-5);border-bottom:1px solid var(--wb-line)}
.menurow:last-child{border-bottom:0;padding-bottom:0}
.menurow-head{display:flex;justify-content:space-between;align-items:baseline;
  gap:var(--s-4);flex-wrap:wrap;font:var(--t-sm)/1.5 var(--font)}
.menurow-label{font-weight:700;color:var(--wb-ink)}
.menurow-state{font:var(--t-xs)/1.6 var(--font-mono);color:var(--wb-muted)}
.menurow-state.ok{color:var(--wb-info)}
.menurow-state.open{color:var(--wb-unknown)}
.menurow-state.unk{color:var(--wb-unknown)}
.menurow-note{margin:var(--s-3) 0 0;font-size:var(--t-xs);color:var(--wb-muted)}
.menurow-instance{padding-top:var(--s-3)}
.menurow-instance:not(:first-child){margin-top:var(--s-3);border-top:1px dashed var(--wb-line)}
.menurow-form{display:flex;flex-wrap:wrap;gap:var(--s-3);align-items:center;
  margin-top:var(--s-3);min-width:0}
.menurow-form select{flex:1 1 auto;min-width:0;max-width:100%;
  background:var(--wb-surface);color:var(--wb-ink);border:1px solid var(--wb-line);
  border-radius:var(--r-sm);padding:var(--s-3) var(--s-4);font:var(--t-sm)/1.4 var(--font)}

/* JS-added search box, one per slot, sitting on its own line above the
   existing select via flex-wrap + flex-basis:100% (the select+button row
   below is untouched). Filters that same select's <option>s in place, so
   there is no second list to keep in sync and no extra height beyond this
   one field - the catalog is headed for thousands of parts (Dylan's
   note), but the picker itself stays exactly as compact as it was. */
.menurow-search{flex:1 1 100%;min-width:0;background:var(--wb-surface);
  color:var(--wb-ink);border:1px solid var(--wb-line);border-radius:var(--r-sm);
  padding:var(--s-3) var(--s-4);font:var(--t-sm)/1.4 var(--font)}

/* ---- FAQ (cluster 2.1 follow-up, added at Dylan's request for SEO).
   Real, sourced questions (dataforseo_faq_questions.md's PAA pull); the
   full buildout with search-volume-validated terms is build plan cluster
   4.3's own scope, not this cluster's. Reuses the same chevron-disclosure
   language as .menu-group above, in its own class family since a FAQ
   entry is prose, not a form. Marked up with schema.org FAQPage JSON-LD
   in build.html for SEO, generated server-side from the same list. */
.faq h2{margin-bottom:var(--s-5)}
.faq-item{border-top:1px solid var(--wb-line)}
.faq-item:first-of-type{border-top:0}
.faq-q{cursor:pointer;list-style:none;display:flex;align-items:center;
  gap:var(--s-3);padding:var(--s-5) 0;font:700 var(--t-sm)/1.5 var(--font);
  color:var(--wb-ink)}
.faq-q::-webkit-details-marker{display:none}
.faq-q::before{content:"\25B8";display:inline-block;color:var(--wb-muted);
  transition:transform .15s ease;flex:none}
.faq-item[open] > .faq-q::before{transform:rotate(90deg)}
.faq-a{margin:0 0 var(--s-5);padding-left:calc(1em + var(--s-3));
  font-size:var(--t-sm);line-height:1.6;color:var(--wb-ink-soft)}

/* The primary image fills its tile (Dylan). The state pip and plus sit on top. */
.tile .box .thumb{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.tile .box .pip{position:relative;z-index:1}
.opt .othumb{width:40px;height:40px;object-fit:cover;border-radius:var(--r-sm,6px);flex:none;margin-right:var(--s-3)}
.tile .box .thumb ~ .pip{display:none}
.opt-off{padding:var(--s-2) 0;opacity:.7}
.opt-note{display:block;color:var(--wb-unknown,#b79cf0)}
.pop-chosen{display:flex;gap:var(--s-3);align-items:center;padding:var(--s-3);margin-bottom:var(--s-3);
  border:1px solid var(--wb-line);border-radius:var(--r-md);color:inherit;text-decoration:none}
.pop-chosen img{width:56px;height:56px;object-fit:cover;border-radius:var(--r-sm,6px);flex:none}
.pop-chosen small{display:block;color:var(--wb-muted)}
.optrow{display:flex;align-items:stretch;gap:var(--s-2)}
.optrow .opt{flex:1;min-width:0}
.opt-view{align-self:center;font-size:var(--t-2xs);color:var(--wb-muted);padding:var(--s-2)}
