/* Chamfr product store — austere technical theme.
   Near-black canvas, monochrome type, a single restrained steel-blue accent used
   only on functional cues (active tab, sort arrow, slider fill, focus). No glow,
   no gradients, no decorative color. D-DIN + Roboto Mono for an instrument feel.
   Fonts + core tokens (surfaces, hairlines, text, accent, motion) come from
   /tokens.css — the ONE design system shared with the instant-quote page and
   the part builder. This sheet keeps only aliases + store-local values. */

:root {
  /* legacy aliases so product/create pages keep their token references */
  --void: #000; --black: var(--bg);
  --gray-950: #0a0b0d; --gray-900: var(--panel); --gray-850: var(--panel-2); --gray-800: var(--panel-3); --slate: #20242a;
  --offwhite: var(--fg); --muted: var(--fg-2); --faint: var(--fg-3);
  --line-strong: var(--line-3);
  --accent-2: var(--accent); --accent-dim: var(--accent-soft); --accent-glow: transparent;
  --signal: var(--accent); --signal-line: var(--accent-line); --signal-dim: var(--accent-soft);
  --shorty: var(--accent); --full: var(--fg-2); --ring: var(--fg-2);
  --c-shorty: var(--fg-2); --c-full: var(--fg-2); --c-ring: var(--fg-2); --c-custom: var(--fg-2);

  /* store-local */
  --track: .04em; --maxw: 1460px;
  /* height of a PINNED crumb (.crumb--pin). It is a layout constant, not a
     line-box outcome: the pinned viewer's sticky offset and its viewport height
     cap are both measured from below it, so it has to be a number they can name
     rather than whatever the font metrics happen to produce. 40px = the crumb's
     22px top padding + its 17.25px line box, rounded up. */
  --crumb-h: 40px;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html { scroll-behavior: smooth; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font); background: var(--bg); color: var(--fg);
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; line-height: 1.5; min-height: 100vh;
}
/* solid accent, dark ink — the old --accent-soft was 10% alpha, invisible
   over dark inputs (operator 2026-09-03: highlight "doesn't show") */
