/* ============================================================================
   SOLO Configurator — Design System  ("Instrument")
   ----------------------------------------------------------------------------
   Direction: enterprise engineering software. Slate ground, one blue for
   action, semantic colour only where it carries meaning. The reference points
   are Fusion 360, Onshape and Linear — instrumentation, not decoration.

   TWO THEMES, dark by default (the supplied engineering palette is a dark one).
   Light is the same language in daylight: slate and blue, never cream and gold.
   Both are defined token-for-token below.

   The A3 sheet itself is NOT themed: it is an issued artifact and must look the
   same in both (verified — the exported SVG is byte-identical either way).

   The finish is carried by --finish-*, written at runtime by solo-store.js from
   the finish's own PBR colour, and it is scoped to the PRODUCT — the mirror
   frame, the soap dispenser, the swatch ring, the 3D model. It deliberately does
   NOT touch the interface: an accent that changes with a dropdown is what made
   this read as a furniture configurator rather than an engineering tool.

   Type: IBM Plex, self-hosted (no CDN — the demo must survive a bad meeting-
   room connection). One family, three roles: light tracked caps for display,
   Sans for UI, Mono for every millimetre and part code.
   ========================================================================== */

/* ------------------------------------------------------------------ fonts */
@font-face{
  font-family:'Plex Sans'; src:url('../fonts/IBMPlexSans-var.woff2') format('woff2');
  font-weight:200 700; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'Plex Mono'; src:url('../fonts/IBMPlexMono-400.woff2') format('woff2');
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'Plex Mono'; src:url('../fonts/IBMPlexMono-500.woff2') format('woff2');
  font-weight:500; font-style:normal; font-display:swap;
}

/* ----------------------------------------------------------------- tokens */
/* Non-colour tokens are theme-independent and live here. Every colour lives in
   one of the two theme blocks below; nothing else in the stylesheet may hardcode
   a palette value, or the theme silently half-applies. */
:root{
  /* THE DRAWING SHEET IS NOT THEMED. It is an issued artifact — the same bytes
     go to SVG, to print and to the client — so it must not change appearance
     with the interface. Warm drawing paper in both themes, deliberately. */
  --paper:#F4F2EC;   --paper-ink:#16151A;   --paper-line:#C9C4B8;

  /* THE VIEWPORT IS A STUDIO and keeps a light neutral backdrop in both themes
     (see backdropTexture() in solo-preview3d.js). Its overlays therefore cannot
     inherit the theme's type colours — on the light studio the dark theme's
     --dim/--faint were barely legible. These are fixed to read on that backdrop
     whichever way the interface is set. */
  --vp-ink:#1E2836;   --vp-dim:#46526244;
  --vp-label:#39485C;  --vp-sub:#5A6878;
  --vp-glass:rgba(255,255,255,.74);
  /* Panel surface + its secondary ink. Separate from --vp-glass/--vp-sub
     because a panel of small text has a legibility budget a two-word pill
     does not — see .scene-panel. */
  --vp-panel:rgba(252,253,254,.965);
  --vp-note:#4A576A;
  --vp-glass-line:rgba(24,34,48,.13);

  --r-xs:4px; --r-sm:7px; --r-md:11px; --r-lg:16px; --r-xl:22px;

  --ease:cubic-bezier(.2,.8,.2,1);
  --ease-out:cubic-bezier(.16,1,.3,1);
  --t1:120ms; --t2:220ms; --t3:380ms; --t4:640ms;

  --rail:88px; --inspector:344px; --topbar:54px; --deck:214px;
}

/* ============================================================ THEME TOKENS
   Direction: enterprise engineering software — Fusion 360, Onshape, Linear.
   Cool slate ground, ONE blue for action, semantic colour used only to carry
   meaning. The goal is credibility, not decoration: a senior engineer should
   read this as instrumentation, and instrumentation does not use colour to
   flatter itself.

   THE ACCENT IS NO LONGER THE FINISH. Previously --metal-* were rewritten at
   runtime from the selected finish, so choosing Brushed Gold turned the whole
   interface gold. That single mechanic is what made this read as a luxury
   furniture configurator: no engineering tool lets a product option repaint its
   own chrome. The finish now drives --finish-* only, which is used where it is
   genuinely about the PRODUCT — the mirror frame in the drawing, the soap
   dispenser, the swatches — and never the interface.

   Token names are unchanged on purpose. Renaming --metal across ~80 call sites
   would be churn with no user-visible benefit and real regression risk; the
   name is now simply "the accent", and it is blue. */

/* ------------------------------------------------------------------- DARK
   The default. Values are the supplied engineering palette verbatim:
   #0F172A ground, #1E293B surface, #334155 border, #F8FAFC / #94A3B8 type,
   #2563EB primary, #3B82F6 accent. */
:root, :root[data-theme="dark"]{
  --ink-0:#0B1220;   --ink-1:#0F172A;   --ink-2:#1E293B;
  --ink-3:#243349;   --ink-4:#2E3F58;
  --line:#334155;    --line-2:#475569;

  /* Supplied secondary is #94A3B8; it measures 4.16:1 on the RAISED card tone
     (--ink-4) and fails AA for 11px body text. Lifted the minimum amount that
     clears it — the hue is unchanged, so the palette still reads as specified. */
  --text:#F8FAFC;    --dim:#CBD5E1;     --faint:#A3B2C6;

  --paper-2:#E2E8F0;                 /* the wash plane in the LIVE elevation */

  /* THE ACCENT — one blue, fixed. Primary action is the brand #2563EB; the
     lighter #60A5FA is the TEXT step, because #3B82F6 on #1E293B measures
     3.3:1 and fails AA for body text while reading fine as a fill. */
  --metal:#2563EB;
  --metal-hi:#3B82F6;
  --metal-lo:#1D4ED8;
  --metal-ink:#FFFFFF;               /* text that sits ON the accent */
  --metal-text:#60A5FA;              /* accent used AS text / annotation */
  --metal-glow:rgba(59,130,246,.18);
  --metal-line:rgba(59,130,246,.42);

  /* semantics — supplied values, with the two that failed AA lifted for TEXT
     only (#EF4444 measures 3.4:1 on the surface tone; #2563EB likewise). */
  --ok:#22C55E;    --ok-bg:rgba(34,197,94,.12);
  --warn:#F59E0B;  --warn-bg:rgba(245,158,11,.12);
  --bad:#F87171;   --bad-bg:rgba(239,68,68,.14);
  --info:#38BDF8;  --info-bg:rgba(56,189,248,.12);
  --bad-solid:#EF4444;               /* fills and marks, where AA does not bind */

  --sheen:rgba(148,163,184,.09);
  --sheen-2:rgba(148,163,184,.05);
  --hairline:rgba(148,163,184,.12);
  --glass:rgba(30,41,59,.78);
  --glass-line:rgba(148,163,184,.16);
  --scrim:rgba(2,6,16,.72);
  --stage-1:#0D1626;  --stage-2:#0A1220;
  --knob-in:rgba(255,255,255,.18);
  --knob-out:rgba(2,6,16,.5);
  --on-metal:#fff;

  --elev-wall:#131E31;      --elev-sidewall:#0D1626;
  --elev-bulk:#1B2739;      --elev-cab:#16202F;
  --elev-plane-line:#64748B;
  --elev-appl:#94A3B8;      --elev-water:#38BDF8;
  --elev-glow:#BFDBFE;      --elev-halo-op:.34;  --elev-strip-op:.5;
  --veil:rgba(15,23,42,.92);

  --sh-1:0 1px 2px rgba(2,6,16,.4);
  --sh-2:0 2px 8px -2px rgba(2,6,16,.5);
  --sh-3:0 12px 28px -10px rgba(2,6,16,.6);
  --sh-4:0 28px 60px -20px rgba(2,6,16,.7);

  color-scheme:dark;
}

/* ------------------------------------------------------------------ LIGHT
   The same language in daylight — slate and blue, not cream and gold. Kept
   because the drawing office reads it better under projector light. */
:root[data-theme="light"]{
  --ink-0:#E2E8F0;   --ink-1:#F1F5F9;   --ink-2:#F8FAFC;
  --ink-3:#FFFFFF;   --ink-4:#FFFFFF;
  --line:#CBD5E1;    --line-2:#94A3B8;

  --text:#0F172A;    --dim:#475569;     --faint:#64748B;

  --paper-2:#F1F5F9;

  --metal:#2563EB;
  --metal-hi:#3B82F6;
  --metal-lo:#1D4ED8;
  --metal-ink:#FFFFFF;
  --metal-text:#1D4ED8;
  --metal-glow:rgba(37,99,235,.10);
  --metal-line:rgba(37,99,235,.34);

  --ok:#15803D;    --ok-bg:rgba(21,128,61,.10);
  --warn:#B45309;  --warn-bg:rgba(180,83,9,.10);
  --bad:#B91C1C;   --bad-bg:rgba(185,28,28,.08);
  --info:#0369A1;  --info-bg:rgba(3,105,161,.08);
  --bad-solid:#DC2626;

  --sheen:rgba(15,23,42,.045);
  --sheen-2:rgba(15,23,42,.026);
  --hairline:rgba(15,23,42,.07);
  --glass:rgba(255,255,255,.8);
  --glass-line:rgba(15,23,42,.09);
  --scrim:rgba(15,23,42,.38);
  --stage-1:#FFFFFF;  --stage-2:#EEF2F7;
  --knob-in:rgba(255,255,255,.6);
  --knob-out:rgba(15,23,42,.18);
  --on-metal:#fff;

  --elev-wall:#E2E8F0;      --elev-sidewall:#CBD5E1;
  --elev-bulk:#334155;      --elev-cab:#1E293B;
  --elev-plane-line:#CBD5E1;
  --elev-appl:#64748B;      --elev-water:#0369A1;
  --elev-glow:#93C5FD;      --elev-halo-op:.5;   --elev-strip-op:.7;
  --veil:rgba(241,245,249,.92);

  --sh-1:0 1px 2px rgba(15,23,42,.08);
  --sh-2:0 2px 8px -2px rgba(15,23,42,.12);
  --sh-3:0 12px 28px -10px rgba(15,23,42,.16);
  --sh-4:0 28px 60px -20px rgba(15,23,42,.20);

  color-scheme:light;
}

/* ------------------------------------------------------------------ reset */
*,*::before,*::after{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0; background:var(--ink-1); color:var(--text);
  font-family:'Plex Sans',system-ui,-apple-system,'Segoe UI',sans-serif;
  font-size:13.5px; line-height:1.5; font-weight:400;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  overflow:hidden;                    /* the shell scrolls its own panes */
}
h1,h2,h3,h4,p{margin:0}
button,input,select,textarea{font:inherit;color:inherit}
button{background:none;border:0;cursor:pointer}
a{color:var(--metal-text);text-decoration:none}
svg{display:block}
[hidden]{display:none!important}
::selection{background:var(--metal-glow);color:var(--text)}
:focus-visible{outline:2px solid var(--metal);outline-offset:2px;border-radius:var(--r-xs)}

/* thin, unobtrusive scrollbars — this is an instrument, not a document */
*{scrollbar-width:thin;scrollbar-color:var(--line-2) transparent}
*::-webkit-scrollbar{width:9px;height:9px}
*::-webkit-scrollbar-thumb{background:var(--line-2);border-radius:6px;border:2px solid transparent;background-clip:content-box}
*::-webkit-scrollbar-thumb:hover{background:var(--faint);background-clip:content-box}
*::-webkit-scrollbar-track{background:transparent}

/* ------------------------------------------------------------- typography */
.display{
  font-weight:200; text-transform:uppercase; letter-spacing:.22em;
  line-height:1.15;
}
.eyebrow{
  font-size:10px; font-weight:500; text-transform:uppercase; letter-spacing:.19em;
  color:var(--faint);
}
.mono{font-family:'Plex Mono',ui-monospace,monospace; font-variant-numeric:tabular-nums}
.num{font-family:'Plex Mono',ui-monospace,monospace; font-variant-numeric:tabular-nums; letter-spacing:-.01em}
.dim{color:var(--dim)} .faint{color:var(--faint)}
.t-xs{font-size:11px} .t-sm{font-size:12px} .t-md{font-size:13.5px}
.t-lg{font-size:16px} .t-xl{font-size:21px}

/* unit suffix: sits quieter than the number it follows */
.u{color:var(--faint); font-size:.82em; margin-left:.18em; letter-spacing:.02em}

/* ============================================================ app shell */
#app{height:100vh;display:flex;flex-direction:column;overflow:hidden}

.topbar{
  height:var(--topbar); flex:0 0 var(--topbar);
  display:flex; align-items:center; gap:14px; padding:0 14px;
  background:linear-gradient(var(--ink-2),var(--ink-1));
  border-bottom:1px solid var(--line); position:relative; z-index:40;
}
.topbar::after{                       /* a hairline of metal — the only chrome */
  content:''; position:absolute; left:0; right:0; bottom:-1px; height:1px;
  background:linear-gradient(90deg,transparent,var(--metal-line) 22%,var(--metal-line) 78%,transparent);
  opacity:.55; transition:opacity var(--t3) var(--ease);
}

.mark{display:flex;align-items:center;gap:9px;padding-right:14px;border-right:1px solid var(--line)}
.mark .glyph{
  width:26px;height:26px;border-radius:7px;position:relative;flex:none;
  background:linear-gradient(145deg,var(--metal-hi),var(--metal) 45%,var(--metal-lo));
  box-shadow:0 0 0 1px var(--metal-line), 0 3px 10px -2px var(--metal-glow);
}
.mark .glyph::before{                 /* a mirror pair — the product itself */
  content:'';position:absolute;inset:6px 7px;border-radius:2px;
  background:var(--metal-ink);opacity:.82;
  clip-path:polygon(0 0,42% 0,42% 100%,0 100%, 58% 0,100% 0,100% 100%,58% 100%);
}
.mark b{font-weight:500;font-size:13px;letter-spacing:.02em}
.mark .sub{display:block;font-size:9.5px;color:var(--faint);letter-spacing:.16em;text-transform:uppercase;line-height:1.1}

.tb-field{display:flex;flex-direction:column;gap:1px;min-width:0}
.tb-field .k{font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--faint)}
.tb-field .v{font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tb-sep{width:1px;height:26px;background:var(--line);flex:none}
.tb-spacer{flex:1}

/* editable project name — looks like text until you touch it */
.inline-input{
  background:transparent;border:1px solid transparent;border-radius:var(--r-xs);
  padding:2px 6px;margin-left:-6px;min-width:60px;max-width:210px;
  transition:background var(--t1),border-color var(--t1);
}
.inline-input:hover{background:var(--ink-3);border-color:var(--line)}
.inline-input:focus{background:var(--ink-3);border-color:var(--metal);outline:none}

.body{flex:1;display:grid;grid-template-columns:var(--rail) 1fr var(--inspector);min-height:0}

/* ------------------------------------------------------------- step rail */
.rail{
  background:var(--ink-2); border-right:1px solid var(--line);
  overflow-y:auto; overflow-x:hidden; padding:8px 0 20px; position:relative; z-index:20;
}
.rail-head{padding:8px 10px 10px;text-align:center}
.step{
  --st:var(--faint);
  width:100%; padding:9px 6px 8px; display:flex; flex-direction:column; align-items:center; gap:5px;
  position:relative; color:var(--st); transition:color var(--t2) var(--ease);
}
.step:hover{color:var(--text)}
.step .ring{
  width:34px;height:34px;border-radius:50%;display:grid;place-items:center;
  border:1px solid var(--line-2); background:var(--ink-3);
  transition:border-color var(--t2) var(--ease), background var(--t2), box-shadow var(--t2), transform var(--t2) var(--ease);
}
.step:hover .ring{transform:translateY(-1px)}
.step .ring svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.5}
.step .lbl{font-size:9.5px;letter-spacing:.09em;text-transform:uppercase;text-align:center;line-height:1.25}
.step .n{
  position:absolute;top:7px;right:12px;font-family:'Plex Mono',monospace;
  font-size:8.5px;color:var(--faint);
}
/* visited */
/* Rail states carry status, not decoration: done is green, current is the
   action blue, everything ahead is muted slate. */
.step.done{--st:var(--dim)}
.step.done .ring{
  border-color:color-mix(in srgb,var(--ok) 46%,transparent);
  color:var(--ok); background:var(--ok-bg);
}
/* active */
.step.on{--st:var(--text)}
.step.on .ring{
  border-color:var(--metal); background:linear-gradient(145deg,var(--metal-hi),var(--metal) 50%,var(--metal-lo));
  color:var(--metal-ink); box-shadow:0 0 0 4px var(--metal-glow);
}
.step.on::before{                     /* active marker on the rail edge */
  content:'';position:absolute;left:0;top:50%;transform:translateY(-50%);
  width:2px;height:26px;background:var(--metal);border-radius:0 2px 2px 0;
}
/* a step that currently carries an error */
.step.err .ring{border-color:var(--bad);color:var(--bad);box-shadow:0 0 0 3px var(--bad-bg)}
.step.on.err .ring{background:var(--bad);color:var(--on-metal)}

/* the connecting thread between steps — fills as you progress */
.rail-thread{position:absolute;left:50%;top:0;bottom:0;width:1px;background:var(--line);z-index:-1}