::selection { background: var(--accent); color: #0b0c0e; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }

/* ---- nav ---- */
.nav {
  position: sticky; top: 0; z-index: 40; height: var(--nav-h);
  display: flex; align-items: center; gap: 28px;
  padding: 0 32px; background: rgba(6,7,8,.86); backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}
.nav__brand { font-weight: 700; letter-spacing: .2em; font-size: 13.5px; text-transform: uppercase; }
.nav__brand span { color: var(--fg-3); font-weight: 400; letter-spacing: .14em; }
.nav__spacer { flex: 1; }
.nav__link { position: relative; color: var(--fg-2); font-size: 11.5px; letter-spacing: .12em; text-transform: uppercase; transition: color var(--dur); }
.nav__link:hover, .nav__link.is-active { color: var(--fg); }
.nav__link::after { content: ""; position: absolute; left: 0; right: 0; bottom: -5px; height: 1px; background: var(--accent-line); transform: scaleX(0); transform-origin: left; transition: transform var(--dur) var(--ease); }
.nav__link:hover::after, .nav__link.is-active::after { transform: scaleX(1); }
/* account nav: the signed-in name, a divider, then Sign out — clearly separated */
#navAcct { display: inline-flex; align-items: center; gap: 20px; }
.nav__name { color: var(--fg); font-size: 11.5px; letter-spacing: .12em; text-transform: uppercase; }
.nav__sep { width: 1px; height: 13px; background: var(--line-2); }

.btn {
  display: inline-flex; align-items: center; gap: 8px; height: 38px; padding: 0 18px;
  border: 1px solid var(--line-2); background: transparent; color: var(--fg);
  font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase; border-radius: var(--r-sm);
  transition: background var(--dur) var(--ease), color var(--dur), border-color var(--dur);
}
.btn:hover { background: rgba(255,255,255,.05); border-color: var(--line-3); }
.btn--accent { border-color: transparent; background: var(--fg); color: #000; font-weight: 700; }
.btn--accent:hover { background: #fff; color: #000; }
.btn--solid { background: var(--fg); color: #000; border-color: var(--fg); }
.btn--sm { height: 32px; padding: 0 14px; font-size: 10.5px; }
.btn:disabled { opacity: .4; pointer-events: none; }

/* ---- shell ---- */
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 32px; }

/* ---- store index ---- */
/* the table below is still the page's main focus, but the diagram earns its
   space (operator, 2026-07-28: bigger) */
.hero { display: grid; grid-template-columns: minmax(0, 1fr) minmax(340px, 48%); gap: 32px; align-items: center; padding: 16px 0 10px; margin-bottom: 30px; }
.hero__text { min-width: 0; }
/* a static SVG technical diagram of the active part type in the header's right
   blank space — interrupted-spiral hypotube or pull ring (no WebGL) */
/* 420, not 330 (operator 2026-08-29): a technical drawing delivered into
   a 576x330 box is a postage stamp — the render downscales 0.31x, so any
   type designed at render scale arrives unreadable. Taller box + type
   designed FOR this size (see hero_anatomy4.py) is the pair that fixes it. */
.hero__media { position: relative; height: 420px; }
.hero__media svg { width: 100%; height: 100%; display: block; }
/* photoreal hero diagram: a catalog Blender render (media/catalog/herodiag)
   with the engineering callouts drawn over it. Image and overlay share the
   render's own pixel space (object-fit: cover == preserveAspectRatio slice,
   same 1920x1080 frame), so the leader dots sit on the features at any
   viewport. The mask feathers the render's ground into the page so the still
   sits on the panel with no visible rectangle. */
.hero-render { position: absolute; inset: 0; }
.hero-render img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  /* the renders' void is remapped to the page black (dev/tools/herodiag_bg.py)
     so there is no rectangle to hide — this gentle fade only feathers the
     part's own light falloff where it meets the frame edge */
  /* the feather used to start at 62% of a 74% ellipse, which ghosted every
     callout living in the outer third — the renders' void is already page
     black, so it only needs to kiss the very edge */
  -webkit-mask-image: radial-gradient(ellipse 96% 96% at 50% 50%, #000 86%, transparent 100%);
          mask-image: radial-gradient(ellipse 96% 96% at 50% 50%, #000 86%, transparent 100%); }
.hero-render svg { position: absolute; inset: 0; }
@media (max-width: 940px) { .hero { grid-template-columns: 1fr; } .hero__media { display: none; } }
.hero__eyebrow { display: inline-flex; align-items: center; gap: 11px; color: var(--fg-3); font-size: 11px; letter-spacing: .24em; text-transform: uppercase; }
.hero__eyebrow::before { content: ""; width: 26px; height: 1px; background: var(--line-3); }
.hero__title { font-size: clamp(28px, 3.8vw, 44px); font-weight: 700; letter-spacing: .02em; line-height: 1.06; text-transform: uppercase; margin: 0 0 6px; }
.hero__title .accent { color: var(--accent); display: inline-block; will-change: clip-path; }
.hero__sub a { color: var(--accent); text-decoration: none; border-bottom: 1px solid var(--accent-line); padding-bottom: 1px; transition: color var(--dur), border-color var(--dur); }
.hero__sub a:hover { color: var(--fg); border-color: var(--fg); }
.hero__sub { color: var(--fg-2); max-width: 600px; font-size: 14.5px;
  transition: opacity .175s var(--ease), transform .175s var(--ease); }  /* matches the arc's per-phase T */
.hero__sub.is-swapping { opacity: 0; transform: translateY(6px); }
@media (prefers-reduced-motion: reduce) { .hero__sub { transition: none; } }
/* catalog: the subtitle rides the hero's right column (the scrapped
   diagram's slot), set off by an accent hairline (operator 2026-08-31) */
.hero > .hero__sub {
  align-self: center;
  justify-self: end;
  margin: 0;
  padding-left: 22px;
  border-left: 2px solid var(--accent-line);
  max-width: 680px;
  font-size: 15px;
}
@media (max-width: 940px) {
  .hero > .hero__sub { justify-self: start; border-left: 0; padding-left: 0; margin-top: 10px; }
}

/* laser-cut motif under the section header — an unrolled helical kerf (two cut
   walls + a lead-in pierce) that re-"cuts" itself in when you switch category.
   Sized to the title width in JS; muted steel-blue, crisp, no glow. */
.hero__cut { height: 26px; margin: 2px 0 10px; pointer-events: none; position: relative; }
.hero__cut svg { display: block; overflow: visible; }
/* the scene is TWO stacked svgs (drawn by the pages' drawHeroCut): the kerf +
   its blurred glow are static once traced, the torch/sparks/embers animate.
   In one svg every ember frame re-ran the blur and re-rastered the whole
   strip — the main thread sat 65-95% busy for ~9s on every page load (perf
   audit 2026-08-07). The animated layer is absolute over the static one and
   composited on its own (will-change), so the blur never re-runs. */
.hero__cut .cut-anim { position: absolute; left: 0; top: 0; will-change: transform; }
/* the kerf: a hot-gradient cut line — cooled steel-blue at the entry pierce,
   incandescent white at the cutting head — over a blurred molten-glow underlay */
.hero__cut .cut-kerf { fill: none; stroke: url(#kerfGrad); stroke-width: 1.8; stroke-linecap: round; stroke-dashoffset: 0; }
.hero__cut .cut-kerf-glow { fill: none; stroke: var(--accent); stroke-width: 4.5; stroke-linecap: round; opacity: .22; filter: blur(3px); stroke-dashoffset: 0; }
.hero__cut .cut-pierce { fill: url(#hotGrad); opacity: .7; }   /* soft entry glow, not a hard dot */
.hero__cut .kg-cool { stop-color: var(--accent); stop-opacity: .30; }
.hero__cut .kg-mid  { stop-color: var(--accent); stop-opacity: .9; }
.hero__cut .kg-hot  { stop-color: #eaf3ff; }
.hero__cut .hot-0 { stop-color: #fff; }
.hero__cut .hot-1 { stop-color: #dcecff; }
.hero__cut .hot-2 { stop-color: var(--accent); stop-opacity: .38; }
.hero__cut .hot-3 { stop-color: var(--accent); stop-opacity: 0; }
/* the cutting head: a white-hot core in a soft blue bloom + forward sparks,
   parked at the end of the finished cut (rides the path so it can sweep) */
.hero__cut .cut-torch { offset-distance: 100%; offset-rotate: 0deg; }
.hero__cut .cut-torch-glow { fill: url(#hotGrad); }
.hero__cut .cut-torch-core { fill: #eaf3ff; opacity: .8; }   /* smaller, softer than a hard white pixel */
/* spark streaks + embers are init-only: streaks fade once the cut is done,
   embers fly a few times and go out. The torch glow/core lighting stays. */
.hero__cut .cut-spark { stroke: #dcecff; stroke-width: 1; stroke-linecap: round; opacity: .55;
  animation: sparkFade .5s ease 2.4s forwards; }
@keyframes sparkFade { to { opacity: 0; } }
/* hero embers live on a tiny CANVAS (kerf_embers.js), not as svg circles or
   DOM particles: ANY animated DOM node ticks style-recalc + PrePaint + Commit
   across the whole page every frame — on the 529-row raw-tubing table four
   flying dots cost ~half a core for their entire flight (measured 2026-08-07,
   svg / div / WAAPI all alike). A canvas redraw is a texture update with no
   tree walks. Same dots, same flights; the container rides the kerf path
   exactly like the torch so the spray never detaches from the head. */
.hero__cut .cut-embers { position: absolute; left: 0; top: 0; width: 0; height: 0;
  offset-rotate: 0deg; offset-distance: 100%; }
/* trace-in: the laser draws the kerf while the cutting head sweeps along it */
.hero__cut.is-cutting .cut-kerf, .hero__cut.is-cutting .cut-kerf-glow { animation: heroCutIn .8s var(--ease) both; }
.hero__cut.is-cutting .cut-torch, .hero__cut.is-cutting .cut-embers { animation: heroTorch .8s var(--ease) both; }
@keyframes heroCutIn { from { stroke-dashoffset: var(--len); } to { stroke-dashoffset: 0; } }
@keyframes heroTorch { from { offset-distance: 0%; } to { offset-distance: 100%; } }
@media (prefers-reduced-motion: reduce) {
  .hero__cut.is-cutting .cut-kerf, .hero__cut.is-cutting .cut-kerf-glow, .hero__cut.is-cutting .cut-torch { animation: none; }
}


/* primary category switch — underline tabs */
.tabs--top { display: flex; gap: 30px; margin: 14px 0 26px; border-bottom: 1px solid var(--line); }
.tab {
  border: none; background: none; color: var(--fg-3); padding: 0 0 13px; margin-bottom: -1px;
  font-size: 12px; letter-spacing: .13em; text-transform: uppercase;
  display: inline-flex; align-items: center; gap: 9px; border-bottom: 2px solid transparent;
  transition: color var(--dur), border-color var(--dur);
}
.tab:hover { color: var(--fg-2); }
.tab.is-active { color: var(--fg); border-bottom-color: var(--accent); }
.tab__n { font-family: var(--mono); font-size: 11px; color: var(--fg-3); }
.tab.is-active .tab__n { color: var(--accent); }

/* search */
.search {
  width: 100%; height: 38px; padding: 0 14px 0 36px; background: var(--panel-2); border: 1px solid var(--line-2);
  color: var(--fg); border-radius: var(--r-sm); font-family: var(--font); font-size: 13px; letter-spacing: .01em;
  transition: border-color var(--dur), background var(--dur);
}
.search::placeholder { color: var(--fg-3); }
.search:focus { outline: none; border-color: var(--accent-line); background-color: var(--panel-3); }
.searchwrap { position: relative; }
.searchwrap svg { position: absolute; left: 13px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; color: var(--fg-3); pointer-events: none; transition: color var(--dur); }
.searchwrap:focus-within svg { color: var(--fg-2); }

/* sidebar filters + table layout */
.catalog-layout { display: grid; grid-template-columns: 244px minmax(0, 1fr); gap: 40px; align-items: start; padding-bottom: 90px; }
.filters { position: sticky; top: 20px; display: flex; flex-direction: column; gap: 22px;
  /* the 44px slot mirrors .table-meta's height so the search bar starts
     level with the table's header row on every tab; the sub-category
     picker floats inside the slot (operator 2026-08-31) */
  padding-top: 44px; }
.filters .search { width: 100%; margin: 0; }
.fsec { display: flex; flex-direction: column; gap: 14px; }
.fsec--ranges { border-top: 1px solid var(--line); padding-top: 22px; }
.fsec__title { display: flex; align-items: center; justify-content: space-between; font-size: 10px; letter-spacing: .18em; text-transform: uppercase; color: var(--fg-3); }
.freset { background: none; border: none; color: var(--fg-3); font-size: 10px; letter-spacing: .1em; text-transform: uppercase; cursor: pointer; padding: 0; transition: color var(--dur); }
.freset:hover { color: var(--fg); }
/* the hypotube-only sub-filter row collapses/expands smoothly (JS-driven height)
   so the ranges below GLIDE up/down to take its space when it appears/disappears */
#subSec { display: flex; flex-direction: row; flex-wrap: wrap; gap: 8px;
  overflow: hidden; transition: height .4s var(--ease); }
@media (prefers-reduced-motion: reduce) { #subSec { transition: none; } }
/* real buttons in the Filters section (operator 2026-08-31: not clickable
   words) - same language as the unit toggle */
.subtab {
  padding: 6px 10px; border: 1px solid var(--line-2); border-radius: var(--r); background: transparent;
  color: var(--fg-3); font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase;
  cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
  transition: color var(--dur), border-color var(--dur), background var(--dur);
}
.subtab:hover { color: var(--fg-2); border-color: var(--line); }
.subtab.is-active { color: var(--fg); background: rgba(255,255,255,.07); border-color: var(--accent); }
.subtab .tab__n { font-family: var(--mono); font-size: 10px; color: var(--fg-3); }
.subtab.is-active .tab__n { color: var(--fg-2); }

/* dual-handle range filter */
.frange { display: flex; flex-direction: column; gap: 9px; padding: 2px 0 16px; }
.frange__head { display: flex; align-items: baseline; justify-content: space-between; }
.frange__head label { font-size: 11px; color: var(--fg-2); letter-spacing: .09em; text-transform: uppercase; }
.frange__head label.exp { color: var(--accent); }
.frange__unit { font-size: 10px; color: var(--fg-3); font-family: var(--mono); }
.frange.is-active .frange__head label { color: var(--fg); }
.frange__slider { position: relative; height: 20px; }
.frange__track { position: absolute; top: 9px; left: 0; right: 0; height: 2px; background: var(--line-2); border-radius: 2px; }
/* laser-cut kerf: a hot gradient over a soft molten glow (straight — no arc) */
.frange__fill { position: absolute; top: 9px; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, var(--accent), #d4e6f7);
  box-shadow: 0 0 5px rgba(127,160,204,.5), 0 0 1px rgba(212,230,247,.7); }
.frange input[type="range"] { position: absolute; top: 0; left: 0; width: 100%; height: 20px; margin: 0; background: none; pointer-events: none; -webkit-appearance: none; appearance: none; }
.frange input[type="range"]::-webkit-slider-runnable-track { background: none; border: none; }
.frange input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; pointer-events: auto; width: 13px; height: 13px; margin-top: 3.5px; border-radius: 50%; background: #fff; border: none; cursor: grab; box-shadow: 0 0 0 1px rgba(0,0,0,.5), 0 0 6px 1px rgba(127,160,204,.5); transition: box-shadow var(--dur); }
.frange input[type="range"]::-webkit-slider-thumb:hover { box-shadow: 0 0 0 1px rgba(0,0,0,.5), 0 0 0 4px var(--accent-soft), 0 0 9px 2px rgba(127,160,204,.6); }
.frange input[type="range"]::-webkit-slider-thumb:active { cursor: grabbing; }
.frange input[type="range"]::-moz-range-track { background: none; border: none; }
.frange input[type="range"]::-moz-range-thumb { pointer-events: auto; width: 13px; height: 13px; border-radius: 50%; background: #fff; border: none; cursor: grab; box-shadow: 0 0 0 1px rgba(0,0,0,.5), 0 0 6px 1px rgba(127,160,204,.5); }
.frange input.hi { z-index: 2; }
.frange__inputs { display: flex; align-items: center; gap: 8px; }
.frange__inputs input { width: 100%; min-width: 0; background: var(--panel-2); border: 1px solid var(--line-2); border-radius: var(--r-xs); color: var(--fg); font-family: var(--mono); font-size: 11.5px; padding: 6px 8px; text-align: center; transition: border-color var(--dur); }
.frange__inputs input:focus { outline: none; border-color: var(--accent-line); }
.frange__inputs input::-webkit-outer-spin-button, .frange__inputs input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.frange__inputs input[type="number"] { -moz-appearance: textfield; }
.frange__inputs span { color: var(--fg-3); font-size: 11px; }

.table-area { min-width: 0; }
.table-meta { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 0 2px 14px; }
.table-count { font-family: var(--mono); font-size: 11.5px; color: var(--fg-3); letter-spacing: .06em; text-transform: uppercase; }
.table-count b { color: var(--fg); font-weight: 400; }
.unit-toggle { display: inline-flex; border: 1px solid var(--line-2); border-radius: var(--r); overflow: hidden; }
.unit-toggle button { background: transparent; border: none; color: var(--fg-3); font-size: 11px; font-family: var(--mono); letter-spacing: .06em; padding: 6px 14px; transition: color var(--dur), background var(--dur); }
.unit-toggle button:hover { color: var(--fg-2); }
.unit-toggle button.is-active { background: rgba(255,255,255,.07); color: var(--fg); }

@media (max-width: 900px) {
  .catalog-layout { grid-template-columns: 1fr; gap: 24px; }
  /* single-column: no table header to align with */
  .filters { position: static; padding-top: 0; }
}

/* the data table */
.tablewrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--r); }
/* fixed layout + a stable min-width so the columns don't reflow when the tab's
   data changes (that resize was the biggest part of the switch "flinch") */
.ptable { width: 100%; min-width: 990px; table-layout: fixed; border-collapse: collapse; font-size: 13px; font-variant-numeric: tabular-nums; }
/* in-row add-to-cart cluster: stepper + live price + Add. The cluster IS the
   price column (one price cell); a hairline + extra padding set the commerce
   zone apart from the part's statistics. */
/* qty + price cluster hugs the right edge; the padding on the qty column is
   the visual gap separating it from the stat columns (no divider line) */
.ptable .col-qty { width: 150px; }
.ptable td.col-qty, .ptable th.col-qty { padding-left: 56px; padding-right: 12px; text-align: right; }
.ptable .col-cart { width: 132px; }
.rowqty { display: flex; align-items: center; justify-content: flex-end; gap: 9px; }
.rowcart { display: flex; align-items: center; justify-content: flex-end; gap: 9px; }
.rc-b {
  width: 22px; height: 22px; flex: none; display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 1px solid var(--line-2); border-radius: var(--r-sm);
  color: var(--fg-3); font-size: 13px; line-height: 1; padding: 0; transition: color var(--dur), border-color var(--dur);
}
.rc-b:hover { color: var(--fg); border-color: var(--accent-line); }
.rc-q { font-family: var(--mono); font-size: 12.5px; color: var(--fg); min-width: 16px; text-align: center; }
/* typeable qty (operator 2026-07-31: "allow the user to type in it"): the same
   readout, now an input; tubing rows snap what you type to stick multiples */
input.rc-q { width: 52px; background: none; border: 0; border-bottom: 1px solid transparent;
  padding: 0; outline: none; -moz-appearance: textfield; appearance: textfield; }
input.rc-q::-webkit-outer-spin-button, input.rc-q::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0; }
input.rc-q:focus { border-bottom-color: var(--accent); }
.rc-t { font-family: var(--mono); font-size: 12.5px; color: var(--fg); min-width: 56px; text-align: right; }
.rc-add {
  height: 24px; padding: 0 11px; flex: none; background: none;
  border: 1px solid var(--accent-line); border-radius: var(--r-sm);
  color: var(--accent); font-size: 10px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  transition: color var(--dur), border-color var(--dur), background var(--dur);
}
.rc-add:hover { background: var(--accent); border-color: var(--accent); color: #000; }
.rc-add.is-added { color: #3fbf6a; border-color: rgba(63, 191, 106, .45); background: none; }
.ptable thead th {
  position: sticky; top: 0; z-index: 2; background: var(--panel); text-align: left; white-space: nowrap;
  padding: 15px 25px; font-size: 10px; letter-spacing: .14em; text-transform: uppercase; color: var(--fg-3); font-weight: 400;
  border-bottom: 1px solid var(--line-2); cursor: pointer; user-select: none; transition: color var(--dur);
}
.ptable thead th:first-child { padding-left: 24px; }
.ptable thead th:last-child { padding-right: 24px; }
.ptable thead th.num { text-align: right; }
.ptable thead th:hover, .ptable thead th.sorted { color: var(--fg); }
.ptable thead th.exp::after {
  content: "EXP"; font-family: var(--mono); font-size: 8px; letter-spacing: .1em; color: var(--fg-3);
  border: 1px solid var(--line-2); border-radius: 2px; padding: 1px 3px; margin-left: 7px; vertical-align: middle;
}
.ptable thead th.exp.sorted::after, .ptable thead th.exp:hover::after { color: var(--accent); border-color: var(--accent-line); }
.ptable thead th .arrow { color: var(--accent); font-size: 9px; }

/* NB: content-visibility / contain do NOT work here — layout containment does
   not apply to internal table elements (display: table-row), so it is a no-op on
   these rows (measured: no change at all on the 529-row Raw Tubing table). The
   fix for that tab's switch cost is chunked rendering in catalog.html. */
.ptable tbody td { padding: 15px 25px; border-bottom: 1px solid var(--line); white-space: nowrap; color: var(--fg-2); }
.ptable tbody tr:last-child td { border-bottom: none; }
.ptable tbody td.num { text-align: right; font-family: var(--mono); font-size: 12.5px; color: var(--fg); }
.ptable tbody td:first-child { padding-left: 24px; }
.ptable tbody td:last-child { padding-right: 24px; }
.ptable .col-part { white-space: normal; width: 25%; }
.ptable td.col-part { white-space: normal; }

/* the ROW is not a link: its numbers must stay selectable so a customer can
   copy a spec straight out of the table. The part name and the trailing >
   are the links (operator, 2026-07-27). */
.prow { transition: background var(--dur); }
.prow:hover { background: rgba(255,255,255,.028); }
.prow:hover .rowview { color: var(--fg-2); }

.t-part { display: flex; align-items: center; }
.t-name { font-weight: 700; font-size: 13px; color: var(--fg); letter-spacing: .01em;
  cursor: pointer; text-decoration: none; }
.t-name:hover { color: var(--accent); }
.t-ga { font-family: var(--mono); font-size: 12.5px; color: var(--accent); }
.t-lot { font-family: var(--mono); font-size: 12px; color: var(--fg-3); }
.col-lot { width: 76px; }
.col-avail { width: 96px; }
.col-ga { width: 68px; }
.t-sub { color: var(--fg-3); font-size: 11px; margin-top: 3px; letter-spacing: .01em; }

.rowview { color: var(--fg-3); font-size: 15px; line-height: 1; transition: color var(--dur);
  display: inline-block; cursor: pointer; text-decoration: none; padding: 4px 8px; margin: -4px -8px; }
.rowview:hover { color: var(--accent); }
.col-view { width: 30px; }

.exp-tag { font-family: var(--mono); font-size: 8.5px; text-transform: uppercase; letter-spacing: .1em; color: var(--accent); border: 1px solid var(--accent-line); border-radius: 2px; padding: 1px 4px; margin-left: 6px; vertical-align: middle; }

.empty { color: var(--fg-3); padding: 64px 0; text-align: center; }

/* ---- product page ---- */
.crumb { padding: 22px 0 0; font-size: 11.5px; color: var(--fg-3); letter-spacing: .06em; text-transform: uppercase; }
.crumb a:hover { color: var(--fg); }
/* --- pinned crumb (product + material pages, where the viewer is pinned too).
   Opt-in per page: the order page's crumb sits on a hero image, where an opaque
   band would be wrong. Height is fixed to --crumb-h so the offsets below it are
   exact; the 22px top padding is kept so the text lands where it always did. */
.crumb--pin {
  position: sticky; top: var(--nav-h); z-index: 30;
  height: var(--crumb-h); background: var(--bg);
}
/* the spec column scrolls UNDER the crumb; fade it out instead of cutting it at
   a hard edge, which would read as a second nav border 40px below the real one */
.crumb--pin::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 14px;
  background: linear-gradient(var(--bg), transparent); pointer-events: none;
}
.product { display: grid; grid-template-columns: minmax(0, 1.92fr) minmax(280px, .78fr); gap: 32px; padding: 24px 0 90px; }
.viewer-panel { position: relative; background: #000; border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; min-height: 600px; }
.viewer-panel .stage { position: absolute; inset: 0; }
.viewer-note {
  position: absolute; left: 12px; bottom: 12px; z-index: 3; font-size: 10.5px; color: var(--fg-2); font-family: var(--mono);
  background: rgba(0,0,0,.6); border: 1px solid var(--line); border-radius: var(--r-xs); padding: 5px 9px; letter-spacing: .02em;
}
.viewer-toggles { position: absolute; right: 12px; top: 12px; z-index: 3; display: flex; flex-direction: column; gap: 8px; align-items: flex-end; }
.viewer-toggle {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 11px; color: var(--fg-2); letter-spacing: .1em; text-transform: uppercase; cursor: pointer;
  background: rgba(0,0,0,.6); border: 1px solid var(--line); border-radius: var(--r-xs); padding: 6px 10px; user-select: none;
}
.viewer-toggle input { accent-color: var(--accent); width: 13px; height: 13px; margin: 0; }
.viewer-toggle:hover { color: var(--fg); border-color: var(--line-2); }
.viewer-slider { gap: 11px; }
.viewer-slider input[type="range"] { width: 118px; height: 3px; accent-color: var(--accent); cursor: pointer; }
/* the condensed disclaimer sits top-left of the stage (where the spec chips
   used to be), clear of the condense slider top-right */
.viewer-panel .condense-note, .condense-note {
  position: absolute; left: 12px; right: auto; top: 12px; z-index: 3;
  align-self: unset; max-width: 250px; text-align: left;
  font-size: 11.5px; color: var(--fg-2); line-height: 1.55;
  background: rgba(0,0,0,.74); border: 1px solid var(--line-2); border-radius: var(--r-xs); padding: 8px 11px;
}
.viewer-fallback { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--fg-3); }

/* 3D Model / Print switch + embedded engineering print */
.vm-switch { padding: 3px; gap: 0; }
.vm-switch button {
  border: 0; background: transparent; color: var(--fg-2); font: inherit;
  font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  padding: 5px 12px; border-radius: calc(var(--r-xs) - 2px); cursor: pointer;
}
.vm-switch button.is-active { background: var(--accent); color: #041018; font-weight: 700; }
.vm-switch button:not(.is-active):hover { color: var(--fg); }
.print-frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; background: #fff; z-index: 2; }
.print-frame[hidden] { display: none; }
/* print mode: every 3D-only overlay goes away; the switch + fullscreen stay */
.viewer-col.vm-print .viewer-toggles > :not(.vm-switch),
.viewer-col.vm-print .condense-note,
.viewer-col.vm-print .viewer-note,
.viewer-col.vm-print .viewer-hint,
.viewer-col.vm-print .dim-badge,
.viewer-col.vm-print .dim-hint { display: none !important; }

/* left column: the 3D viewer + a mouse-controls hint below it.
   The column is PINNED, directly below the pinned crumb: the crumb, the model
   and the print all stay in view for the whole product read while only the spec
   column scrolls. Sticky is constrained to the grid area, so the column travels
   with the spec column and stops at its end (the footer is never covered). The
   panel is capped to the viewport rather than a flat 600px, otherwise on a short
   screen the pinned panel's bottom edge - fullscreen button, viewer note - would
   sit permanently off-screen. */
.viewer-col {
  display: flex; flex-direction: column; gap: 10px; min-width: 0;
  align-self: start; position: sticky;
  top: calc(var(--nav-h) + var(--crumb-h) + 20px);
}
/* min-height:0 undoes .viewer-panel's 600px floor, which would beat `height`.
   The cap subtracts everything that is never the panel: the nav and the pinned
   crumb above it, then 73px = the section's 24px top padding (a bigger gap than
   the 20px pinned offset, so IT is what has to fit at scroll 0) + 29 for the gap
   and mouse-hint below the panel + 20 of bottom breathing. */
.viewer-col .viewer-panel {
  height: clamp(360px, calc(100vh - var(--nav-h) - var(--crumb-h) - 73px), 600px);
  min-height: 0;
}
/* the UA marks fullscreen sizing !important, but be explicit about it anyway */
.viewer-col .viewer-panel:fullscreen { height: 100%; }
/* mouse-controls hint: small, no bar, tucked into the bottom-left below the model */
.viewer-hint { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 15px; padding: 0 2px; }
.mctrl { display: inline-flex; align-items: center; gap: 6px; }
.mctrl span { font-size: 9.5px; color: var(--fg-3); letter-spacing: .03em; }
.mctrl b { color: var(--fg-2); font-weight: 600; font-size: 8.5px; text-transform: uppercase; letter-spacing: .08em; margin-right: 4px; }
.mouse-ico { width: 12px; height: auto; color: var(--fg-3); flex-shrink: 0; }
.mouse-ico .mo-hi { fill: var(--accent); }
.mouse-ico .mo-wheel { fill: var(--fg-3); }
/* touch devices get touch words — a mouse legend is a lie on a phone
   (UX pass 2026-09-03) */
.mctrl--touch { display: none; }
@media (pointer: coarse) {
  .viewer-hint .mctrl { display: none; }
  .viewer-hint .mctrl--touch { display: inline-flex; }
}
/* narrow stages: the condensed-preview explainer was absolutely positioned
   into the same corner band as the 3D/PRINT controls and collided with them
   at phone widths — below 560px it drops into the flow under the controls */
@media (max-width: 560px) {
  .viewer-panel .condense-note, .condense-note {
    position: static; max-width: none; margin: 8px 10px 0; order: 9; }
}

/* "Show dimensions" overlay — leader lines pinned to the model + a spec badge */
.dim-overlay .dim-line { stroke: #eaf0f6; stroke-width: 1.5; fill: none; stroke-linecap: round; }
.dim-overlay .dim-arrow { fill: #eaf0f6; }
.dim-overlay .dim-lead { stroke: rgba(234,240,246,.7); stroke-width: 1.1; fill: none; }
.dim-overlay .dim-ext { stroke: rgba(234,240,246,.45); stroke-width: 1; stroke-dasharray: 2.5 2.5; }
.dim-overlay .dim-label { fill: #f4f7fb; font-family: var(--mono); font-size: 11px; letter-spacing: .02em; }
.dim-overlay .dim-label-bg { fill: rgba(4,6,9,.82); stroke: rgba(255,255,255,.16); stroke-width: 1; }
/* A callout is a control: clicking it flies the camera to that measurement, the
   same as clicking its spec row. The overlay stays pointer-events:none THROUGHOUT
   — a label that were a hit target would swallow the pointerdown and a
   rotate-drag starting on it could not orbit — so dimensions.js hit-tests in JS
   and marks the hovered label .is-hot. Do not add pointer-events here. */
.dim-overlay .dim-label-g--hit .dim-label,
.dim-overlay .dim-label-g--hit .dim-label-bg { transition: fill var(--dur) var(--ease), stroke var(--dur) var(--ease); }
.dim-overlay .dim-label-g--hit.is-hot .dim-label-bg { fill: rgba(6,10,16,.94); stroke: var(--accent); }
.dim-overlay .dim-label-g--hit.is-hot .dim-label { fill: #fff; }
.dim-badge { position: absolute; left: 12px; top: 12px; z-index: 4; display: flex; flex-wrap: wrap; gap: 6px; max-width: 60%; }
.dim-badge span {
  font-family: var(--mono); font-size: 10.5px; color: var(--fg); background: rgba(0,0,0,.66);
  border: 1px solid var(--line); border-radius: var(--r-xs); padding: 4px 8px; letter-spacing: .02em;
}
.dim-hint {
  position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%); z-index: 4;
  font-family: var(--mono); font-size: 10.5px; color: var(--accent); background: rgba(0,0,0,.7);
  border: 1px solid var(--accent-line); border-radius: 999px; padding: 5px 12px; letter-spacing: .04em; white-space: nowrap;
}
.fs-btn {
  position: absolute; right: 12px; bottom: 12px; z-index: 3; width: 30px; height: 30px; line-height: 1;
  display: flex; align-items: center; justify-content: center; font-size: 15px; cursor: pointer;
  color: var(--fg-2); background: rgba(0,0,0,.6); border: 1px solid var(--line); border-radius: var(--r-xs);
}
.fs-btn:hover { color: var(--fg); border-color: var(--line-2); }
.viewer-panel:fullscreen { border-radius: 0; }
.viewer-panel:fullscreen .stage { position: absolute; inset: 0; }

.spec-head h1 { font-size: 24px; font-weight: 700; letter-spacing: .01em; margin: 4px 0 4px; }
.spec-head .pn { font-family: var(--mono); color: var(--accent); font-size: 12.5px; }
.spec-head .sub { color: var(--fg-2); margin: 8px 0 0; font-size: 14px; }
.spec-tags { display: flex; gap: 8px; flex-wrap: wrap; margin: 16px 0; }
.tag { font-size: 10px; letter-spacing: .12em; text-transform: uppercase; padding: 4px 10px; border: 1px solid var(--line-2); border-radius: var(--r-xs); color: var(--fg-2); }

.price-row { display: flex; align-items: baseline; gap: 16px; margin: 18px 0 6px; padding: 16px 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.price-row .p { font-family: var(--mono); font-size: 23px; color: var(--fg); }
.price-row .p span { color: var(--fg-3); font-size: 12.5px; }
.price-row .q { color: var(--fg-2); font-size: 12.5px; font-family: var(--mono); }

.spec-table { width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 13px; }
.spec-table td { padding: 9px 0; border-bottom: 1px solid var(--line); vertical-align: top; }
.spec-table td:first-child { color: var(--fg-2); width: 46%; }
.spec-table td:last-child { font-family: var(--mono); text-align: right; color: var(--fg); }
/* a tolerance rides UNDER its dimension instead of wrapping beside it */
.spec-table .tol-sub { display: block; font-size: 9.5px; color: var(--fg-3); margin-top: 2px; }
/* chunked spec groups: tracked micro-header over a kerf-blue hairline (signal
   color stays scarce — thin accent rules only) */
.spec-table .spec-group th {
  padding: 20px 0 7px; text-align: left;
  font: 700 10px var(--mono); letter-spacing: .18em; text-transform: uppercase; color: var(--fg-3);
  border-bottom: 1px solid var(--accent-line);
}
.spec-table tr.spec-group:first-child th { padding-top: 2px; }
.spec-table .spec-group th svg { vertical-align: -1.5px; margin-right: 8px; stroke: var(--accent); }
/* rows with a matching 3D callout: hover previews it, click flies to it */
.spec-table tr[data-dim] { cursor: pointer; }
.spec-table tr[data-dim]:hover td { background: rgba(127,160,204, .06); }
.spec-table tr[data-dim]:hover td:first-child { color: var(--accent); }
/* the hovered callout draws alone, bright */
.dim-overlay.dim-hl .dim-line, .dim-overlay.dim-hl .dim-ext, .dim-overlay.dim-hl .dim-lead { stroke: #b9cee2; }
.dim-overlay.dim-hl .dim-arrow { fill: #b9cee2; }
.dim-overlay.dim-hl .dim-label { fill: #eaf3ff; }
/* one-shot laser kerf under the product title (motion-onset entrance) */
.spec-kerf { height: 2px; margin: 10px 0 12px; }
.spec-kerf i {
  display: block; height: 100%; width: 0; border-radius: 1px;
  background: linear-gradient(90deg, #33465b, #7fa0cc 70%, #d3e2f1);
  animation: specKerf .8s var(--ease) .15s forwards;
}
@keyframes specKerf { to { width: 100%; } }
@media (prefers-reduced-motion: reduce) { .spec-kerf i { animation: none; width: 100%; } }
/* live extended cost beside the buy row (unit-price anchor + total) */
.line-total { margin: 10px 2px 0; font-family: var(--mono); font-size: 12.5px; color: var(--fg-2); }
.line-total b { color: var(--fg); font-weight: 400; }

.actions { display: flex; align-items: center; gap: 10px; margin-top: 24px; flex-wrap: wrap; }

/* ---- create page ---- */
/* the create page runs wide (80% of the viewport) with a big 3D preview */
.wrap--create { max-width: 80%; }
.create { display: grid; grid-template-columns: minmax(320px, .7fr) minmax(0, 1.35fr); gap: 40px; padding: 24px 0 90px; align-items: start; }
.create .viewer-panel { position: sticky; top: calc(var(--nav-h) + 20px); min-height: 680px; }
.form-card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r); padding: 24px; }
.seg { display: inline-flex; border: 1px solid var(--line-2); border-radius: var(--r-sm); overflow: hidden; margin-bottom: 22px; }
.seg button { background: transparent; color: var(--fg-3); border: 0; height: 38px; padding: 0 22px; font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase; transition: color var(--dur), background var(--dur); }
.seg button:hover { color: var(--fg-2); }
.seg button.is-active { background: var(--fg); color: #000; font-weight: 700; }
.field { margin-bottom: 16px; }
.field > label { display: block; font-size: 10.5px; color: var(--fg-3); letter-spacing: .1em; text-transform: uppercase; margin-bottom: 6px; }
.field input {
  width: 100%; height: 40px; padding: 0 12px; background: var(--panel-2);
  border: 1px solid var(--line-2); color: var(--fg); border-radius: var(--r-sm); font-family: var(--mono); font-size: 13.5px;
  transition: border-color var(--dur);
}
.field input:focus { outline: none; border-color: var(--accent-line); }
.field .unit { color: var(--fg-3); font-size: 11px; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; }
.form-sec { font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase; color: var(--fg-3); margin: 24px 0 14px; padding-bottom: 9px; border-bottom: 1px solid var(--line); }
.form-err { color: #e06666; font-size: 12.5px; margin-top: 10px; min-height: 18px; }
.checkrow { display: inline-flex; align-items: center; gap: 9px; margin-top: 12px; color: var(--fg-2); font-size: 12.5px; cursor: pointer; user-select: none; }
.checkrow input { accent-color: var(--accent); width: 14px; height: 14px; margin: 0; }
.checkrow:hover { color: var(--fg); }
.fit-note { margin-top: 12px; padding: 9px 12px; border: 1px solid var(--line-2); background: var(--panel-2); border-radius: var(--r-sm); color: var(--fg-2); font-size: 12px; line-height: 1.5; }

.spin { width: 22px; height: 22px; border: 2px solid var(--line-2); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.center-abs { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }

@media (max-width: 900px) {
  .product, .create { grid-template-columns: 1fr; }
  .create .viewer-panel { position: relative; top: 0; min-height: 380px; }
  /* stacked: the viewer is its own row above the specs, so pinning it would
     just hold it over its own (short) row and eat the screen - let it scroll */
  .viewer-col { position: static; top: auto; }
  .viewer-col .viewer-panel { height: 420px; }
  .wrap--create { max-width: none; }
  .search { margin-left: 0; width: 100%; }
}

/* ---- accounts: auth (sign in / create account), styled to the store ---- */
/* Split card: a branded left panel beside the form, near-black --panel surfaces,
   tracked-mono detail lines, a static laser-cut arc, sharp --r corners. */
.auth {
  display: grid; grid-template-columns: 1.02fr 0.98fr;
  max-width: 940px; margin: 48px auto 90px;
  border: 1px solid var(--line-2); border-radius: var(--r); overflow: hidden; background: var(--panel);
}
.auth__brand {
  position: relative; display: flex; flex-direction: column; padding: 40px 40px 36px;
  background: linear-gradient(155deg, var(--panel-2), var(--panel) 62%); border-right: 1px solid var(--line-2);
}
.auth__brandtop { font-size: 12px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase; color: var(--fg); }
.auth__brandtop span { color: var(--fg-3); font-weight: 400; }
.auth__brandmid { flex: 1; display: flex; flex-direction: column; justify-content: center; padding: 30px 0; }
.auth__brandmid h2 { font-size: 23px; line-height: 1.26; font-weight: 700; letter-spacing: .01em; color: var(--fg); margin: 0 0 22px; max-width: 17ch; }
.auth__arc { display: block; width: 210px; max-width: 100%; height: auto; }   /* the SVG's viewBox sets the aspect */
.auth__arc--laser { width: 100%; max-width: 330px; }   /* the cutting-head scene fills the column */
/* the laser is ON: glow layers breathe, sparks flicker */
.auth__arc--laser .lzp { animation: lzPulse 1.7s ease-in-out infinite; }
.auth__arc--laser .lzs { animation: lzSpark .8s steps(2, jump-none) infinite; transition: opacity .7s ease; }
/* the piece is cut (laser_trace.js flags lz-done): live effects go out —
   sparks and embers stop; the head, beam and its glow lighting stay */
.auth__arc--laser.lz-done .lzs { animation: none; opacity: 0; }
.auth__arc--laser.lz-done .emb { animation: none; }   /* base opacity is 0 */
@keyframes lzPulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
@keyframes lzSpark { 0%, 100% { opacity: .9; } 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) {
  .auth__arc--laser .lzp, .auth__arc--laser .lzs { animation: none; }
}

/* ── laser trace-in ────────────────────────────────────────────────────────
   Marked geometry (class="tr" + pathLength="1") draws itself in as if the
   beam were cutting it: dashoffset 1 -> 0, staggered per element via --td,
   with laser_trace.js riding a glowing cutting tip on each line's leading
   edge. The working head/beam/sparks arrive after (g.lz-late fades in). */
.auth__arc--laser .tr {
  stroke-dasharray: 1; stroke-dashoffset: 1;
  animation: lzTraceIn var(--tt, .5s) var(--ease) var(--td, 0s) 1 forwards;
}
@keyframes lzTraceIn { to { stroke-dashoffset: 0; } }
.auth__arc--laser .lz-late { opacity: 0;
  animation: lzFadeIn .45s ease var(--td, 1.6s) 1 forwards; }
@keyframes lzFadeIn { to { opacity: 1; } }
/* the traveling cutting tip laser_trace.js rides along each drawing line */
.lz-tip { pointer-events: none; }
/* the cart scene: big and centered when the cart is empty, compact at the top
   of the summary panel when it has items */
.cart-empty .cart-art { width: 250px; margin: 0 auto 16px; display: block; }
.cart-summary .cart-art { width: 180px; margin: 0 0 12px; display: block; }
/* replayed renders (qty clicks) show the finished piece, no animation */
.auth__arc--laser.lz-static .tr { animation: none; stroke-dashoffset: 0; }
.auth__arc--laser.lz-static .lz-late { animation: none; opacity: 1; }
.lz-tip-halo { fill: #7fa0cc; opacity: .16; }
.lz-tip-glow { fill: #b9d2ea; opacity: .45; }
.lz-tip-core { fill: #f2f8ff; }
@media (prefers-reduced-motion: reduce) {
  .auth__arc--laser .tr, .auth__arc--laser .lz-late {
    animation: none; stroke-dashoffset: 0; opacity: 1;
  }
}

/* ── ember sparks ─────────────────────────────────────────────────────────
   Tiny molten motes ejected from a melt pool: each loops a short ballistic
   flight (--dx/--dy, SVG user units), igniting bright and cooling to nothing.
   Staggered --d/--dl per particle keep the spray from visibly repeating.
   Used by every laser scene (auth pages, catalog hero kerf, checkout rule). */
.emb {
  fill: #eaf3ff; opacity: 0;
  animation: embFly var(--d, 1s) cubic-bezier(.15, .65, .45, 1) infinite;
  animation-delay: var(--dl, 0s);
}
.emb--cool { fill: #d3e2f1; }
@keyframes embFly {
  0%   { opacity: 0; transform: translate(0, 0); }
  6%   { opacity: .95; }
  55%  { opacity: .55; }
  100% { opacity: 0; transform: translate(var(--dx, 8px), var(--dy, -12px)); }
}
@media (prefers-reduced-motion: reduce) { .emb { animation: none; opacity: 0; } }
.auth__points { list-style: none; margin: 0; padding: 0; display: grid; gap: 11px; }
.auth__points li { position: relative; padding-left: 18px; font: 10px/1.5 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--fg-3); }
.auth__points li::before { content: ""; position: absolute; left: 0; top: 5px; width: 6px; height: 6px; background: var(--accent); border-radius: var(--r-xs); }
.auth__form { display: flex; flex-direction: column; justify-content: center; padding: 44px 40px; }
.auth__form h1 { font-size: 21px; font-weight: 700; letter-spacing: .01em; margin: 0 0 6px; color: var(--fg); }
.auth__form .sub { color: var(--fg-2); font-size: 13px; line-height: 1.55; margin: 0 0 24px; }
/* gate note: why a signed-out visitor was bounced to this form. Reads as a
   quiet callout, NOT an error — accent left rule over a --panel-2 fill, same
   language as the catalog's .fit-note. Hidden until gate_note.js fills it. */
.auth__form.has-why .sub { margin-bottom: 14px; }
.auth__why {
  display: flex; gap: 10px; align-items: flex-start; margin: 0 0 22px; padding: 11px 13px;
  background: var(--panel-2); border: 1px solid var(--line-2);
  border-left: 2px solid var(--accent); border-radius: var(--r-sm);
}
.auth__why svg { flex: none; width: 15px; height: 15px; margin-top: 1.5px; color: var(--accent); }
.auth__why p { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--fg-2); }
.auth__why b { display: block; color: var(--fg); font-weight: 700; margin-bottom: 2px; }
.auth__form .form-card { background: none; border: none; border-radius: 0; padding: 0; }
.auth__form .actions { margin-top: 26px; }
.auth__form .btn--accent { width: 100%; justify-content: center; height: 44px; letter-spacing: .12em; }
.auth__form .authswap { margin-top: 22px; font-size: 12.5px; color: var(--fg-3); }
.auth__form .authswap a { color: var(--accent); }
.auth__form .authswap a:hover { color: var(--fg); }
.form-err.ok { color: #3fbf6a; }

/* table-page detailing on the auth pages: the form reads like catalog rows —
   hairline-separated rows, tracked uppercase micro-labels, mono values, the
   row's rule warming to accent on focus (like a sorted column head). */
.auth__kicker { font: 10.5px var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--fg-3); margin: 0 0 12px; }
.auth__kicker b { color: var(--accent); font-weight: 400; }
.auth .form-card { border-top: 1px solid var(--line); }
.auth .field { margin: 0; padding: 13px 2px 12px; border-bottom: 1px solid var(--line); transition: border-color var(--dur); }
.auth .field > label { margin-bottom: 5px; font-size: 10px; letter-spacing: .16em; transition: color var(--dur); }
.auth .field input {
  height: 26px; padding: 0; background: none; border: 0; border-radius: 0;
  font-size: 14px;
}
.auth .field input::placeholder { color: var(--fg-3); opacity: .5; }
.auth .field:focus-within { border-bottom-color: var(--accent-line); }
.auth .field:focus-within > label { color: var(--accent); }
.auth .grid2 { gap: 0 30px; }
/* brand-column footer: a quiet table-meta strip over a hairline */
.auth__brandfoot {
  border-top: 1px solid var(--line); padding-top: 14px; display: flex; flex-wrap: wrap; gap: 7px 10px;
  font: 10px/1.6 var(--mono); letter-spacing: .13em; text-transform: uppercase; color: var(--fg-3);
}
.auth__brandfoot i { font-style: normal; color: var(--accent); }
.auth__brandfoot a { color: var(--fg-3); text-decoration: none; transition: color var(--dur); }
.auth__brandfoot a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
/* single-window variant: no side panel — the laser scene sits inside the form
   card, cutting directly above the heading so it leads the eye into the form */
.auth--single { display: block; max-width: 470px; margin-top: 36px; }
.auth--single .auth__form { padding: 28px 36px 26px; }
.auth--single .auth__top {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 6px;
}
.auth--single .auth__top .auth__kicker { margin: 0; }
.auth--single .auth__arc--laser { max-width: 272px; margin: 2px auto 8px; display: block; }
.auth--single .auth__brandfoot { margin-top: 22px; }
@media (max-width: 760px) {
  .auth { grid-template-columns: 1fr; max-width: 440px; margin-top: 28px; }
  .auth__brand { display: none; }
  .auth__form { padding: 34px 26px; }
  .auth--single .auth__form { padding: 26px 24px 22px; }
}

/* ---- accounts: admin list ---- */
.admin-wrap { padding: 40px 0 90px; }
.admin-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 22px; }
.admin-head h1 { font-size: 26px; font-weight: 700; letter-spacing: .01em; margin: 0; }
.admin-head .sub { color: var(--fg-3); font-family: var(--mono); font-size: 12px; letter-spacing: .06em; text-transform: uppercase; }
/* the Accounts<->Orders cross-link lives HERE, not in the nav — the nav is the
   shared shell and must be identical on every page (operator, 2026-07-31) */
.admin-head__right { display: inline-flex; align-items: baseline; gap: 18px; }
.acct-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.acct-table th { text-align: left; padding: 13px 16px; font-size: 10px; letter-spacing: .14em; text-transform: uppercase; color: var(--fg-3); font-weight: 400; border-bottom: 1px solid var(--line-2); white-space: nowrap; }
.acct-table td { padding: 14px 16px; border-bottom: 1px solid var(--line); color: var(--fg-2); white-space: nowrap; }
.acct-table tr:last-child td { border-bottom: none; }
.acct-table td.name { color: var(--fg); font-weight: 700; }
.acct-table td.email { font-family: var(--mono); font-size: 12px; }
.rolechip { font-family: var(--mono); font-size: 9px; text-transform: uppercase; letter-spacing: .1em; padding: 2px 7px; border-radius: var(--r-xs); border: 1px solid var(--line-2); color: var(--fg-3); }
.rolechip.admin { color: var(--accent); border-color: var(--accent-line); }

/* The floating mini-cart lives in cartfab.css (self-injected by cart.js on
   every page that loads it — including non-store pages like the builder). */

/* ---- product page: qty stepper + add to cart ---- */
.buy-row { display: flex; align-items: stretch; gap: 12px; margin: 20px 0 6px; }
.qty { display: inline-flex; align-items: center; border: 1px solid var(--line-2); border-radius: var(--r-sm); overflow: hidden; }
.qty button { width: 36px; background: var(--panel-2); border: none; color: var(--fg-2); font-size: 17px; line-height: 1; cursor: pointer; transition: color var(--dur), background var(--dur); }
.qty button:hover { color: var(--fg); background: var(--panel-3); }
.qty input { width: 76px; height: 44px; border: none; border-left: 1px solid var(--line-2); border-right: 1px solid var(--line-2); background: var(--panel-2); color: var(--fg); text-align: center; font-family: var(--mono); font-size: 14px; }
.qty input::-webkit-outer-spin-button, .qty input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.qty input[type="number"] { -moz-appearance: textfield; }
.buy-row .btn--accent { flex: 1; height: 44px; justify-content: center; }
.buy-note { color: var(--fg-3); font-size: 11.5px; margin: 4px 0 0; }
.btn--added { background: #2b6b3e !important; color: #d9f7e2 !important; }

/* ---- cart page ---- */
.cart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; padding: 34px 0 8px; }
.cart-head h1 { font-size: 27px; font-weight: 700; letter-spacing: .01em; margin: 0; }
.cart-head .sub { color: var(--fg-3); font-family: var(--mono); font-size: 12px; letter-spacing: .06em; text-transform: uppercase; }
/* checkout runs its header tighter than the cart's: every pixel here is a
   pixel of Place order below the fold */
.co-page .cart-head { padding: clamp(8px, 2.2vh, 20px) 0 clamp(3px, .7vh, 6px); }
.co-page .cart-head h1 { font-size: clamp(19px, 2.5vh, 27px); }
.cart-layout { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(300px, .9fr); gap: 40px; align-items: start; padding: 16px 0 90px; }
.cart-items { border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
/* TWO ZONES per line: the data row, then the render beneath it. The render used
   to be a 96x54 framed plate in the row's first column, which was too small to
   read (operator 2026-08-04) and, being a bordered black plate, looked like an
   icon rather than the part. */
.cart-item { display: block; padding: 18px 22px; border-bottom: 1px solid var(--line); }
.cart-item:last-child { border-bottom: none; }
.cart-item .ci-line {
  display: grid; grid-template-columns: 1fr auto 132px 96px 26px;
  gap: 20px; align-items: center;
}
/* NO background, NO border, NO frame. The image's own void is recoloured to the
   page's #060708 (dev/tools/still_plain.py), so it reads as sitting directly on
   the row — it is a colour match, not alpha. Fixed HEIGHT with width:auto (not
   the reverse): after trimming to the part, a long spiral is ~3.2:1 and a ring
   ~1:1, so a fixed height keeps every part at the same visual scale and lets the
   wide ones simply run wider. */
.cart-item .ci-art img {
  /* a FIXED height, not 100%: the strip's height is what animates, and the 8px
     top margin is the gap under the sub-heading — carried inside the animated
     height so only one property moves */
  height: var(--art-h); width: auto; max-width: 100%;
  margin-top: 8px; object-fit: contain; display: block;
}
/* COLLAPSED AT REST, EXPANDS ON HOVER (operator 2026-08-04). At rest the cart is
   compact data rows with no imagery competing for attention, so PROCEED TO
   CHECKOUT is the loudest thing on the page; hovering an item expands it and
   reveals that part's render. This replaced a dim + kerf-scan-sweep treatment —
   hiding the still outright serves the same goal with no effect to tune.
   `overflow: hidden` is load-bearing: it is what clips the image while the strip
   is at height 0. Height animates because both ends are definite lengths, and the
   margins ride along so a collapsed row carries no dead space. */
.cart-item { --art-h: 132px; }
.cart-item .ci-art {
  /* display:block is load-bearing — .ci-art is an <a>, and `height` does not
     apply to an inline element, so without this the collapse silently does
     nothing and every row stays full height */
  display: block;
  height: 0; overflow: hidden; margin-left: -22px;
  /* ONE property, FAST, no fade. The first cut eased height + margin + opacity
     together over .30s and read as a performance rather than a row making room
     (operator 2026-08-04: "that animation is a little too cheesy"). A single
     dimension moving in .13s ease-out is matter-of-fact: the gap for the part
     comes from the image's own margin, so no second property has to animate and
     nothing snaps. Set `transition: none` here for an instant reveal. */
  transition: height .13s ease-out;
}
.cart-item:hover .ci-art { height: calc(var(--art-h) + 8px); }
@media (prefers-reduced-motion: reduce) {
  .cart-item .ci-art { transition: none; }
}
/* COMPACT parts do NOT bleed. A pull ring trims to ~1:1, and the edge-bleed that
   makes a long tube read as "continuing off the row" just makes a small ring look
   stuck to the border. Tagged from the image's own natural aspect on load (< 2:1),
   so it needs no per-product data. Centred in the LEFT HALF so the part lands
   ~25% across rather than 50%; `* { box-sizing: border-box }` means the padding
   shrinks the content box instead of widening the strip. */
.cart-item .ci-art.is-compact {
  margin-left: 0; display: flex; align-items: center; justify-content: center;
  padding-right: 50%;
}
.cart-item:hover .ci-art.is-compact { margin-left: 0; }
.cart-item .ci-name { font-weight: 700; color: var(--fg); font-size: 13.5px; }
.cart-item .ci-name a { color: inherit; }
.cart-item .ci-name a:hover { color: var(--accent); }
.cart-item .ci-sub { color: var(--fg-3); font-size: 12px; margin-top: 4px; }
.cart-item .ci-unit { font-family: var(--mono); font-size: 12.5px; color: var(--fg-2); text-align: right; white-space: nowrap; }
.cart-item .ci-total { font-family: var(--mono); font-size: 13.5px; color: var(--fg); text-align: right; }
.cart-item .ci-rm { color: var(--fg-3); background: none; border: none; cursor: pointer; font-size: 16px; line-height: 1; }
.cart-item .ci-rm:hover { color: #e06666; }
.cart-item .qty { height: 40px; }
.cart-item .qty button { width: 32px; height: 40px; }
.cart-item .qty input { width: 56px; height: 40px; font-size: 12.5px; }
/* HOME treatment (operator 2026-08-31, matches the checkout breakdown):
   a ruled column, mono micro-cap labels, quiet unruled money rows, one
   rule before the total, solid + quiet-arrow actions */
.cart-summary { background: transparent; border: 0; border-radius: 0; border-left: 1px solid var(--line-2); padding: 4px 0 4px 28px; }
.cart-summary h2 { font: 600 10px var(--mono); text-transform: uppercase; letter-spacing: .2em; color: var(--accent); margin: 0 0 16px; }
.cart-summary .sum-row { border-bottom: 0; padding: 6px 0; font-size: 12px; }
.cart-summary .sum-row > span:first-child { font: 600 9.5px var(--mono); letter-spacing: .16em; text-transform: uppercase; color: var(--fg-3); }
.cart-summary .sum-row .mono { font-size: 13px; color: var(--fg-2); }
.cart-summary .sum-row.total { text-transform: uppercase; letter-spacing: .02em; font-weight: 700; font-size: 14px; color: var(--fg); border-top: 1px solid var(--line-2); margin-top: 12px; padding-top: 18px; }
.cart-summary .sum-row.total > span:first-child { font: inherit; color: var(--fg); letter-spacing: .02em; }
.cart-summary .sum-row.total .mono { font-size: 22px; color: var(--fg); }
.sum-row { display: flex; align-items: baseline; justify-content: space-between; padding: 10px 0; font-size: 13px; color: var(--fg-2); border-bottom: 1px solid var(--line); }
.sum-row .mono { font-family: var(--mono); color: var(--fg); }
.sum-row.total { color: var(--fg); font-size: 17px; border-bottom: none; padding: 15px 0 4px; }
.sum-row.total .mono { font-size: 18px; }
.cart-summary .btn--accent {
  width: 100%; justify-content: center; height: 46px; margin-top: 18px;
  border-radius: 0; border: 1px solid var(--accent); background: var(--accent); color: #06121f;
  font-weight: 700; font-size: 12px; letter-spacing: .08em; text-transform: uppercase;
  transition: background .22s ease, border-color .22s ease;
}
/* blue, brighter blue on hover (operator 2026-08-31) - flat, no gradient */
.cart-summary .btn--accent:hover { background: #9cbde2; border-color: #9cbde2; }
.cart-summary .cont {
  display: flex; justify-content: space-between; align-items: center;
  margin-top: 8px; padding: 12px 2px 0; text-decoration: none;
  font: 600 10.5px var(--mono); letter-spacing: .16em; text-transform: uppercase;
  color: var(--fg-2); transition: color var(--dur);
}
.cart-summary .cont::after { content: "→"; transition: transform .22s var(--ease); }
.cart-summary .cont:hover { color: var(--fg); }
.cart-summary .cont:hover::after { transform: translateX(4px); }
.cart-note { color: var(--fg-3); font-size: 11.5px; line-height: 1.5; margin-top: 14px; }

/* ---- checkout ---- */
.checkout-layout { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(300px, .9fr);
  column-gap: 36px; row-gap: clamp(9px, 1.6vh, 14px); align-items: start;
  padding: clamp(4px, 1vh, 8px) 0 60px; }
.co-summary { position: sticky; top: calc(var(--nav-h) + 20px); background: var(--panel); border: 1px solid var(--line); border-radius: var(--r); padding: 24px; }
.co-line { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; }
.co-line .ci-name { color: var(--fg); }
.co-line .ci-q { color: var(--fg-3); font-family: var(--mono); }
.co-line .ci-t { font-family: var(--mono); color: var(--fg); white-space: nowrap; }

/* Place order lives at the BOTTOM of the FORM column, under the fields it
   validates — not in the summary rail, which is now a pure price breakdown.
   Three grid children: form (1,1), summary (1,2), actions (2,1). The rail
   SPANS both rows so its sticky range reaches the button instead of ending
   with the form — the price has to still be on screen when you press it. */
.checkout-layout .co-summary { grid-row: span 2; }
.co-actions { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  padding: clamp(11px, 1.8vh, 16px) 22px clamp(12px, 1.9vh, 17px); }
.co-actions .btn--accent { width: 100%; justify-content: center; height: clamp(40px, 5vh, 46px); }
.co-actions .cart-note { margin-top: clamp(6px, 1vh, 9px); }
.sum-row .at-cost { color: var(--fg-3); }

/* The required star. With no readiness sentence on the page, this IS the
   signal — so it is amber, bigger than the label it sits in, and captioned
   once next to the Contact rule so nobody has to guess what it means. */
.field > label .req, .req-legend .req { color: #e6a15c; font-size: 15px;
  line-height: 0; font-weight: 700; margin-left: 3px; vertical-align: -1px; }
.req-legend { float: right; text-transform: none; letter-spacing: 0;
  font-size: 10.5px; color: var(--fg-3); }
.req-legend .req { margin: 0 3px 0 0; font-size: 13px; }
/* a starred field only turns red once they have actually pressed Place order */
.field.is-missing input,
.field.is-missing select { border-color: #e06666; box-shadow: 0 0 0 3px rgba(224,102,102,.12); }

/* checkout has the only <select> in a .field; match the inputs beside it */
.checkout-layout .field select {
  width: 100%; height: clamp(30px, 3.9vh, 36px); padding: 0 10px;
  background: var(--panel-2); border: 1px solid var(--line-2); color: var(--fg);
  border-radius: var(--r-sm); font-family: var(--mono); font-size: 13px;
  transition: border-color var(--dur); }
.checkout-layout .field select:focus { outline: none; border-color: var(--accent-line); }

/* who the carrier bills. It rides INSIDE the section rule rather than sitting
   above the method list, so the default ("bill it to us") costs no height at
   all on a page whose whole problem is height. */
.pay-seg { float: right; display: inline-flex; gap: 0; margin-top: -3px;
  border: 1px solid var(--line-2); border-radius: var(--r-sm); overflow: hidden; }
.pay-seg button { background: transparent; border: 0; cursor: pointer;
  color: var(--fg-3); font: 600 9.5px var(--mono); letter-spacing: .09em;
  text-transform: uppercase; padding: 5px 10px;
  transition: color var(--dur), background var(--dur); }
.pay-seg button:hover { color: var(--fg-2); }
.pay-seg button.on { background: rgba(127,160,204,.14); color: #b8d2ee; }
#shipAcct { margin-bottom: 4px; }

/* ---- checkout: fit the form + Place order in one view ----
   The airy /store/create rhythm made this page ~1270px of form before the
   button, so ordering meant scrolling to a control you could not see.

   The rhythm is vh-aware (same idiom as .section--req on the homepage) rather
   than stepped at breakpoints: stepped tiers left dead bands — a 950px-tall
   window got the full-size spacing and a 860px one got the 940px tier, both
   pushing the button under the fold. Clamps scale it continuously instead.
   Everything is scoped to .checkout-layout so /store/create and the auth
   forms keep the roomier spacing. */
.checkout-layout .form-card { padding: clamp(12px, 1.9vh, 20px) 20px; }
.checkout-layout .field { margin-bottom: clamp(5px, 1.1vh, 11px); }
.checkout-layout .field > label { margin-bottom: clamp(2px, .45vh, 4px); }
.checkout-layout .field input { height: clamp(30px, 3.9vh, 36px); font-size: 13px; }
.checkout-layout .grid2,
.checkout-layout .grid3,
.checkout-layout .grid4,
.checkout-layout .grid-addr { gap: clamp(8px, 1.2vh, 11px); }
.checkout-layout .form-sec { margin: clamp(8px, 1.7vh, 16px) 0 clamp(5px, 1.1vh, 10px);
  padding-bottom: clamp(4px, .8vh, 7px); }
.checkout-layout .form-err { margin-top: 5px; min-height: 15px; }
/* street + suite share a row (2:1), and Country joins city/state/zip: four
   stacked rows become two */
.grid-addr { display: grid; grid-template-columns: 2fr 1fr; }
.grid4 { display: grid; grid-template-columns: 1.4fr 1fr .9fr 1.2fr; }
/* phones: four-across city/state/zip/country left ~70px fields — wrap to
   2x2 so each keeps a typable width (UX pass 2026-09-03) */
@media (max-width: 560px) {
  .grid4 { grid-template-columns: 1fr 1fr; }
}
.order-done { max-width: 560px; margin: 70px auto; text-align: center; }
.order-done .badge-ok { width: 54px; height: 54px; margin: 0 auto 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 26px; color: #3fbf6a; border: 2px solid #3fbf6a; }
.order-done h1 { font-size: 26px; font-weight: 700; margin: 0 0 10px; }
.order-done p { color: var(--fg-2); font-size: 14px; line-height: 1.6; }
.order-done .oid { font-family: var(--mono); color: var(--accent); }

.cart-empty { text-align: center; padding: 90px 0; color: var(--fg-3); }
.cart-empty .btn { margin-top: 18px; }

@media (max-width: 820px) {
  .cart-layout, .checkout-layout { grid-template-columns: 1fr; }
  /* stacked: the breakdown reads BEFORE the button you press against it */
  .checkout-layout .form-card { order: 1; }
  .checkout-layout .co-summary { order: 2; grid-row: auto; position: static; }
  .checkout-layout .co-actions { order: 3; }
  /* The render stays on mobile — it is how you recognise the line at a glance —
     just smaller; the unit price is what gives way. FIVE tracks for the five
     cells that remain visible (art, name, qty, total, remove): with four, the
     remove × wrapped onto a second row under the art and read as detached from
     its line. minmax(0,1fr) lets the name column actually shrink on a narrow
     phone instead of forcing the row wider than the screen. */
  .cart-item .ci-line { grid-template-columns: minmax(0, 1fr) auto auto auto; gap: 12px; }
  .cart-item .ci-unit { display: none; }
  .cart-item { --art-h: 104px; }
}
/* Phones: a DELIBERATE two-row line — identity on top, controls beneath. One row
   cannot hold the art plate, the name, a 120px stepper, the line total AND the
   remove button on a 390px screen; forcing it made the page scroll sideways.
   Placement is explicit because the hidden .ci-unit would otherwise leave a hole
   in auto-placement. */
@media (max-width: 560px) {
  .cart-item { padding: 16px 16px; }
  /* the art is its own zone now, so the phone line only has to fit the name,
     the stepper, the total and the remove x — two rows, no art column, and no
     explicit grid-areas needed to dodge the hidden .ci-unit */
  .cart-item .ci-line {
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 12px; row-gap: 12px;
  }
  .cart-item .ci-id  { grid-area: 1 / 1; }
  .cart-item .ci-rm  { grid-area: 1 / 2; align-self: start; }
  /* justify-self: the stepper is a bordered control — letting it stretch left an
     empty box trailing the + button */
  .cart-item .qty    { grid-area: 2 / 1; justify-self: start; }
  .cart-item .ci-total { grid-area: 2 / 2; align-self: center; }
  .cart-item { --art-h: 88px; }
  .cart-item .ci-art { margin-left: -16px; }   /* = .cart-item padding-left */
}

/* tube-stock product view: diagram <-> 3D switch, and the diagram surface */
.matview { position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; padding: 18px; }
.matview[hidden] { display: none; }
.matview svg { width: 100%; height: 100%; max-height: 100%; }
.viewseg { display: inline-flex; border: 1px solid var(--line-2); border-radius: var(--r-sm);
  overflow: hidden; background: rgba(0,0,0,.55); }
.viewseg button { background: transparent; border: 0; color: var(--fg-3); cursor: pointer;
  font: 600 10px var(--mono); letter-spacing: .1em; text-transform: uppercase; padding: 6px 12px;
  transition: color var(--dur), background var(--dur); }
.viewseg button:hover { color: var(--fg-2); }
.viewseg button.is-on { color: var(--fg); background: rgba(255,255,255,.08); }
/* unit suffix drawn INSIDE the number input (a bare "ft" wedged between the
   field and the + button read as clutter) */
.qty__in { position: relative; display: inline-flex; }
.qty__in input { width: 84px; padding-right: 26px; }
.qty__in i {
  position: absolute; right: 9px; top: 50%; transform: translateY(-50%);
  font-style: normal; font-family: var(--mono); font-size: 11px;
  color: var(--fg-3); pointer-events: none;
}
/* the tube-stock price row carries the running total on its right */
.price-row .line-total { margin-left: auto; text-align: right; }

/* The cut-tube artwork is a WATERMARK behind the cart — it shares the items
   column's grid cell (bigger + CENTERED on the item list, operator
   2026-07-28), never competing with the numbers it used to sit on top of. */
.cart-layout { position: relative; }
.cart-layout > .cart-items { grid-column: 1; grid-row: 1; position: relative; }
/* RIGHT COLUMN = summary, then the cart mark under it (operator 2026-08-04:
   that space is "guranteed to always have room for it"). They share ONE cell:
   a grid row 2 would sit below the whole ITEM LIST, not below the summary,
   because row 1 is as tall as the items. STICKY LIVES HERE, not on the summary
   -- the pair scrolls together, and putting `position` on .cart-summary is the
   trap that made its `top` a relative 84px offset (see the note below). */
.cart-layout > .cart-side {
  grid-column: 2; grid-row: 1; align-self: start;
  position: sticky; top: calc(var(--nav-h) + 20px);
  display: flex; flex-direction: column; gap: 22px;
}
.cart-mark { display: block; opacity: .75; pointer-events: none; }
.cart-mark .cart-art { width: 100%; max-width: none;   /* = the summary width; out-ranks the 330px auth-art clamp */
  height: auto; margin: 0 auto; display: block; }
/* z-index only — declaring position here OVERRODE the summary's sticky (same
   property, higher specificity), and sticky's `top` then applied as a
   RELATIVE offset: the whole panel sat 84px low */
.cart-layout > .cart-items, .cart-layout > .cart-side { z-index: 1; }
/* Undo those explicit placements once the layout collapses to ONE column.
   `grid-column: 2` on the summary has to be revoked or it conjures an implicit
   second column that the single 1fr track knows nothing about: measured at a
   760px viewport the layout came out `2px 639px` and the whole item list was
   squeezed into a 2px sliver — the cart was unusable on a phone. This block has
   to live AFTER the rules above; the earlier max-width:900px query cannot undo
   them, being the same specificity and earlier in source order. */
@media (max-width: 900px) {
  .cart-layout > .cart-items,
  .cart-layout > .cart-side { grid-column: 1; grid-row: auto; position: static; }
}
/* tube-stock views: a modest front section beside/above a side elevation */
.mviews { display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; width: 100%; height: 100%; }
.mviews .mv { width: min(100%, 520px); height: auto; max-height: 58%; }
.mviews .mv:last-child { max-height: 34%; }

/* ---- live hero viewer (2026-08-29): the product modeler in the hero ----
   Display piece, not a control: the canvas never grabs the wheel or drags,
   so it keeps its idle spin and the page keeps its scroll. Dimension
   furniture runs heavier than the product page's so it reads at hero size
   and carries the site's laser-blue. */
.hero__media canvas { pointer-events: none; }
.hero__media .dim-badge, .hero__media .dim-hint { display: none !important; }
.hero__media .dim-overlay .dim-label { font-size: 13.5px; letter-spacing: .07em; fill: #eaf3ff; }
.hero__media .dim-overlay .dim-label-bg { fill: rgba(5,8,12,.92); stroke: rgba(138,162,189,.5); }
.hero__media .dim-overlay .dim-line { stroke: #b9d2ec; stroke-width: 1.8; }
.hero__media .dim-overlay .dim-arrow { fill: #b9d2ec; }
.hero__media .dim-overlay .dim-lead { stroke: rgba(185,210,236,.75); }
.hero__media .dim-overlay .dim-ext { stroke: rgba(185,210,236,.5); }

/* ── auth pages in the HOME language (operator 2026-08-31) ──
   Eyebrow + display title + lead on the left with the laser art; the form
   on a hairline panel right. gate_note.js swaps the header and fills
   .auth__why when ?next= names an account-walled destination. */
.auth-home {
  display: grid;
  grid-template-columns: minmax(320px, 1.05fr) minmax(340px, .95fr);
  gap: clamp(40px, 7vw, 120px);
  align-items: center;
  max-width: 1100px;
  margin: clamp(48px, 9vh, 96px) auto 110px;
  padding: 0 28px;
}
.auth-home__eyebrow {
  font: 600 10.5px var(--mono);
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 14px;
}
.auth-home__title {
  margin: 0 0 16px;
  text-transform: uppercase;
  font-weight: 700;
  letter-spacing: -1px;   /* the home pages' negative display tracking */
  line-height: 1.04;
  font-size: clamp(30px, 4.6vw, 52px);
  color: var(--fg);
}
.auth-home__lead {
  color: var(--fg-2);
  font-size: 15px;
  line-height: 1.6;
  max-width: 46ch;
  margin: 0 0 18px;
}
.auth-home .auth__arc--laser { margin-top: 24px; max-width: 300px; }
.auth-home .auth__brandfoot { margin-top: 30px; }
.auth-home__panel { min-width: 0; }
.auth-home__panel .form-card {
  background: transparent;
  border: 0;
  border-radius: 0;
  border-top: 1px solid var(--line-2);
  padding: 26px 0 0;
}
.auth-home__panel .authswap { margin-top: 18px; }
@media (max-width: 860px) {
  .auth-home { grid-template-columns: 1fr; gap: 30px; margin-top: 34px; }
}

/* the catalog (tables) page also defaults to 110% (operator 2026-08-31);
   nothing here is viewport-height-sized, so no compensation needed */
body.is-zoom110 { zoom: 1.1; }

/* PHONES: the 990px table used to scroll sideways, with the price and Add
   button off-screen to the right (operator 2026-09-04: "mobile catalog rows
   showing price + Add inline"). Below 640px each row becomes a card: the
   part name across the top, the numbers as label: value pairs in two
   columns, and the qty stepper + price/Add cluster as the card's last row.
   The td[data-label] attribute is written by catalog.html's row renderer. */
@media (max-width: 640px) {
  .tablewrap { overflow: visible; border: 0; border-radius: 0; }
  .ptable { display: block; min-width: 0; }
  .ptable thead { display: none; }
  .ptable tbody { display: block; }
  .ptable tbody tr.prow {
    display: grid; grid-template-columns: 1fr 1fr; gap: 7px 14px;
    padding: 16px 2px; border-bottom: 1px solid var(--line);
  }
  .ptable tbody tr:not(.prow) { display: block; }
  .ptable tbody td { display: block; padding: 0; border: 0; white-space: normal;
    text-align: left; }
  .ptable tbody td:first-child, .ptable tbody td:last-child { padding: 0; }
  /* width:auto beats the desktop table's 25% column width, which otherwise
     wraps the part name into a narrow strip inside the full-width card */
  .ptable tbody td.col-part { grid-column: 1 / -1; width: auto; margin-bottom: 4px; }
  .ptable tbody td.col-part .t-name { font-size: 14.5px; }
  .ptable tbody td.num:not(.col-cart):not(.col-qty)::before {
    content: attr(data-label) ": "; color: var(--fg-3); font-family: var(--mono);
    font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  }
  .ptable tbody td.col-view { display: none; }          /* the name is the link */
  .ptable tbody td.col-qty, .ptable tbody td.col-cart {
    grid-column: span 1; padding-top: 8px; align-self: end;
  }
  .ptable tbody td.col-qty .rowqty { justify-content: flex-start; }
  .ptable tbody td.col-cart .rowcart { justify-content: flex-end; }
  .ptable tbody td.empty { grid-column: 1 / -1; padding: 20px 0; }
}

/* tab switch (operator 2026-08-31, fourth pass): the table NEVER empties
   or collapses - rows stay on screen and the CONTENT crossfades in place.
   Old rows dim with the arc's retract, the cells swap at the trough (still
   visible, so there is no blink), and the new rows brighten with the
   extend. Filters rebuild instantly on the click. */
#tbody { transition: opacity .3s var(--ease); }
#tbody.is-swapping { opacity: .25; }
@media (prefers-reduced-motion: reduce) { #tbody { transition: none; } }

/* ── checkout in the HOME language, complete rework (operator 2026-08-31:
   "needs a complete re work to match the minimal styling") ──
   The AUTH-page pattern the home look already owns: identity + the ORDER
   summary on the LEFT behind a hairline, the form on the RIGHT in the home
   field language (near-black inputs, faint hairlines, tracked micro
   labels), shipping as a quiet row list, one solid action. Big whitespace
   everywhere; no boxes anywhere. Scoped to .co-page. */
.co-eyebrow { font: 600 10.5px var(--mono); letter-spacing: .24em; text-transform: uppercase; color: var(--accent); margin: 0 0 12px; }
.co-page .cart-head { align-items: flex-end; padding-top: clamp(20px, 4vh, 44px); }
.co-page .cart-head h1 { font-size: clamp(32px, 5vh, 50px); font-weight: 700; letter-spacing: -1px; text-transform: uppercase; line-height: 1; }
.co-lead { color: var(--fg-2); font-size: 14.5px; line-height: 1.6; max-width: 54ch; margin: 14px 0 0; }
.co-page .cart-head .sub a { color: var(--fg-2); text-decoration: none; transition: color var(--dur); }
.co-page .cart-head .sub a:hover { color: var(--fg); }
.co-page .checkout-layout { column-gap: clamp(44px, 6vw, 100px); row-gap: 26px; padding-bottom: 90px; }
@media (min-width: 941px) {
  .co-page .checkout-layout { grid-template-columns: minmax(0, 1.25fr) minmax(280px, .75fr); }
  .co-page .checkout-layout .co-summary { grid-column: 2; grid-row: 1 / span 2; }
  .co-page .checkout-layout .form-card { grid-column: 1; grid-row: 1; }
  .co-page .checkout-layout .co-actions { grid-column: 1; grid-row: 2; }
}
/* the form: open ground, hairline sections, home fields */
.co-page .form-card { background: transparent; border: 0; border-radius: 0; padding: 0; }
.co-page .form-sec {
  font: 600 10px var(--mono); letter-spacing: .2em; text-transform: uppercase;
  color: var(--accent); border-bottom: 0; border-top: 1px solid var(--line);
  padding: 22px 0 0; margin: 34px 0 18px;
  display: flex; justify-content: space-between; align-items: baseline; gap: 14px;
}
.co-page .req-legend { float: none; color: var(--fg-3); letter-spacing: .1em; }
.co-page .field { margin-bottom: 16px; }
.co-page .field > label { font-size: 10.5px; letter-spacing: .12em; margin-bottom: 6px; color: var(--fg-3); }
.co-page .field input, .co-page .field select {
  border-radius: 0; background: #08090b; border-color: var(--line);
  height: 40px; font-size: 14px; padding: 0 13px;
}
.co-page .field input:focus, .co-page .field select:focus { border-color: var(--accent-line); }
.co-page .grid2, .co-page .grid4, .co-page .grid-addr { gap: 14px; }
.co-page .guest-note {
  background: transparent; border: 0; border-radius: 0;
  border-left: 2px solid var(--accent-line); padding: 4px 0 4px 16px; margin: 10px 0 30px;
}
/* shipping: a quiet row list, not a grid of boxes; the chosen row earns
   the accent rail (same gesture as the home accordion) */
.co-page .ship-grid { grid-template-columns: 1fr; gap: 0; border-bottom: 0; }
.co-page .ship-opt {
  border: 0; border-radius: 0;
  background: transparent; padding: 9px 2px; transition: background var(--dur);
}
.co-page .ship-opt:hover { background: rgba(127,160,204,.04); }
.co-page .ship-opt.on { background: transparent; }
.co-page .ship-opt .so-l { font-size: 13px; color: var(--fg-2); transition: color var(--dur); }
.co-page .ship-opt.on .so-l { color: var(--fg); }
.co-page .ship-opt .so-p { font-size: 11.5px; }
.co-page .pay-seg button { border-radius: 0; }
/* the summary: the left context column behind a hairline, like the auth head */
.co-page .co-summary {
  background: transparent; border: 0; border-radius: 0;
  border-left: 1px solid var(--line-2); padding: 4px 0 4px 32px;
}
.co-sum-title { font: 600 10px var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--accent); margin: 0 0 16px; }
/* the breakdown reads as a spec sheet (operator 2026-08-31): item lines
   ruled, then ONE rule before the quiet unruled money trio, then ONE rule
   before the total. Labels are mono micro-caps, values tabular. */
.co-page .co-line { padding: 8px 0; border-bottom: 0; }
.co-page .sum-row { border-bottom: 0; padding: 6px 0; font-size: 12px; }
.co-page .sum-row > span:first-child { font: 600 9.5px var(--mono); letter-spacing: .16em; text-transform: uppercase; color: var(--fg-3); }
.co-page .sum-row .mono { font-size: 13px; color: var(--fg-2); }
.co-page .co-line + .sum-row { margin-top: 16px; }
.co-page .sum-row.total { text-transform: uppercase; letter-spacing: .02em; font-weight: 700; font-size: 14px; border-top: 1px solid var(--line-2); margin-top: 12px; padding-top: 18px; }
.co-page .sum-row.total > span:first-child { font: inherit; color: var(--fg); letter-spacing: .02em; }
.co-page .sum-row.total .mono { font-size: 22px; color: var(--fg); }
/* the payer choice is its own full-width segmented control above the
   carrier list - it was squeezed into the section header (operator
   2026-08-31: "better positioned") */
.co-page .pay-seg {
  float: none; display: grid; grid-template-columns: 1fr 1fr;
  margin: 0 0 16px; border: 1px solid var(--line-2); border-radius: 0; overflow: hidden;
}
.co-page .pay-seg button { height: 38px; font-size: 10.5px; letter-spacing: .14em; padding: 0 10px; }
.co-page .pay-seg button + button { border-left: 1px solid var(--line-2); }
.co-page .pay-seg button.on { background: rgba(127,160,204,.1); color: var(--fg); box-shadow: inset 0 -2px 0 var(--accent); }
/* the ONE action: home solid (base look lives on #place in checkout.html) */
.co-page .co-actions { background: transparent; border: 0; border-radius: 0; padding: 0; }
.co-page .co-actions .btn--accent { border-radius: 0; height: 48px; font-size: 12.5px; }

/* checkout summary lines: hovering a part expands its Blender still under
   the line (operator 2026-08-31, "just like the cart page") - the cutout
   strip rides the page ground with no chrome, and the column grows on the
   same 0fr->1fr glide the site uses everywhere */
.co-page .co-art {
  display: grid; grid-template-rows: 0fr; opacity: 0;
  transition: grid-template-rows .45s var(--ease), opacity .4s ease;
}
.co-page .co-item:hover .co-art { grid-template-rows: 1fr; opacity: 1; }
.co-page .co-art__in { overflow: hidden; }
.co-page .co-art img { width: 100%; display: block; padding: 8px 0 4px; }
@media (prefers-reduced-motion: reduce) { .co-page .co-art { transition: none; } }

/* the reveal needs an affordance (operator 2026-08-31: raw text does not
   read as hoverable): the house "+" from the home accordion, rotating to
   x as the still expands */
.co-page .co-item.has-art .ci-name::before {
  content: "+";
  display: inline-block;
  margin-right: 9px;
  font: 300 14px var(--mono);
  color: var(--fg-3);
  transition: transform .35s var(--ease), color .35s;
}
.co-page .co-item.has-art:hover .ci-name::before { transform: rotate(45deg); color: var(--accent); }

/* terms acceptance at checkout: a required tick, quiet but unmissable */
/* the bill-to toggle sits between the address blocks, tighter than the
   terms line it borrows its look from */
.co-agree.co-bill { margin: 16px 0 2px; }
.co-agree { display: flex; gap: 11px; align-items: flex-start; margin: 22px 0 0;
  padding: 14px 0 0; border-top: 1px solid var(--line); cursor: pointer; }
.co-agree input { margin: 2px 0 0; flex: none; accent-color: var(--accent);
  width: 16px; height: 16px; }
.co-agree span { font-size: 12.5px; line-height: 1.55; color: var(--fg-2); }
.co-agree a { color: var(--accent); }