/* ============================================================== viewport */
.stage{display:flex;flex-direction:column;min-width:0;background:var(--ink-0);position:relative}

/* No background grid. It was tried and removed: at any opacity that made it
   visible it competed with the dimension chain, and the drawing already carries
   its own measurement language. The canvas stays a clean ground. */
.viewport{
  flex:1; min-height:180px; position:relative; overflow:hidden;
  background:
    radial-gradient(120% 90% at 50% 8%, var(--sheen), transparent 60%),
    linear-gradient(var(--stage-1), var(--stage-2));
}
.viewport canvas{display:block;width:100%;height:100%;touch-action:none}
.viewport .svg-stage{position:absolute;inset:0;width:100%;height:100%}

/* floating viewport controls — glass, because they sit over the product */
.vp-tools{
  position:absolute; top:12px; left:12px; display:flex; gap:6px; z-index:6;
}
/* Chips sit ON the studio, so they take the studio's glass and ink. */
.viewport .glass{ background:var(--vp-glass); border-color:var(--vp-glass-line) }
.viewport .seg button{ color:var(--vp-sub) }
.viewport .seg button:hover{ color:var(--vp-ink); background:rgba(24,34,48,.06) }
.viewport .seg button.on{ color:var(--metal-ink); background:var(--metal) }
.viewport .icon-btn{ color:var(--vp-sub) }
.viewport .icon-btn:hover{ color:var(--vp-ink); background:rgba(24,34,48,.06) }
.viewport .icon-btn.on{ color:var(--metal) }
.vp-tools.right{left:auto;right:12px}
.vp-tools.bottom{top:auto;bottom:12px}
.glass{
  background:var(--glass); backdrop-filter:blur(14px) saturate(1.3);
  -webkit-backdrop-filter:blur(14px) saturate(1.3);
  border:1px solid var(--glass-line); border-radius:var(--r-md);
  box-shadow:var(--sh-2);
}
.seg{display:flex;padding:3px;gap:2px}
.seg button{
  padding:5px 11px;border-radius:calc(var(--r-md) - 4px);font-size:11px;
  letter-spacing:.07em;text-transform:uppercase;color:var(--dim);
  transition:color var(--t1),background var(--t1);white-space:nowrap;
}
.seg button:hover{color:var(--text);background:var(--sheen)}
.seg button.on{background:var(--metal);color:var(--metal-ink);font-weight:500}

.icon-btn{
  width:32px;height:32px;display:grid;place-items:center;border-radius:var(--r-sm);
  color:var(--dim);transition:color var(--t1),background var(--t1),transform var(--t1);
}
.icon-btn svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.6}
.icon-btn:hover{color:var(--text);background:var(--sheen)}
.icon-btn:active{transform:scale(.93)}
.icon-btn.on{color:var(--metal)}
.tool-group{display:flex;padding:3px;gap:1px}

/* viewport status readouts, bottom-left */
.vp-readout{
  position:absolute;left:12px;bottom:12px;z-index:5;display:flex;gap:14px;
  padding:7px 12px;font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--vp-sub);
}
.vp-readout b{color:var(--vp-ink);font-weight:500}

/* the product name plate, top-centre — quiet, like a museum label */
.nameplate{
  position:absolute;top:14px;left:50%;transform:translateX(-50%);z-index:4;
  text-align:center;pointer-events:none;
}
.nameplate .t{font-size:11px;letter-spacing:.26em;text-transform:uppercase;font-weight:300;color:var(--vp-label)}
.nameplate .s{font-size:10px;letter-spacing:.14em;color:var(--vp-sub);margin-top:3px}

/* ============================================================ option deck
   The current step's choices live along the bottom, so the product stays the
   hero. Behaves like a CAD command bar rather than a form. */
/* The deck must never starve the product view. It used to be flex:0 0 auto with
   no maximum, so a step carrying several groups — Appliances has three (tap,
   soap, appliance set) — grew to whatever its content needed and squeezed the
   viewport to a sliver. The product is the point of the screen; the deck yields
   to it and scrolls its own overflow instead. */
.dock{
  flex:0 1 auto; min-height:var(--deck); max-height:min(46vh, 460px);
  background:var(--ink-2);
  border-top:1px solid var(--line); display:flex; flex-direction:column;
  position:relative; z-index:15;
}
/* With a document open the height is driven in pixels by the controller, which
   clamps it against the viewport's own 180 px floor — the cap has to step aside
   or the expand control would silently do nothing. */
.dock.doc-open{ max-height:none }
.dock-panes{ flex:1; min-height:0; display:flex; flex-direction:column }

.deck{
  flex:1; min-height:0; display:flex; flex-direction:column; position:relative;
}
.deck-head{
  display:flex;align-items:baseline;gap:12px;padding:11px 18px 9px;
  border-bottom:1px solid var(--line);
}
.deck-head .step-n{
  font-family:'Plex Mono',monospace;font-size:10.5px;color:var(--metal-text);
  letter-spacing:.1em;
}
.deck-head h2{font-size:15px;font-weight:500;letter-spacing:.01em}
.deck-head .blurb{font-size:12px;color:var(--faint);flex:1;min-width:0}
.deck-body{
  flex:1;min-height:0;overflow-x:auto;overflow-y:auto;padding:14px 18px 16px;
  display:flex;gap:10px;align-items:stretch;scroll-behavior:smooth;
}
.deck-body.wrap{flex-wrap:wrap;overflow-y:auto;overflow-x:hidden;align-content:flex-start}
.deck-body.col{flex-direction:column;overflow-y:auto;gap:12px}

/* ------------------------------------------------------------ option card */
.opt{
  --oc:var(--line-2);
  flex:0 0 auto; align-self:stretch; width:168px; min-height:104px; padding:12px;
  background:var(--ink-3); border:1px solid var(--oc); border-radius:var(--r-md);
  display:flex; flex-direction:column; gap:6px; text-align:left; position:relative;
  transition:border-color var(--t2) var(--ease), background var(--t2),
             transform var(--t2) var(--ease), box-shadow var(--t2) var(--ease);
  overflow:hidden;
}
.opt::after{                          /* metal wash on selection */
  content:'';position:absolute;inset:0;pointer-events:none;opacity:0;
  background:linear-gradient(150deg,var(--metal-glow),transparent 62%);
  transition:opacity var(--t3) var(--ease);
}
.opt:hover{background:var(--ink-4);border-color:var(--faint);transform:translateY(-2px);box-shadow:var(--sh-2)}
.opt:active{transform:translateY(0) scale(.99)}
.opt.on{border-color:var(--metal);background:var(--ink-4);box-shadow:0 0 0 1px var(--metal-line),var(--sh-2)}
.opt.on::after{opacity:1}
.opt .name{font-size:13px;font-weight:500;letter-spacing:.01em}
.opt .desc{font-size:11.5px;color:var(--faint);line-height:1.4;flex:1}
.opt .tag{
  font-family:'Plex Mono',monospace;font-size:9.5px;color:var(--dim);
  letter-spacing:.08em;text-transform:uppercase;
}
.opt .tick{
  position:absolute;top:9px;right:9px;width:17px;height:17px;border-radius:50%;
  background:var(--metal);display:grid;place-items:center;
  opacity:0;transform:scale(.5);transition:opacity var(--t2) var(--ease),transform var(--t2) var(--ease-out);
}
.opt .tick svg{width:10px;height:10px;stroke:var(--metal-ink);stroke-width:2.6;fill:none}
.opt.on .tick{opacity:1;transform:scale(1)}
.opt.blocked{opacity:.4}
.opt.blocked:hover{transform:none;border-color:var(--bad);background:var(--ink-3)}
.opt .why{font-size:10.5px;color:var(--bad)}

/* A card carrying its own small drawing of the option.
   The svg MUST be sized in both axes: with width:auto an inline <svg> stretches
   to the container width and then derives its height from the viewBox ratio —
   88px inside a 44px box, painted straight over the title. Sizing both axes and
   letting preserveAspectRatio ("xMidYMid meet") letterbox keeps it in its lane. */
.opt .fig{height:50px;flex:none;display:block;margin:0 0 2px}
.opt .fig svg{display:block;width:100%;height:100%;overflow:visible}
.opt .fig svg [data-ln]{stroke:var(--dim);fill:none;stroke-width:1.4;vector-effect:non-scaling-stroke}
.opt .fig svg [data-fl]{fill:var(--ink-1);stroke:var(--dim);stroke-width:1.2;vector-effect:non-scaling-stroke}
.opt.on .fig svg [data-ln]{stroke:var(--metal)}
.opt.on .fig svg [data-fl]{stroke:var(--metal)}

/* --------------------------------------------------------- finish swatch */
.swatches{display:flex;flex-wrap:wrap;gap:9px;align-content:flex-start}
.sw-group{display:flex;flex-direction:column;gap:8px;padding-right:16px;border-right:1px solid var(--line)}
.sw-group:last-child{border-right:0}
.sw-group .eyebrow{padding-left:2px}
.sw-row{display:flex;flex-wrap:wrap;gap:8px;max-width:330px}
.sw{
  width:54px;padding:0;background:none;border:0;position:relative;
  display:flex;flex-direction:column;gap:5px;align-items:center;
}
.sw .chip{
  width:44px;height:44px;border-radius:50%;position:relative;
  border:1px solid var(--knob-out);
  box-shadow:inset 0 1px 3px var(--knob-in),inset 0 -3px 7px var(--knob-out),var(--sh-1);
  transition:transform var(--t2) var(--ease-out),box-shadow var(--t2);
}
.sw:hover .chip{transform:scale(1.09)}
.sw.on .chip{
  box-shadow:0 0 0 2px var(--ink-2),0 0 0 4px var(--finish,var(--metal)),
             inset 0 1px 3px var(--knob-in),inset 0 -3px 7px var(--knob-out);
}
.sw .code{font-family:'Plex Mono',monospace;font-size:9.5px;color:var(--faint);letter-spacing:.04em}
.sw.on .code{color:var(--metal-text)}
.sw .nm{
  position:absolute;bottom:calc(100% + 7px);left:50%;transform:translateX(-50%) translateY(3px);
  white-space:nowrap;font-size:10.5px;padding:4px 8px;border-radius:var(--r-xs);
  background:var(--ink-4);border:1px solid var(--line-2);color:var(--text);
  opacity:0;pointer-events:none;transition:opacity var(--t1),transform var(--t1);z-index:5;
}
.sw:hover .nm{opacity:1;transform:translateX(-50%) translateY(0)}

/* ------------------------------------------------------- numeric control
   Width and station count: a slider married to a mono readout. Dimensions are
   the product, so the number is typographically the loudest thing here. */
.dimctl{display:flex;flex-direction:column;gap:9px;min-width:340px;max-width:520px}
.dimctl .read{display:flex;align-items:baseline;gap:7px}
.dimctl .read .big{
  font-family:'Plex Mono',monospace;font-size:38px;font-weight:500;
  letter-spacing:-.02em;line-height:1;color:var(--text);
}
.dimctl .read .un{font-size:13px;color:var(--faint);letter-spacing:.1em}
.dimctl .read .hint{margin-left:auto;font-size:11px;color:var(--faint)}
.rng{
  -webkit-appearance:none;appearance:none;width:100%;height:26px;background:transparent;cursor:grab;
  margin:0;                            /* UA default 2px pushed it past .dimctl */
}
.rng:active{cursor:grabbing}
.rng::-webkit-slider-runnable-track{
  height:3px;border-radius:2px;
  background:linear-gradient(90deg,var(--metal) 0 var(--pct,50%),var(--line-2) var(--pct,50%) 100%);
}
.rng::-moz-range-track{height:3px;border-radius:2px;background:var(--line-2)}
.rng::-moz-range-progress{height:3px;border-radius:2px;background:var(--metal)}
.rng::-webkit-slider-thumb{
  -webkit-appearance:none;width:17px;height:17px;border-radius:50%;margin-top:-7px;
  background:linear-gradient(145deg,var(--metal-hi),var(--metal-lo));
  border:1px solid var(--knob-out);box-shadow:0 2px 8px -1px var(--metal-glow),var(--sh-1);
  transition:transform var(--t1) var(--ease);
}
.rng::-moz-range-thumb{
  width:17px;height:17px;border-radius:50%;border:1px solid var(--knob-out);
  background:linear-gradient(145deg,var(--metal-hi),var(--metal-lo));
}
.rng:hover::-webkit-slider-thumb{transform:scale(1.15)}
.rng:active::-webkit-slider-thumb{transform:scale(1.24)}
.ticks{display:flex;justify-content:space-between;font-family:'Plex Mono',monospace;font-size:10px;color:var(--faint)}

/* stepper for station count */
.stepper{display:flex;align-items:center;gap:2px;background:var(--ink-3);border:1px solid var(--line-2);border-radius:var(--r-sm);padding:3px}
.stepper button{width:30px;height:30px;border-radius:var(--r-xs);display:grid;place-items:center;color:var(--dim);transition:background var(--t1),color var(--t1)}
.stepper button:hover:not(:disabled){background:var(--ink-4);color:var(--text)}
.stepper button:disabled{opacity:.3;cursor:not-allowed}
.stepper .val{min-width:44px;text-align:center;font-family:'Plex Mono',monospace;font-size:17px;font-weight:500}

/* station pips — a visual count you can read without reading the number */
.pips{display:flex;gap:4px;align-items:flex-end;height:26px}
.pip{width:7px;background:var(--metal);border-radius:1.5px;opacity:.85;animation:pip-in var(--t3) var(--ease-out) backwards}
@keyframes pip-in{from{transform:scaleY(.2);opacity:0}to{transform:scaleY(1);opacity:.85}}

/* ============================================================= inspector */
.inspector{
  background:var(--ink-2); border-left:1px solid var(--line);
  display:flex; flex-direction:column; min-height:0; z-index:20;
}
.insp-tabs{display:flex;border-bottom:1px solid var(--line);flex:none}
.insp-tabs button{
  flex:1;padding:11px 4px 9px;font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;
  color:var(--faint);position:relative;transition:color var(--t2);
}
.insp-tabs button:hover{color:var(--dim)}
.insp-tabs button.on{color:var(--text)}
.insp-tabs button.on::after{
  content:'';position:absolute;left:14%;right:14%;bottom:-1px;height:2px;
  background:var(--metal);border-radius:2px 2px 0 0;
}
.insp-body{flex:1;overflow-y:auto;padding:14px 15px 26px}

.sect{margin-bottom:20px}
.sect-h{
  display:flex;align-items:center;gap:8px;margin-bottom:9px;
}
.sect-h .eyebrow{flex:none}
.sect-h .rule{flex:1;height:1px;background:var(--line)}

/* the spec row — label left, value right, mono, hairline between */
.row{
  display:flex;align-items:baseline;gap:10px;padding:5.5px 0;
  border-bottom:1px solid var(--hairline);
}
.row:last-child{border-bottom:0}
.row .k{font-size:12px;color:var(--dim);flex:1;min-width:0}
.row .v{
  font-family:'Plex Mono',monospace;font-size:12px;text-align:right;
  font-variant-numeric:tabular-nums;color:var(--text);
}
.row .v.metal{color:var(--metal-text)}
/* value that just changed — a brief metal flash, the "recently changed" cue */
.row .v.flash{animation:flash var(--t4) var(--ease)}
@keyframes flash{
  0%{color:var(--metal);text-shadow:0 0 12px var(--metal-glow)}
  100%{color:var(--text);text-shadow:none}
}

/* headline price / stat block */
.stat{padding:12px 13px;background:var(--ink-3);border:1px solid var(--line);border-radius:var(--r-md)}
.stat .lab{font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--faint)}
.stat .big{
  font-family:'Plex Mono',monospace;font-size:26px;font-weight:500;letter-spacing:-.02em;
  margin-top:3px;display:flex;align-items:baseline;gap:5px;
}
.stat .cur{font-size:12px;color:var(--faint)}
.stat .foot{font-size:10.5px;color:var(--faint);margin-top:5px}

.badge{
  display:inline-flex;align-items:center;gap:5px;padding:2.5px 7px;border-radius:999px;
  font-size:10px;letter-spacing:.1em;text-transform:uppercase;font-weight:500;
  border:1px solid currentColor;
}
.badge.b-ok{color:var(--ok);background:var(--ok-bg)}
.badge.b-warn{color:var(--warn);background:var(--warn-bg)}
.badge.b-bad{color:var(--bad);background:var(--bad-bg)}
.badge.b-info{color:var(--info);background:var(--info-bg)}
.badge.b-metal{color:var(--metal-text);background:var(--metal-glow)}

/* ------------------------------------------------------------ violations */
.vio{
  display:flex;gap:9px;padding:9px 10px;border-radius:var(--r-sm);margin-bottom:7px;
  border:1px solid var(--line);background:var(--ink-3);
  animation:vio-in var(--t3) var(--ease-out) backwards;
}
@keyframes vio-in{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
.vio .ic{width:15px;height:15px;flex:none;margin-top:1px}
.vio .ic svg{width:15px;height:15px;stroke-width:1.7;fill:none}
.vio .t{font-size:12px;font-weight:500;margin-bottom:2px}
.vio .h{font-size:11.5px;color:var(--dim);line-height:1.45}
.vio .id{font-family:'Plex Mono',monospace;font-size:9.5px;letter-spacing:.06em;margin-left:auto;flex:none;opacity:.75}
.vio.v-error{border-color:color-mix(in srgb, var(--bad) 42%, transparent);background:var(--bad-bg)}
.vio.v-error .ic{color:var(--bad)} .vio.v-error .id{color:var(--bad)}
.vio.v-warn{border-color:color-mix(in srgb, var(--warn) 38%, transparent);background:var(--warn-bg)}
.vio.v-warn .ic{color:var(--warn)} .vio.v-warn .id{color:var(--warn)}
.vio.v-info .ic{color:var(--info)} .vio.v-info .id{color:var(--info)}
.vio .fix{
  margin-top:6px;font-size:11px;color:var(--metal-text);display:inline-flex;align-items:center;gap:5px;
  white-space:nowrap;align-self:flex-start;
}
.vio .fix svg{width:13px;height:13px;flex:none;stroke-width:1.7;fill:none}
.vio .fix b{font-weight:500;text-transform:capitalize}
.vio .fix:hover{text-decoration:underline}
.vio .grow{min-width:0}

/* --------------------------------------------------------------- buttons */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:7px;
  padding:9px 16px;border-radius:var(--r-sm);font-size:12.5px;font-weight:500;
  letter-spacing:.02em;border:1px solid var(--line-2);background:var(--ink-3);color:var(--text);
  transition:background var(--t1),border-color var(--t1),transform var(--t1),box-shadow var(--t2);
  white-space:nowrap;
}
.btn svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.7}
.btn:hover{background:var(--ink-4);border-color:var(--faint)}
.btn:active{transform:translateY(1px)}
.btn:disabled{opacity:.42;cursor:not-allowed;transform:none}
/* Flat fill, one hairline, no bloom. A gradient with a glow reads as a
   marketing button; enterprise tools use a solid action colour and let the
   label do the work. */
.btn-metal{
  background:var(--metal); color:var(--metal-ink);
  border-color:var(--metal-lo); font-weight:500;
  box-shadow:var(--sh-1);
}
.btn-metal:hover{background:var(--metal-hi);box-shadow:var(--sh-2)}
.btn-metal:active{background:var(--metal-lo)}
.btn-ok{background:var(--ok);color:#04140A;border-color:transparent;font-weight:500}
.btn-danger{background:var(--bad-solid);color:#fff;border-color:transparent;font-weight:500}
.btn-ghost{background:transparent;border-color:transparent;color:var(--dim)}
.btn-ghost:hover{background:var(--ink-3);color:var(--text)}
.btn-lg{padding:12px 22px;font-size:13.5px}
.btn-sm{padding:6px 11px;font-size:11.5px}

/* progress through the flow, in the topbar */
.prog{display:flex;align-items:center;gap:9px}
.prog .bar{width:104px;height:3px;border-radius:2px;background:var(--line-2);overflow:hidden}
.prog .fill{height:100%;background:linear-gradient(90deg,var(--metal-lo),var(--metal));border-radius:2px;transition:width var(--t3) var(--ease)}
.prog .txt{font-family:'Plex Mono',monospace;font-size:11px;color:var(--dim)}

/* autosave pip */
.saved{display:flex;align-items:center;gap:6px;font-size:10.5px;color:var(--faint);letter-spacing:.06em}
.saved .dot{width:6px;height:6px;border-radius:50%;background:var(--ok);box-shadow:0 0 7px var(--ok)}
.saved.busy .dot{background:var(--warn);box-shadow:0 0 7px var(--warn);animation:pulse 1s infinite}
@keyframes pulse{50%{opacity:.35}}

/* ------------------------------------------------------------- data table */
.tbl{width:100%;border-collapse:collapse;font-size:12px}
.tbl th{
  text-align:left;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--faint);font-weight:500;padding:0 9px 7px;border-bottom:1px solid var(--line);
}
.tbl td{padding:7px 9px;border-bottom:1px solid var(--hairline);vertical-align:top}
.tbl tbody tr{transition:background var(--t1)}
.tbl tbody tr:hover{background:var(--sheen-2)}
.tbl .n,.tbl .code{font-family:'Plex Mono',monospace;font-variant-numeric:tabular-nums}
.tbl .n{text-align:right}
.tbl .code{font-size:11px;color:var(--metal-text)}

/* ---------------------------------------------------------------- toasts */
.toasts{position:fixed;bottom:18px;left:50%;transform:translateX(-50%);z-index:200;display:flex;flex-direction:column;gap:8px;align-items:center;pointer-events:none}
.toast{
  display:flex;align-items:center;gap:9px;padding:10px 15px;border-radius:var(--r-md);
  font-size:12.5px;pointer-events:auto;box-shadow:var(--sh-3);
  animation:toast-in var(--t3) var(--ease-out);
}
.toast.out{animation:toast-out var(--t2) var(--ease) forwards}
@keyframes toast-in{from{opacity:0;transform:translateY(14px) scale(.96)}to{opacity:1;transform:none}}
@keyframes toast-out{to{opacity:0;transform:translateY(8px) scale(.97)}}
.toast .ic{width:16px;height:16px;flex:none}
.toast .ic svg{width:16px;height:16px;stroke-width:1.8;fill:none}
.toast.t-ok .ic{color:var(--ok)} .toast.t-bad .ic{color:var(--bad)} .toast.t-info .ic{color:var(--metal-text)}
.toast .undo{margin-left:5px;color:var(--metal-text);font-weight:500}

/* ---------------------------------------------------------------- modal */
.scrim{
  position:fixed;inset:0;z-index:150;background:var(--scrim);
  backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px);
  display:grid;place-items:center;padding:26px;
  animation:fade var(--t2) var(--ease);
}
@keyframes fade{from{opacity:0}to{opacity:1}}
.modal{
  background:var(--ink-2);border:1px solid var(--line-2);border-radius:var(--r-lg);
  box-shadow:var(--sh-4);max-width:min(94vw,880px);max-height:88vh;
  display:flex;flex-direction:column;overflow:hidden;
  animation:modal-in var(--t3) var(--ease-out);
}
@keyframes modal-in{from{opacity:0;transform:translateY(16px) scale(.985)}to{opacity:1;transform:none}}
.modal-h{display:flex;align-items:center;gap:12px;padding:15px 18px;border-bottom:1px solid var(--line)}
.modal-h h3{font-size:15px;font-weight:500;flex:1}
.modal-b{padding:18px;overflow:auto}
.modal-f{padding:13px 18px;border-top:1px solid var(--line);display:flex;gap:9px;justify-content:flex-end}

/* -------------------------------------------------------------- skeleton */
.skel{
  background:linear-gradient(90deg,var(--ink-3) 25%,var(--ink-4) 50%,var(--ink-3) 75%);
  background-size:200% 100%;animation:shimmer 1.3s infinite linear;border-radius:var(--r-xs);
}
@keyframes shimmer{from{background-position:200% 0}to{background-position:-200% 0}}

/* --------------------------------------------------- entrance choreography
   One orchestrated load, not scattered effects. */
.rise{animation:rise var(--t4) var(--ease-out) backwards}
@keyframes rise{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
.fade-in{animation:fade var(--t3) var(--ease) backwards}

/* --------------------------------------------------------------- utility */
.stack{display:flex;flex-direction:column}
.rowf{display:flex;align-items:center}
.gap4{gap:4px}.gap6{gap:6px}.gap8{gap:8px}.gap10{gap:10px}.gap14{gap:14px}
.mt6{margin-top:6px}.mt10{margin-top:10px}.mt16{margin-top:16px}
.grow{flex:1;min-width:0}
.center{display:grid;place-items:center}
.hr{height:1px;background:var(--line);margin:14px 0}

/* --------------------------------------------------------------- a11y/motion */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}
}

/* ------------------------------------------------------------- responsive
   Desktop first (this is a workstation tool), tablet supported, mobile usable. */
@media (max-width:1280px){ :root{--inspector:300px} }
@media (max-width:1080px){
  :root{--rail:66px;--inspector:0px}
  .body{grid-template-columns:var(--rail) 1fr}
  .inspector{
    position:fixed;top:var(--topbar);right:0;bottom:0;width:320px;z-index:60;
    transform:translateX(100%);transition:transform var(--t3) var(--ease);
    box-shadow:var(--sh-4);
  }
  .inspector.open{transform:none}
  .step .lbl{display:none}
}
@media (max-width:720px){
  :root{--deck:240px}
  .body{grid-template-columns:0 1fr}
  .rail{position:fixed;left:0;top:var(--topbar);bottom:0;width:66px;transform:translateX(-100%);
        transition:transform var(--t3) var(--ease);z-index:60}
  .rail.open{transform:none}
  .opt{width:142px}
  .tb-field.hide-sm{display:none}
}

/* ================================================= engineering checks panel
   The panel answers "what did you check?", so the ledger rows carry the value
   the rule was tested against and are weighted quieter than the score. */
.checks{ display:flex; flex-direction:column; gap:10px }

.ck-head{ display:flex; align-items:center; gap:12px }
.ck-ring{ width:44px; height:44px; flex:none }
.ck-ring svg{ width:44px; height:44px; transform:rotate(-90deg) }
.ck-ring circle{ fill:none; stroke-width:3; stroke-linecap:round }
.ck-ring .trk{ stroke:var(--line) }
.ck-ring .bar{ stroke:var(--ok); transition:stroke var(--t3) var(--ease) }
.ck-ring.s-warn .bar{ stroke:var(--warn) }
.ck-ring.s-err  .bar{ stroke:var(--bad) }

.ck-score{ display:flex; align-items:baseline; gap:4px }
.ck-score .n{ font-family:'Plex Mono',monospace; font-size:23px; font-weight:500; letter-spacing:-.02em }
.ck-score .of{ font-size:11.5px; color:var(--faint) }
.ck-sub{ display:flex; align-items:center; gap:6px; font-size:11px; margin-top:2px }
.ck-sub .dot{ color:var(--line-2) }
.ck-sub .bad{ color:var(--bad); font-weight:500 }
.ck-sub .warn{ color:var(--warn); font-weight:500 }

.ck-status{
  font-size:10.5px; letter-spacing:.14em; text-transform:uppercase; font-weight:500;
  padding:7px 10px; border-radius:var(--r-sm); border:1px solid var(--line);
  background:var(--ink-3); color:var(--dim);
}
.ck-status.s-ok{ color:var(--ok); border-color:color-mix(in srgb,var(--ok) 34%,transparent); background:var(--ok-bg) }
.ck-status.s-warn{ color:var(--warn); border-color:color-mix(in srgb,var(--warn) 34%,transparent); background:var(--warn-bg) }
.ck-status.s-err{ color:var(--bad); border-color:color-mix(in srgb,var(--bad) 38%,transparent); background:var(--bad-bg) }

.ck-list{ display:flex; flex-direction:column; gap:1px }
.ck{
  display:flex; gap:8px; padding:7px 8px; border-radius:var(--r-sm);
  opacity:0; transform:translateY(-3px);
  transition:opacity var(--t3) var(--ease), transform var(--t3) var(--ease-out), background var(--t2);
}
.ck.in{ opacity:1; transform:none }
.ck:hover{ background:var(--sheen-2) }
.ck-mark{ width:14px; height:14px; flex:none; margin-top:2px; color:var(--ok) }
.ck-mark svg{ width:14px; height:14px; stroke-width:2; fill:none }
.ck-error .ck-mark{ color:var(--bad) }
.ck-warn  .ck-mark{ color:var(--warn) }
.ck-info  .ck-mark{ color:var(--info) }
.ck-t{ display:flex; align-items:baseline; gap:6px }
.ck-name{ font-size:11.5px; font-weight:500 }
.ck-id{
  font-family:'Plex Mono',monospace; font-size:9px; color:var(--faint);
  letter-spacing:.05em; margin-left:auto; flex:none; opacity:.8;
}
.ck-d{
  font-family:'Plex Mono',monospace; font-size:10.5px; color:var(--dim);
  line-height:1.45; margin-top:1px; word-break:break-word;
}
.ck-error .ck-d, .ck-warn .ck-d{ font-family:'Plex Sans',sans-serif; font-size:11px }
.ck .fix{
  margin-top:5px; font-size:11px; color:var(--metal-text);
  display:inline-flex; align-items:center; gap:5px; white-space:nowrap; align-self:flex-start;
}
.ck .fix svg{ width:13px; height:13px; flex:none; stroke-width:1.7; fill:none }
.ck .fix b{ font-weight:500; text-transform:capitalize }
.ck .fix:hover{ text-decoration:underline }

/* measured row unit */
.row .v .u{ font-size:10.5px; color:var(--faint); margin-left:3px }

/* Motion is a signal, not a tax. */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.01ms!important; animation-iteration-count:1!important;
    transition-duration:.01ms!important; scroll-behavior:auto!important;
  }
  .ck{ opacity:1; transform:none }
}

/* A laptop is the common case in a meeting room, so the deck gives up more of
   its height as the window gets shorter rather than crowding the drawing. */
@media (max-height:900px){
  :root{ --deck:186px }
  .deck{ max-height:min(42vh, 380px) }
}
@media (max-height:740px){
  :root{ --deck:158px }
  .deck{ max-height:min(38vh, 300px) }
}

/* ===================================================== micro-interactions
   Weighted, not bouncy. Buttons compress rather than scale up; cards lift by a
   couple of pixels. A control that overshoots reads as a toy, and this is an
   instrument an engineer is meant to work in all day. */
.btn, .icon-btn, .opt, .sw, .out, .deliv-chip, .step{ will-change:transform }
.btn:active, .icon-btn:active, .seg button:active{ transform:translateY(1px) scale(.985) }
.btn-metal{ transition:background var(--t2) var(--ease), transform var(--t1) var(--ease), box-shadow var(--t2) }
.btn:disabled, .icon-btn:disabled{ opacity:.42; pointer-events:none }

/* The finish swatch is the signature control — it deserves the most motion. */
.sw .chip{ transition:transform var(--t2) var(--ease-out), box-shadow var(--t2) var(--ease) }
.sw:hover .chip{ transform:translateY(-2px) scale(1.05) }
.sw:active .chip{ transform:translateY(0) scale(.99) }

/* Focus is visible and shares the accent, so keyboard use never feels second-class. */
:focus-visible{ outline:2px solid var(--metal); outline-offset:2px }
.opt:focus-visible, .sw:focus-visible{ outline-offset:3px }

/* Deck steps cross-fade rather than cut. */
.deck-body > *{ animation:deck-in var(--t3) var(--ease-out) backwards }
@keyframes deck-in{ from{ opacity:0; transform:translateY(4px) } to{ opacity:1; transform:none } }

/* The deck is height-capped, so tall steps have content below the fold. A soft
   fade at the lower edge is the cue that more exists — shown only while there
   is actually more to reach, never as permanent decoration. */
.deck::after{
  content:''; position:absolute; left:0; right:0; bottom:0; height:36px;
  pointer-events:none; opacity:0; transition:opacity var(--t2) var(--ease);
  background:linear-gradient(transparent, var(--ink-2));
}
.deck.more::after{ opacity:1 }

/* ------------------------------------------------------------- status bar */
.statusbar{
  flex:none; display:flex; align-items:center; gap:16px;
  height:30px; padding:0 16px;
  background:var(--ink-2); border-top:1px solid var(--line);
  font-size:11px; color:var(--faint);
}
.sb-brand{ display:flex; align-items:baseline; gap:8px; flex:none }
.sb-brand b{ color:var(--text); font-weight:600; letter-spacing:.02em }
.sb-tag{ color:var(--faint); font-size:10.5px }
.sb-mid{ display:flex; align-items:center; gap:7px; margin-left:auto; margin-right:auto }
.sb-dot{ width:6px; height:6px; border-radius:50%; background:var(--ok); flex:none }
.sb-mid.busy .sb-dot{ background:var(--warn); animation:pulse 1s infinite }
.sb-right{ display:flex; align-items:center; gap:14px; flex:none }
.sb-item{ font-family:'Plex Mono',monospace; font-size:10.5px; letter-spacing:.04em }
@media (max-width:860px){ .sb-tag, .sb-item:first-child{ display:none } }

/* The top bar now carries identity and actions; the left rail owns the workflow.
   Drawing number and validation state live in the status bar and the project
   code is editable in step 01, so those fields stay hidden here rather than
   being shown twice. The elements remain in the DOM — the topbar sync still
   writes to them. */
.topbar .tb-field, .topbar .tb-sep{ display:none }
.topbar .mark{ flex:none }

/* ======================================================= appliance library
   The right-column library from the approved specification. Cards are sized so
   the thumbnail, name and mounting each get their own line — the mockup's
   proportions — and the grid stays two-up so nothing is ever squeezed below a
   legible width in a 344 px inspector. */
.lib-grid{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px }
.lib{
  position:relative; display:flex; flex-direction:column; gap:2px;
  padding:10px 10px 9px; text-align:left;
  background:var(--ink-3); border:1px solid var(--line); border-radius:var(--r-md);
  opacity:0; transform:translateY(4px);
  transition:opacity var(--t3) var(--ease), transform var(--t3) var(--ease-out),
             border-color var(--t2), background var(--t2);
}
.lib.in{ opacity:1; transform:none }
.lib:hover{ background:var(--ink-4); border-color:var(--line-2) }
.lib:active{ transform:translateY(1px) scale(.99) }
.lib.on{ border-color:var(--metal); background:var(--metal-glow) }
.lib-th{ display:block; height:58px; margin-bottom:4px }
.lib-th svg.thumb{ width:100%; height:100%; overflow:visible }
.thumb .f{ fill:var(--ink-1); stroke:var(--faint); stroke-width:1; vector-effect:non-scaling-stroke }
.thumb .t{ fill:var(--ink-4) }
.thumb .r{ fill:var(--ink-0) }
.lib.on .thumb .f{ stroke:var(--metal-text) }
.lib-n{ font-size:11.5px; font-weight:500; line-height:1.3 }
.lib-m{ font-size:10px; color:var(--faint); letter-spacing:.02em }
.lib-tick{
  position:absolute; top:8px; right:8px; width:16px; height:16px; border-radius:50%;
  background:var(--metal); display:grid; place-items:center;
}
.lib-tick svg{ width:9px; height:9px; stroke:var(--metal-ink); stroke-width:2.8; fill:none }
/* a quiet mark that some values await confirmation — never a warning */
.lib-pend{
  position:absolute; top:11px; left:10px; width:5px; height:5px; border-radius:50%;
  background:var(--warn); opacity:.75;
}
.lib.unbound{ opacity:.9 }
.lib.unbound.in{ opacity:.9 }

/* ------------------------------------------------------- property panel */
.props{ display:flex; flex-direction:column; gap:1px }
/* Thumbnail beside its values, as the approved layout has it. Size BOTH axes:
   width:auto on an inline <svg> stretches to the container and derives height
   from the viewBox — the defect that once painted the option-card figures over
   their own titles, and that recurred here. */
.prop-head{ display:grid; grid-template-columns:96px minmax(0,1fr); gap:12px; align-items:start }
.prop-th{
  height:116px; display:block; background:var(--ink-1);
  border:1px solid var(--line); border-radius:var(--r-sm); padding:8px;
}
.prop-th svg.thumb{ display:block; width:100%; height:100%; overflow:visible }
.prop-rows{ display:flex; flex-direction:column; min-width:0 }
.basis{ font-family:'Plex Sans',sans-serif; font-size:10px; color:var(--faint) }
@media (max-width:1180px){ .prop-head{ grid-template-columns:minmax(0,1fr) } .prop-th{ height:90px } }
/* A row that cannot fit wraps as a whole — label, then value beneath it —
   rather than breaking "Part number" and "SR-BIN-SAN" mid-token, which is how
   it first rendered in a 344 px inspector. */
.prop{ display:flex; align-items:baseline; gap:10px; padding:4px 0;
       border-bottom:1px solid var(--hairline); font-size:11.5px; flex-wrap:wrap }
.prop:last-of-type{ border-bottom:0 }
.prop .k{ color:var(--faint); white-space:nowrap }
.prop .v{ margin-left:auto; font-family:'Plex Mono',monospace; font-size:11.5px;
          display:inline-flex; align-items:baseline; gap:5px; white-space:nowrap }
.prop .u{ color:var(--faint); font-size:10.5px }
.prop.pending .v{ color:var(--dim) }
.pend-tag{
  font-family:'Plex Sans',sans-serif; font-size:9px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--warn);
  background:var(--warn-bg); border-radius:3px; padding:1px 5px;
}
.prop-status{
  display:flex; align-items:center; gap:7px; margin-top:12px;
  padding:8px 10px; border-radius:var(--r-sm); font-size:11.5px;
  border:1px solid var(--line); background:var(--ink-3); color:var(--dim);
}
.prop-status .ic{ width:14px; height:14px; flex:none }
.prop-status .ic svg{ width:14px; height:14px; stroke-width:2; fill:none }
.prop-status.ok{ color:var(--ok); border-color:color-mix(in srgb,var(--ok) 34%,transparent); background:var(--ok-bg) }
.prop-status.off{ color:var(--faint) }
.prop-note{ font-size:11px; color:var(--faint); margin-top:8px; line-height:1.5 }
.prop-src{
  margin-top:10px; padding:9px 10px; border-radius:var(--r-sm);
  border:1px solid color-mix(in srgb,var(--warn) 26%,transparent);
  background:var(--warn-bg); font-size:10.5px; line-height:1.55; color:var(--dim);
}
.prop-src b{ display:block; color:var(--warn); font-size:9.5px; letter-spacing:.12em;
             text-transform:uppercase; margin-bottom:3px; font-weight:600 }

/* ========================================================== halo comparison
   A seam across the viewport rather than a modal: the client keeps their camera,
   their run and their finish, and sweeps the difference. Chrome is kept to a
   line, a grip and two labels — the product is the content. */
.wipe{ position:absolute; inset:0; z-index:7; --at:50%; pointer-events:none }
.wipe-line{
  position:absolute; top:0; bottom:0; left:var(--at); width:1px;
  background:rgba(255,255,255,.9); box-shadow:0 0 0 1px rgba(24,34,48,.18);
  pointer-events:auto; cursor:ew-resize;
}
.wipe-grip{
  position:absolute; top:50%; left:var(--at); transform:translate(-50%,-50%);
  width:36px; height:36px; border-radius:50%; pointer-events:auto; cursor:ew-resize;
  background:var(--metal); color:var(--metal-ink);
  display:grid; grid-auto-flow:column; place-items:center; gap:0;
  box-shadow:0 2px 10px -2px rgba(2,6,16,.5), 0 0 0 1px var(--metal-lo);
  transition:transform var(--t2) var(--ease), box-shadow var(--t2);
}
.wipe-grip svg{ width:13px; height:13px; stroke-width:2.2; fill:none; margin:0 -3px }
.wipe-grip:hover{ transform:translate(-50%,-50%) scale(1.06) }
.wipe.dragging .wipe-grip{ transform:translate(-50%,-50%) scale(.97) }
.wipe-grip:focus-visible{ outline:2px solid var(--metal-hi); outline-offset:3px }

.wipe-lab{
  position:absolute; top:14px; font-family:'Plex Mono',monospace; font-size:10px;
  letter-spacing:.16em; text-transform:uppercase; padding:4px 9px; border-radius:var(--r-xs);
  background:var(--vp-glass); border:1px solid var(--vp-glass-line); color:var(--vp-ink);
}
.wipe-lab.l{ left:14px }
.wipe-lab.r{ right:14px; color:#8A6D12; border-color:rgba(245,158,11,.4) }
.wipe-note{
  position:absolute; left:50%; bottom:12px; transform:translateX(-50%);
  font-size:10px; letter-spacing:.1em; text-transform:uppercase; color:var(--vp-sub);
  background:var(--vp-glass); border:1px solid var(--vp-glass-line);
  padding:4px 10px; border-radius:var(--r-xs); white-space:nowrap;
}
/* Reduced motion: an A/B toggle, no sweep. */
.wipe.ab .wipe-line{ display:none }
.wipe.ab .wipe-grip{ left:50%; border-radius:var(--r-sm); width:auto; padding:0 12px; height:30px }

/* ==================================================== engineering workspace
   A document dock, not a drawer. The tab strip puts the step's options and the
   model's documents on the same row because they are peers: one edits the
   model, the others show it. Nothing here ever covers the viewport. */
.dock-bar{
  display:flex; align-items:stretch; gap:10px; min-height:36px;
  padding-left:14px; padding-right:8px;
  background:var(--ink-1); border-bottom:1px solid var(--line);
}
.dock-tabs{ display:flex; align-items:stretch; gap:2px; flex:1; min-width:0; overflow-x:auto; scrollbar-width:none }
.dock-tabs::-webkit-scrollbar{ height:0 }
.dock-sep{ align-self:center; width:1px; height:16px; background:var(--line); margin:0 6px; flex:none }

.dtab{
  display:flex; align-items:center; gap:6px; flex:none;
  padding:0 11px; border:0; background:none; cursor:pointer;
  /* --dim, not --faint: at 12 px these are normal-size text and --faint is
     4.34:1 on the light surface. The active tab is still unmistakable — full
     --text, plus the underline. */
  font-size:12px; color:var(--dim); white-space:nowrap;
  border-bottom:2px solid transparent;
  transition:color var(--t2) var(--ease), border-color var(--t2) var(--ease), background var(--t2) var(--ease);
}
.dtab svg{ width:13px; height:13px }
.dtab:hover{ color:var(--text) }
.dtab.on{ color:var(--text); border-bottom-color:var(--metal) }
.dtab.opts.on{ border-bottom-color:var(--line-2) }
.dock-acts{ display:flex; align-items:center; gap:2px; flex:none }
.dock-acts .icon-btn:disabled{ opacity:.3; cursor:default }

.doc{ flex:1; min-height:0; display:flex; flex-direction:column }
.doc-head{
  display:flex; align-items:center; gap:12px; padding:9px 16px 8px;
  border-bottom:1px solid var(--line);
}
.doc-head .dt{ font-size:13.5px; font-weight:500; letter-spacing:.01em }
.doc-head .ds{ margin-top:1px }
.doc-body{ flex:1; min-height:0; position:relative; overflow:auto; padding:14px 16px 16px }
/* the docked elevation is the live instance, absolutely filling the pane */
.doc[data-doc="elev"] .doc-body{ padding:0; overflow:hidden }
.doc[data-doc="elev"] .doc-body > .svg-stage{ position:absolute; inset:0; width:100%; height:100% }

.doc-surface{ min-height:100% }
.doc-surface.enter{ animation:doc-in var(--t3) var(--ease-out) backwards }
@keyframes doc-in{ from{ opacity:0; transform:translateY(6px) } to{ opacity:1; transform:none } }

.doc-grid{
  display:grid; gap:14px;
  grid-template-columns:repeat(auto-fill, minmax(260px, 1fr));
  align-items:start;
}
.doc-cols{ display:flex; gap:16px; align-items:flex-start }
.doc-side{ flex:0 0 258px; display:flex; flex-direction:column; gap:12px }
@media (max-width:900px){ .doc-cols{ flex-direction:column } .doc-side{ flex:1 1 auto; width:100% } }

.tbl-wrap{ min-width:0; overflow-x:auto }
.tbl-doc{ width:100% }
.tbl-doc td.u{ font-size:11px }

/* A pending field states the gap; it does not fill it with a plausible number. */
.prow{ display:flex; align-items:center; gap:10px; padding:5px 0; border-bottom:1px solid var(--hairline) }
.prow:last-child{ border-bottom:0 }
.prow .k{ font-size:12px; color:var(--faint); flex:1; min-width:0 }
.prow.pending .k{ color:var(--dim) }

.doc-note{ display:flex; align-items:flex-start; gap:7px; margin-top:12px; max-width:78ch }
.doc-note .ic svg{ width:13px; height:13px; display:block }

/* The sheet at issue size — real A3 proportion, on paper, on a desk. */
.paper-wrap{ display:flex; justify-content:center }
.paper{
  width:100%; max-width:1180px; aspect-ratio:420 / 297;
  background:#F4F2EC; border:1px solid var(--line-2); border-radius:2px;
  box-shadow:0 1px 2px rgba(8,14,24,.10), 0 10px 26px rgba(8,14,24,.10);
  overflow:hidden;
}
.paper.issue{ box-shadow:0 1px 2px rgba(8,14,24,.12), 0 18px 44px rgba(8,14,24,.16) }
.paper > svg{ display:block; width:100%; height:100% }
.mb6{ margin-bottom:6px }

/* A spec row in the inspector is 344 px wide with short values; in a document
   grid column it can meet "DWS panel — BMS, energy sensor, refill, cleaner
   mode". `.row .k` is flex:1 min-width:0, so the LABEL collapsed to nothing
   rather than the value wrapping — measured at 40 px of text in 0 px of box.
   In documents the row wraps as a whole instead, label intact. */
.doc-body .row{ flex-wrap:wrap; align-items:baseline }
.doc-body .row .k{ flex:1 1 auto; min-width:40% }
.doc-body .row .v{ flex:1 1 auto; min-width:0; overflow-wrap:anywhere }
.doc-body .doc-grid{ grid-template-columns:repeat(auto-fill, minmax(300px, 1fr)) }

/* A sentence in a value slot. Full width, left aligned, body face — the mono
   right-aligned treatment is for measurements. */
.row-note{ flex-direction:column; align-items:stretch; gap:3px }
.row-note .nv{ font-size:12px; color:var(--dim); line-height:1.5; max-width:60ch }

/* An unfilled field. Dashed outline, no fill, no semantic colour: this is a
   blank awaiting Dolphin's content, not a warning about the configuration. */
.pend-chip{
  font-size:10px; letter-spacing:.09em; text-transform:uppercase;
  color:var(--dim); border:1px dashed var(--line-2); border-radius:3px;
  padding:2.5px 8px; white-space:nowrap;
}

/* At a short viewport the centred nameplate prints straight through the
   drawing. The dock header names the document and the readout pill carries the
   live numbers, so the nameplate is the one that yields. */
.stage.short .nameplate{ opacity:0; pointer-events:none }
.nameplate{ transition:opacity var(--t2) var(--ease) }

/* Reserve the nameplate's band so the drawing cannot run under it.
   At most run lengths the elevation is HEIGHT-constrained — it fills the
   viewport top to bottom — so the type printed through the bulkhead, and on a
   taller mirror it would cross the mirrors themselves. Tuning the drawing's own
   top padding cannot fix this: that padding is a fraction of the viewBox, so it
   only clears a fixed-pixel overlay at one viewport height.
   Insetting the stage is exact at every height. It must be applied to the SVG
   and not to .svg-host — the SVG is position:absolute, so it resolves against
   the host's PADDING box and padding would move nothing. */
.viewport{ --np-band:58px }
.stage:not(.short) .viewport .svg-stage{ top:var(--np-band); height:calc(100% - var(--np-band)) }

/* ================================================================== scene ==
   Presentation settings, anchored in the viewport because that is what they
   affect. Glass over the render like the rest of the viewport chrome, so it
   reads as an overlay on the view rather than as another engineering panel. */
.scene-panel{
  position:absolute; top:60px; right:14px; z-index:6; width:296px; max-width:calc(100% - 28px);
  /* Nearly opaque, unlike the rest of the viewport chrome. The other glass
     panels carry one or two words; this one carries small secondary text, and
     at 0.74 alpha its legibility followed whatever was rendered behind it —
     measured on real pixels, the description text fell from 3.1:1 over the
     white wall to 1.96:1 over the black one. A control surface must read the
     same on every backdrop, and Matte Black is a backdrop this panel exists to
     select. The blur stays, so it still belongs to the viewport. */
  background:var(--vp-panel); border:1px solid var(--vp-glass-line); border-radius:8px;
  backdrop-filter:blur(18px) saturate(1.25); -webkit-backdrop-filter:blur(18px) saturate(1.25);
  box-shadow:0 2px 6px rgba(8,14,24,.10), 0 16px 40px rgba(8,14,24,.18);
  padding:11px 11px 9px; animation:sc-in var(--t2) var(--ease-out);
  max-height:calc(100% - 84px); overflow-y:auto;
}
@keyframes sc-in{ from{ opacity:0; transform:translateY(-6px) } to{ opacity:1; transform:none } }
.scene-panel .sc-head{ display:flex; align-items:center; justify-content:space-between; gap:8px }
.scene-panel .eyebrow{ color:var(--vp-label) }
.scene-panel .icon-btn.sm{ width:22px; height:22px; color:var(--vp-sub) }
.scene-panel .icon-btn.sm svg{ width:12px; height:12px }
.sc-note{ font-size:10.5px; line-height:1.45; color:var(--vp-note); margin:5px 0 10px; max-width:34ch }
.sc-lab{
  font-size:10px; letter-spacing:.12em; text-transform:uppercase;
  color:var(--vp-sub); margin-bottom:5px;
}
.sc-list{ display:flex; flex-direction:column; gap:1px }

/* A swatch row, not a colour picker: named preset, true colour, and the hex —
   the way a finishes schedule reads. */
.sc-opt{
  display:flex; align-items:center; gap:9px; width:100%; text-align:left;
  padding:6px 7px; border:1px solid transparent; border-radius:5px;
  background:none; cursor:pointer; color:var(--vp-ink);
  transition:background var(--t2) var(--ease), border-color var(--t2) var(--ease);
}
.sc-opt:hover{ background:rgba(24,34,48,.06) }
.sc-opt.on{ border-color:var(--metal); background:rgba(24,34,48,.05) }
.sc-sw{
  flex:none; width:22px; height:22px; border-radius:4px;
  border:1px solid rgba(24,34,48,.22); box-shadow:inset 0 1px 2px rgba(255,255,255,.35);
}
/* A near-black swatch needs a light edge or it reads as a hole in the panel. */
.sc-sw.dk{ border-color:rgba(255,255,255,.28); box-shadow:inset 0 1px 2px rgba(255,255,255,.10) }
.sc-tx{ flex:1; min-width:0; display:flex; flex-direction:column; gap:1px }
.sc-tx .n{ font-size:12px; line-height:1.25 }
.sc-tx .d{ font-size:10px; color:var(--vp-note); line-height:1.3 }
.sc-opt.on .n{ color:var(--metal-ink, var(--vp-ink)); font-weight:500 }
.sc-hex{
  flex:none; font-family:'Plex Mono',monospace; font-size:9.5px;
  color:var(--vp-note); letter-spacing:.02em;
}
@media (max-width:900px){ .scene-panel{ width:auto; left:14px; right:14px } .sc-hex{ display:none } }
