/* PartWatch — application shell.
   Design system: Fixfactor OS (OS/docs/DESIGN-SYSTEM.md, v1.0, 19 Sep 2026).
   Values come from tokens.css, a verbatim copy of @fos/design-tokens
   (fos-tokens v1.0.0); day and night are light-dark() pairs and the register
   is chosen by color-scheme on <html>. Hover is a border-colour change, never
   a lift; shadows are structural only.

   Components read semantic tokens only (--color-*, --space-*, --radius-*,
   --font-*, --dur-*, --z-*); a literal here is a lint error
   (tests/test_design_system.py). Stage 2 (19 Sep 2026) moved every rule off
   the old ff-tokens names and onto the fOS component spec: sharp controls
   (radius 4), soft windows (12 / 16), round people and chips; hierarchy by
   weight (400 text, 500 labels, 600 titles, 700 display figures); hover only
   on rows that open something; status is a dot and a word. */

/* Schibsted Grotesk, self-hosted (OFL) - see fonts/LICENSE.txt. The same two
   subset files fixfactor.co.uk and the FOS console serve; latin-ext carries the
   Polish glyphs. Metric-matched fallback on Arial so the swap moves nothing. */
@font-face {
  font-family:"Schibsted Grotesk";
  font-style:normal;
  font-weight:var(--font-weight-regular) 900;
  font-display:swap;
  src:url("/fonts/schibsted-grotesk-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family:"Schibsted Grotesk";
  font-style:normal;
  font-weight:var(--font-weight-regular) 900;
  font-display:swap;
  src:url("/fonts/schibsted-grotesk-latin-ext.woff2") format("woff2");
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family:"Schibsted Grotesk Fallback";
  src:local("Arial");
  size-adjust:111.3%;
  ascent-override:87.75%;
  descent-override:23.16%;
  line-gap-override:0%;
}

/* Layout numbers PartWatch owns (not colours, not type): the list pane. */
:root{--list-w:minmax(340px, 420px)}

/* The register switch. index.html stamps data-theme (light / dark / auto
   resolved); color-scheme is what light-dark() reads. */
html{color-scheme:light}
html[data-theme="dark"]{color-scheme:dark}

/* Segmented control (fOS C): a view or mode switch. The active segment sits
   on raised with a hairline; colour stays neutral, position says "on". */
.vat,.mclasses,.mlevels,.lang,.cat-view{display:inline-flex;align-items:center;flex-shrink:0;background:var(--color-surface-sunken);border-radius:var(--radius-surface);padding:var(--space-0-5);gap:var(--space-0-5)}
.vat button,.mclass,.mlevels button,.lang button,.cat-view button{height:30px;padding:0 var(--space-3);border:1px solid transparent;border-radius:var(--radius-control);background:transparent;color:var(--color-text-secondary);font:inherit;font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);display:inline-flex;align-items:center;gap:var(--space-2);white-space:nowrap;cursor:pointer;transition:background-color var(--dur-fast) var(--ease-color),color var(--dur-fast) var(--ease-color);line-height:var(--font-line-sm)}
.vat button:hover,.mclass:hover,.mlevels button:hover,.lang button:hover,.cat-view button:hover{color:var(--color-text-primary)}
.vat button[aria-pressed="true"],.mlevels button[aria-pressed="true"],
.mclass[aria-selected="true"],.lang button[aria-pressed="true"],.cat-view button[aria-pressed="true"]{background:var(--color-surface-raised);border-color:var(--color-line-subtle);color:var(--color-text-primary)}
::-webkit-scrollbar{width:11px;height:11px}
::-webkit-scrollbar-thumb{
  background:var(--color-line-default);border-radius:var(--radius-pill);
  border:3px solid transparent;background-clip:content-box;
}
::-webkit-scrollbar-track{background:transparent}

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}

/* The [hidden] attribute must always win. The UA sheet's display:none loses
   to any author display (.row{display:block} beat it), which made the search
   "filter" the list in data while every row stayed visibly on screen — the
   exact complaint that got reported as "search does not narrow the list". */
[hidden]{display:none!important}

html,body{height:100%}
/* One bold per block (fOS v3.5): an emphasis is 600, never 700 - 700 is a
   KPI figure's alone. Inside a table a value is regular; the row is the door. */
strong,b{font-weight:var(--font-weight-semibold)}
td strong,td b{font-weight:inherit}
body{
  font-family:var(--font-family-sans);
  font-size:var(--font-size-md);
  line-height:var(--font-line-md);
  color:var(--color-text-primary);
  background:var(--color-surface-canvas);
  overscroll-behavior:none;
}

:focus-visible{outline:2px solid var(--color-state-focus);outline-offset:2px}
/* A field shows focus with its own frame, never a second ring (fOS v3.84,
   Marcin 1 Oct 2026: "no double frames on an active field"): the border takes
   the focus colour plus one inset pixel of it, so the frame is 2 px, one line,
   no gap and no layout shift. The app-wide ring is kept out of the way with a
   transparent outline, which forced-colours mode still draws. */
input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=button]):not([type=submit]):focus-visible,
select:focus-visible,textarea:focus-visible{border-color:var(--color-state-focus);box-shadow:inset 0 0 0 1px var(--color-state-focus);outline-color:transparent}

/* Number inputs: no spinner arrows anywhere — prices are typed, not nudged
   a penny at a time (owner rule, 20 Aug 2026). */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
input[type="number"]{-moz-appearance:textfield;appearance:textfield}

/* ============================================================ APP SHELL */
/* Option B — Calm workbench: one top bar (brand, section pills, controls),
   the work surface below it at full width. */

.app{
  display:grid;
  /* minmax(0,1fr), not auto:an auto track sizes to content and lets a wide
     row push the whole page into horizontal scroll on phones. */
  grid-template-columns:minmax(0,1fr);
  grid-template-rows:auto minmax(0,1fr);
  grid-template-areas:"topbar" "main";
  height:100dvh;
}

/* ---- the top bar ----
   Three parts on one line: the wordmark, the sections, and the tools (the
   price age and Check Parts, VAT, theme, account). Under 1200 the sections
   take a row of their own under the other two; under 640 VAT and theme move
   into the account menu and Check Parts keeps only its icon. Every control
   on the bar is one height, so they share one centre line. */
.topbar{
  grid-area:topbar;
  /* 64, not 56: the bar had no air above and below its controls (Marcin,
     3 Oct 2026). Everything on it is 32 high, centred on one line. */
  --size-header:var(--space-16);
  /* The wordmark, then the sections a clear step away from it, then the
     tools at the far edge: the sections read as the page's navigation, not
     as a label of the logo, and the free room sits between them and the
     tools. 12 at the narrowest single row, 40 once there is room. */
  display:grid;grid-template-columns:max-content minmax(0,1fr) max-content;
  align-items:center;
  column-gap:clamp(var(--space-3),calc((100vw - 75rem) / 4 + var(--space-3)),var(--space-10));
  padding:0 var(--space-6);
  background:var(--color-surface-raised);
  border-bottom:1px solid var(--color-line-subtle);
}
.brand-lockup{
  display:flex;align-items:center;flex:none;min-height:var(--size-header);
  text-decoration:none;color:inherit;border-radius:var(--radius-tiny);
}
.brand-logo{display:block;height:20px;width:auto}
.brand-logo-dark{display:none}
[data-theme="dark"] .brand-logo-light{display:none}
[data-theme="dark"] .brand-logo-dark{display:block}

/* The sections sit on the bar's bottom edge, so the position mark is the
   bar's own line. The row scrolls inside itself if it ever runs out of room. */
.topbar .nav{
  flex:1 1 0;min-width:0;align-self:stretch;
  border-bottom:0;gap:var(--space-1);justify-self:start;max-width:100%;
}
.topbar .nav .utab{height:var(--size-header)}


/* Three groups on one rhythm: the prices (their age and the check that
   refreshes them), how this screen shows them (VAT, theme), and whose it is.
   8 inside a group, 24 between groups. */
.topbar-tools{
  display:flex;align-items:center;gap:var(--space-2);flex:none;margin-left:auto;
  min-height:var(--size-header);
}
.topbar-tools > .vat-switch{margin-left:var(--space-4)}
.topbar-tools > .acct{margin-left:var(--space-2)}
/* Check Parts is the bar's one action, not its loudest object: 32 high like
   everything beside it, the label at body size. */
.topbar .run-btn{height:var(--space-8);padding:0 var(--space-3);font-size:var(--font-size-md);gap:var(--space-1-5)}
.topbar .theme-toggle{width:var(--space-8);height:var(--space-8)}
.topbar .acct-btn{width:var(--space-8);height:var(--space-8);padding:0;border-radius:var(--radius-pill)}

/* The VAT switch (fOS .switch): a track, a knob and the word. It states a
   preference, so the accent fills it, never green. */
.vat-switch{display:inline-flex;align-items:center;gap:var(--space-2);height:var(--space-8);padding:0 var(--space-2);
  border:0;border-radius:var(--radius-control);background:transparent;color:var(--color-text-secondary);
  font:inherit;font-size:var(--font-size-md);line-height:var(--font-line-md);cursor:pointer;white-space:nowrap}
.vat-switch:hover{color:var(--color-text-primary);background:var(--color-state-hover)}
.switch-track{position:relative;display:inline-block;flex:none;width:calc(var(--space-0-5) * 17);height:var(--space-5);
  border-radius:var(--radius-pill);background:var(--color-line-default);transition:background-color var(--dur-fast) var(--ease-color)}
.switch-thumb{position:absolute;top:var(--space-0-5);left:var(--space-0-5);width:var(--space-4);height:var(--space-4);
  border-radius:var(--radius-pill);background:var(--color-surface-raised);box-shadow:var(--shadow-1);
  transition:transform var(--dur-fast) var(--ease-enter)}
.vat-switch[aria-checked="true"]{color:var(--color-text-primary)}
.vat-switch[aria-checked="true"] .switch-track{background:var(--color-customer-solid)}
.vat-switch[aria-checked="true"] .switch-thumb{transform:translateX(calc(var(--space-0-5) * 7))}
@media (prefers-reduced-motion:reduce){.switch-track,.switch-thumb{transition:none}}

/* One slot, fixed width: the price age at rest, the check's progress while
   it runs. Nothing beside it moves when the words change. */
.freshness{
  display:flex;flex-direction:column;justify-content:center;gap:var(--space-1);
  width:8.5rem;min-height:var(--size-control-md);margin:0;white-space:nowrap;
}
.freshness:has(.pw-status){flex-direction:row;align-items:center;justify-content:flex-end;gap:var(--space-2)}
/* The dot says the state (its colour, and its word to a screen reader); the
   time sits beside it. "Connected 5 days a…" was the word squeezing the time
   out of a fixed slot (3 Oct 2026). */
.freshness .pw-status{font-size:0;gap:0}
.freshness-when{font-size:var(--font-size-sm);color:var(--color-text-secondary);overflow:hidden;text-overflow:ellipsis;line-height:var(--font-line-sm)}
.fr-text{display:flex;justify-content:space-between;gap:var(--space-2);font-size:var(--font-size-sm);color:var(--color-text-secondary);line-height:var(--font-line-sm)}
.fr-long{overflow:hidden;text-overflow:ellipsis}
.fr-bar{position:relative;display:block;height:var(--space-1);border-radius:var(--radius-pill);background:var(--color-surface-sunken);overflow:hidden}
.fr-fill{display:block;height:100%;background:var(--color-customer-solid);border-radius:var(--radius-pill);transition:width var(--dur-slow) var(--ease-color)}
/* Even at 0 of 10 something has to move, or the first long supplier reads as
   a hang. */
.fr-fill.creeping{position:relative}
.fr-fill.creeping::after{
  content:'';position:absolute;inset:0;width:34%;
  background:linear-gradient(90deg,transparent,var(--color-surface-raised),transparent);
  animation:rp-sweep calc(var(--dur-base) * 8) linear infinite;
}
@keyframes rp-sweep{from{transform:translateX(-40%)}to{transform:translateX(300%)}}

/* The theme button shows the current choice. */
.theme-toggle svg{display:none}
.theme-toggle[data-choice="auto"] .t-auto,.theme-toggle[data-choice="light"] .t-light,
.theme-toggle[data-choice="dark"] .t-dark{display:block}

/* The account: an initial on ink, round because it is a person. */
.acct{position:relative;display:inline-flex}
.acct > .pw-menu{top:calc(100% + var(--space-1));right:0}
.acct-avatar{
  display:grid;place-items:center;width:var(--space-8);height:var(--space-8);border-radius:var(--radius-pill);
  background:var(--color-internal-solid);color:var(--color-internal-on-solid);overflow:hidden;
  font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);line-height:1;
}
/* A person's photo, when they have given one: round because it is a person. */
.acct-avatar img,.user-avatar img{display:block;width:100%;height:100%;object-fit:cover}
.pw-menu-head{margin:0;padding:var(--space-2) var(--space-3) var(--space-1);font-size:var(--font-size-sm);color:var(--color-text-secondary);line-height:var(--font-line-sm)}
.pw-menu-head span{color:var(--color-text-primary)}
.pw-menu-sep{border:0;border-top:1px solid var(--color-line-subtle);margin:var(--space-1) 0}
.acct-state{font-size:var(--font-size-sm);color:var(--color-text-secondary);line-height:var(--font-line-sm)}
.acct-phone{display:none}

/* A counter with nothing in it is not a zero, it is a badge that has not
   loaded — draw nothing rather than an empty pill. */
.cnt:empty{display:none}
/* The one loud counter: a count that asks for attention. */
.cnt.is-danger{background:var(--color-danger-solid);color:var(--color-danger-on-solid);font-weight:var(--font-weight-semibold)}


/* A note instead of a title=: readable on a phone, reachable from a keyboard.
   The wrapper positions it; 04-nav.js fills it from data-note. */
.tipw{position:relative;display:inline-flex}
.tipw > .pw-note{
  top:calc(100% + var(--space-1));right:0;
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity var(--dur-fast) var(--ease-color),visibility var(--dur-fast) var(--ease-color);
}
.tipw:hover > .pw-note,.tipw:focus-within > .pw-note{opacity:1;visibility:visible}

/* ---- search ---- */
/* The scope bar atop the Pricing list: search, then Type - Brand - Model. */
.scope{
  padding:var(--space-3) var(--space-3) var(--space-2);
  border-bottom:1px solid var(--color-line-subtle);
  background:var(--color-surface-raised);
  display:flex;flex-direction:column;gap:var(--space-2);
}
.scope-row{display:flex;align-items:center;gap:var(--space-2)}
.scope-row .fsel{flex:1;min-width:0}
.scope-row .btn{flex:none}

/* ---- VAT switch: the toggle Marcin asked for, always visible ---- */

.spacer{flex:1}

/* ---- buttons ---- */
/* Button (fOS C): secondary by default — hairline on raised. Solid teal only
   for the action that sends or saves, one per view. Disabled is a token pair,
   never opacity. Sizes sm 28 / md 36 / lg 44, radius 4. */
.btn{
  font:inherit;font-size:var(--font-size-md);font-weight:var(--font-weight-semibold);
  display:inline-flex;align-items:center;gap:var(--space-2);
  height:var(--size-control-md);padding:0 var(--space-3);
  border-radius:var(--radius-control);border:1px solid var(--color-line-control);
  background:var(--color-surface-raised);color:var(--color-text-primary);
  cursor:pointer;white-space:nowrap;
  transition:background-color var(--dur-fast) var(--ease-color),border-color var(--dur-fast) var(--ease-color),color var(--dur-fast) var(--ease-color);
}
.btn:hover:not(:disabled){background:var(--color-state-hover)}
.btn:active:not(:disabled){background:var(--color-state-press)}
.btn:disabled{background:var(--color-state-disabled-bg);color:var(--color-text-disabled);border-color:transparent;cursor:not-allowed}
a.btn{text-decoration:none}
/* The solid button (fOS v3.31): ink for what stays with us - save, check,
   accept - and the customer's teal only for what reaches the customer: a
   push to the live shop. One solid per form or panel. */
.btn-primary{background:var(--color-internal-solid);color:var(--color-internal-on-solid);border-color:transparent}
.btn-primary:hover:not(:disabled){background:color-mix(in oklab,var(--color-internal-solid) 90%,var(--color-surface-raised))}
.btn-primary:active:not(:disabled){background:color-mix(in oklab,var(--color-internal-solid) 80%,var(--color-surface-raised))}
.btn-customer{background:var(--color-customer-solid);color:var(--color-customer-on-solid);border-color:transparent}
.btn-customer:hover:not(:disabled){background:color-mix(in oklab,var(--color-customer-solid) 90%,var(--color-text-primary))}
.btn-customer:active:not(:disabled){background:color-mix(in oklab,var(--color-customer-solid) 80%,var(--color-text-primary))}
.btn-sm{height:var(--size-control-sm);padding:0 var(--space-2);font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);line-height:var(--font-line-sm)}
.btn-icon{width:var(--size-control-md);padding:0;justify-content:center}

/* ============================================================ MAIN */

/* Back to the list, phone widths only — desktop shows both panes at once. */
.detail-back{
  display:none;align-items:center;gap:var(--space-2);
  font:inherit;font-size:var(--font-size-md);font-weight:var(--font-weight-semibold);color:var(--color-text-primary);
  background:var(--color-surface-raised);border:1px solid var(--color-line-subtle);
  border-radius:var(--radius-control);height:var(--size-control-md);padding:0 var(--space-3);
  margin-bottom:var(--space-3);cursor:pointer;
}
.detail-back:hover{background:var(--color-state-hover)}

/* [FIX] clip, not hidden: the slash motif sits 4vw past the right edge, and
   a hidden box still scrolls programmatically (focus, scrollIntoView), which
   shoved the Find a Part page 51px left on every search. clip cannot scroll. */
.main{grid-area:main;overflow:clip;position:relative;display:flex;flex-direction:column;min-height:0}
.main > .banner{flex:none;margin:var(--space-3) var(--space-6) 0}
.view{display:none;position:relative}
.view.active{display:block;flex:1;min-height:0}

/* The brand slash: one per page, bottom-right, behind the content. */
/* The brand slash: one per page, whole, bottom-right, behind the content.
   A signature is quiet — it was a 26vmax wedge cut off by the viewport edge
   at 12–28 % and read as a shape, not a mark. */
/* Large and faint: small it reads as an artefact, dark it pulls the eye.
   light-dark() is a colour function — on opacity the declaration was dropped
   and the slash drew at full strength — so one rule per register. */
.pw-slash{
  position:absolute;right:-2vw;bottom:-6vh;
  width:34vmax;height:auto;
  color:var(--color-customer-line);opacity:.045;
  pointer-events:none;
}
html[data-theme="dark"] .pw-slash{opacity:.07}

/* ---- editorial view headers (Option B) ---- */
.view-h1{
  font-size:var(--font-size-xl);line-height:var(--font-line-xl);font-weight:var(--font-weight-semibold);
  letter-spacing:-.01em;color:var(--color-text-primary);margin-bottom:var(--space-1);
}
.view-lead{
  font-size:var(--font-size-md);line-height:var(--font-line-md);color:var(--color-text-secondary);
  margin-bottom:var(--space-6);max-width:72ch;
}

/* ---- master / detail ---- */
.split{
  display:grid;
  grid-template-columns:var(--list-w) 1fr;
  /* minmax(0,1fr) is the whole scroll fix. A grid row defaults to `auto`,
     which grows with content — so a 965-row list quietly stretched the pane
     taller than the screen and overflow-y:auto had nothing to do. The row must
     be allowed to be SMALLER than its content before a child can scroll. */
  grid-template-rows:minmax(0,1fr);
  height:100%;
}

.list-pane{
  display:flex;flex-direction:column;
  background:var(--color-surface-raised);
  border-right:1px solid var(--color-line-subtle);
  min-width:0;min-height:0;
}
.list-head{
  display:flex;align-items:center;gap:var(--space-2);
  padding:var(--space-2) var(--space-3);
  border-bottom:1px solid var(--color-line-subtle);
  min-height:var(--size-control-lg);
}

/* Issue reason tags — visible only while the Issues filter is on. They are
   pw-tags now; this only decides when the group is on screen. */
.row-why{display:none}
.list.issues-on .row-why{display:inline-flex;gap:var(--space-1);flex-wrap:wrap}
/* Filter chip (fOS C): a bordered pill, 28 high; "on" is ink. Facts wear no
   hover — only a chip that filters does. */
.chip{
  font:inherit;font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);white-space:nowrap;
  display:inline-flex;align-items:center;gap:var(--space-2);
  height:var(--size-control-sm);padding:0 var(--space-3);
  border:1px solid var(--color-line-control);background:var(--color-surface-raised);
  color:var(--color-text-primary);border-radius:var(--radius-control);cursor:pointer;
  transition:background-color var(--dur-fast) var(--ease-color),color var(--dur-fast) var(--ease-color);line-height:var(--font-line-sm);}
.chip:hover{background:var(--color-state-hover)}
.chip[aria-pressed="true"]{background:var(--color-customer-tint-strong);color:var(--color-customer-text);border-color:transparent}
.chip[aria-pressed="true"] .cnt{background:var(--color-surface-raised);color:inherit}

.list{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}
.row{
  /* content-visibility keeps a 1,000-row list cheap without a virtualiser */
  content-visibility:auto;
  contain-intrinsic-size:auto 62px;
  display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:var(--space-3);
  width:100%;text-align:left;
  padding:var(--space-2-5) var(--space-4);
  border:none;border-bottom:1px solid var(--color-surface-sunken);
  background:none;font:inherit;cursor:pointer;
  transition:background var(--dur-fast) var(--ease-color);
}
.row:hover{background:var(--color-state-row-hover)}
/* Selection is a full-row tint — no edge accents (owner rule). */
.row[aria-selected="true"]{background:var(--color-state-selected)}
.row-title{
  font-size:var(--font-size-md);font-weight:var(--font-weight-regular);color:var(--color-text-primary);
  display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.row-meta{
  grid-column:1 / -1;
  display:flex;align-items:center;flex-wrap:wrap;gap:var(--space-1) var(--space-2);margin-top:var(--space-0-5);
  font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm);}
/* The service name gives way first: status and price are the facts the
   row is scanned for, the service repeats what the title already said. */
.row-price{font-size:var(--font-size-md);color:var(--color-text-primary);font-variant-numeric:tabular-nums;text-align:right}
/* The quiet notes after the verdict: words, not tags. */
.row-note::before{content:"·";margin-right:var(--space-2);color:var(--color-text-tertiary)}
.row-note.is-danger{color:var(--color-danger-text)}
.row-note.is-customer{color:var(--color-customer-text)}

/* The list's own line: how many, in what order, and what narrows it. */
.list-count{font-size:var(--font-size-sm);line-height:var(--font-line-sm);color:var(--color-text-secondary);white-space:nowrap}
.list-count b{font-weight:var(--font-weight-semibold);color:var(--color-text-primary)}
/* The order is a way of reading the list, not a filter on it: no frame, and
   it never fills the way an active filter does. */
.list-head{flex-wrap:wrap}
.list-head .fsel-quiet{flex:none;border-color:transparent;background:transparent}
.list-head .fsel-quiet:hover{background:var(--color-state-hover)}
.list-head .fsel-quiet[data-active="true"]{background:transparent;color:inherit}
.list-focus{flex-basis:100%;display:flex}
.list-focus[hidden]{display:none}
#focusChip .ico{margin-right:calc(-1 * var(--space-1))}

/* ---- the start screen: what needs a decision ---- */
.start h1{margin-bottom:var(--space-1)}
.start .detail-sub{margin-bottom:var(--space-6)}
.start-tiles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-4);margin-bottom:var(--space-6)}
.start-tile{display:flex;flex-direction:column;align-items:flex-start;gap:var(--space-1-5);padding:var(--space-5);
  font:inherit;text-align:left;color:var(--color-text-primary);cursor:pointer;
  background:var(--color-surface-raised);border:1px solid var(--color-line-subtle);border-radius:var(--radius-card);
  transition:border-color var(--dur-fast) var(--ease-color)}
.start-tile:hover:not(:disabled){border-color:var(--color-line-strong)}
.start-tile:disabled{cursor:default}
.start-num{font-size:var(--font-size-3xl);line-height:var(--font-line-3xl);font-weight:var(--font-weight-bold)}
.start-tile:disabled .start-num{color:var(--color-text-tertiary)}
.start-title{display:inline-flex;align-items:center;gap:var(--space-2);font-size:var(--font-size-md);font-weight:var(--font-weight-semibold)}
.start-note{font-size:var(--font-size-sm);line-height:var(--font-line-sm);color:var(--color-text-secondary)}
.start-go{display:inline-flex;align-items:center;gap:var(--space-1);margin-top:var(--space-1);
  font-size:var(--font-size-sm);line-height:var(--font-line-sm);color:var(--color-customer-text)}
.start-go .ico{transition:transform var(--dur-fast) var(--ease-enter)}
.start-tile:hover .start-go .ico{transform:translateX(2px)}
.start-tile:disabled .start-go{color:var(--color-text-tertiary)}
.start-all{display:flex;flex-direction:column;gap:var(--space-3);padding:var(--space-5)}
.start-all h2{margin:0;font-size:var(--font-size-md);line-height:var(--font-line-md);font-weight:var(--font-weight-semibold)}
.start-bar{display:flex;gap:var(--space-0-5);height:var(--space-2-5);border-radius:var(--radius-pill);overflow:hidden}
.start-bar > span{flex:1 1 0;min-width:var(--space-1)}
.start-legend{display:flex;flex-wrap:wrap;gap:var(--space-1-5) var(--space-5);margin:0;padding:0;list-style:none;
  font-size:var(--font-size-sm);line-height:var(--font-line-sm);color:var(--color-text-secondary)}
.start-legend li{display:inline-flex;align-items:center;gap:var(--space-2)}
.start .dot{display:inline-block;flex:none;width:var(--space-2);height:var(--space-2);border-radius:var(--radius-pill)}
.start .is-success{background:var(--color-success-solid)}
.start .is-customer{background:var(--color-customer-line)}
.start .is-warning{background:var(--color-warning-line)}
/* In the dark the line tones sink into the surface; the bright member of
   each pair is the solid one there. */
[data-theme="dark"] .start .is-customer{background:var(--color-customer-solid)}
[data-theme="dark"] .start .is-warning{background:var(--color-warning-solid)}
.start .is-danger{background:var(--color-danger-solid)}
.start .is-neutral{background:var(--color-neutral-line)}
.start .is-empty{background:var(--color-surface-sunken);box-shadow:inset 0 0 0 1px var(--color-line-subtle)}
@media (prefers-reduced-motion:reduce){.start-tile,.start-go .ico{transition:none}}
@media (max-width:640px){.start-tiles{grid-template-columns:1fr}}

.detail{overflow-y:auto;padding:var(--space-6) var(--space-8);min-width:0;min-height:0}
.detail-inner{max-width:1000px}

/* ---- detail header ---- */
.detail h1{
  font-size:var(--font-size-xl);line-height:var(--font-line-xl);font-weight:var(--font-weight-semibold);
  letter-spacing:-.01em;color:var(--color-text-primary);margin-bottom:var(--space-1);
}
/* Device class, brand, service: a sentence, because that is what they are.
   Six tinted pills made facts look like judgements about the job. */
.detail-sub{
  font-size:var(--font-size-sm);color:var(--color-text-tertiary);margin:0 0 var(--space-2);line-height:var(--font-line-sm);}
.detail-states{margin-bottom:var(--space-5)}

/* The numbers band (pw-honest-number): the figure, what it counts, and the
   clock saying when it was true. Colour never lands on the figure. */
.pw-kpis{margin-bottom:var(--space-6)}
.pw-kpi .pw-verdict{margin-top:var(--space-1)}
.kpi-of{font-size:var(--font-size-md);font-weight:var(--font-weight-regular);color:var(--color-text-tertiary)}
.kpi-word{display:block;margin:var(--space-1) 0}
/* A count that can be acted on is a door to the thing it counts. */
.kpi-door{display:block;width:100%;text-align:left;font:inherit;cursor:pointer;
  transition:border-color var(--dur-fast) var(--ease-color)}
.kpi-door:hover{border-color:var(--color-line-strong)}

.section-title{
  font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);
  color:var(--color-text-tertiary);margin:0 0 var(--space-2);line-height:var(--font-line-sm);}

/* ---- the variants table ----
   One row per variant, its facts side by side; the open one holds the
   equation. One open at a time, so the screen never stacks five equations.
   The open row is told by a tint across the whole row, never by an edge. */
.detail-summary{display:flex;align-items:flex-start;gap:var(--space-2);margin:0 0 var(--space-5);
  padding:var(--space-2-5) var(--space-3);border-radius:var(--radius-control);
  background:var(--color-customer-tint);color:var(--color-customer-text);
  font-size:var(--font-size-md);line-height:var(--font-line-md)}
.detail-summary .ico{flex:none;margin-top:var(--space-0-5)}
.detail-summary.is-warning{background:var(--color-warning-tint);color:var(--color-warning-text)}

.vtable-card{margin-bottom:var(--space-6)}
.vtable,.ptable{width:100%;border-collapse:collapse}
.vtable th,.vtable td,.ptable th,.ptable td{padding:var(--space-3);text-align:left;vertical-align:middle;
  font-size:var(--font-size-md);line-height:var(--font-line-md)}
.vtable thead th,.ptable thead th{padding-top:var(--space-3);padding-bottom:var(--space-2);
  font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);color:var(--color-text-tertiary);
  line-height:var(--font-line-sm);white-space:nowrap;border-bottom:1px solid var(--color-line-subtle)}
.vtable thead small,.ptable thead small{color:var(--color-text-tertiary)}
.vtable .num,.ptable .num{text-align:right;font-variant-numeric:tabular-nums}
.vtable tr > :first-child,.ptable tr > :first-child{padding-left:var(--space-5)}
.vtable tr > :last-child,.ptable tr > :last-child{padding-right:var(--space-5)}

.vrow{cursor:pointer;transition:background-color var(--dur-fast) var(--ease-color)}
/* Rows are divided by the table's own hairline (tbody td); the row headers
   need it too, and the last visible row needs none against the card edge. */
.vtable tbody th,.ptable tbody th{border-bottom:1px solid var(--color-line-subtle)}
.vtable tbody tr:nth-last-child(2):has(+ tr[hidden]) > *,
.ptable tbody tr:nth-last-child(2):has(+ tr[hidden]) > *{border-bottom:0}
.vrow:hover{background:var(--color-state-row-hover)}
.vrow.is-open{background:var(--color-state-selected)}
/* The name is the row's button; the global focus ring marks it. */
.vrow-btn{padding:0;border:0;background:none;font:inherit;font-weight:var(--font-weight-semibold);color:var(--color-text-primary);text-align:left;cursor:pointer;border-radius:var(--radius-tiny)}
.vcell-name{font-weight:var(--font-weight-regular)}
.vcell-name .pw-status{margin-left:var(--space-2)}
.vcell-site small{display:block;font-size:var(--font-size-sm);line-height:var(--font-line-sm);color:var(--color-text-tertiary)}
.vcell-part{color:var(--color-text-secondary);white-space:nowrap}
.vcell-part .num{color:var(--color-text-primary)}
.vcell-who{color:var(--color-text-secondary)}
.vcell-verdict .pw-verdict .num{color:var(--color-text-secondary)}
.vcell-chev{width:var(--space-8);text-align:right;color:var(--color-text-secondary)}
.vcell-chev .ico{transition:transform var(--dur-fast) var(--ease-enter)}
.vrow.is-open .vcell-chev .ico{transform:rotate(180deg)}
@media (prefers-reduced-motion:reduce){.vrow,.vcell-chev .ico{transition:none}}

.vtable .vdetail > td{padding:0}
.vdetail .variant{padding:var(--space-5);background:var(--color-surface-canvas)}

.variant-part{
  display:flex;align-items:center;gap:var(--space-2) var(--space-3);flex-wrap:wrap;
  margin-top:var(--space-4);padding-top:var(--space-3);border-top:1px solid var(--color-line-subtle);
  font-size:var(--font-size-sm);line-height:var(--font-line-sm);
}
.variant-part > .pw-label{margin:0;color:var(--color-text-tertiary)}
.variant-part > .btn{margin-left:auto}
.variant-part .none{color:var(--color-text-tertiary);font-style:normal}
.part-title{color:var(--color-text-primary)}
.part-open{display:inline-flex;align-items:center;gap:var(--space-1)}

/* Colours that share a row: their names as quiet chips beside the model, and
   under the equation one line per colour with the part it is priced from. */
.vrow-chips{display:inline-flex;flex-wrap:wrap;gap:var(--space-1);margin-left:var(--space-2);vertical-align:middle}
.colour-parts{margin-top:var(--space-4);padding-top:var(--space-3);border-top:1px solid var(--color-line-subtle)}
.colour-parts > .pw-label{color:var(--color-text-tertiary)}
.colour-part{display:grid;grid-template-columns:7rem minmax(0,1fr) auto auto auto;gap:var(--space-1) var(--space-3);align-items:center;
  padding:var(--space-2) 0;border-top:1px solid var(--color-line-subtle);font-size:var(--font-size-sm);line-height:var(--font-line-sm)}
.colour-part:first-of-type{border-top:0}
.colour-part b{font-weight:var(--font-weight-semibold);color:var(--color-text-primary)}
.colour-part-what{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-1) var(--space-2);min-width:0}
@media (max-width:640px){
  .colour-part{grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"c p" "w w" "g b"}
  .colour-part > b{grid-area:c}.colour-part > .colour-part-what{grid-area:w}.colour-part > .num{grid-area:p;text-align:right}
}
/* Add-ons under their base variant: the same repair plus a fee, one quiet
   line each. A missing price is the one thing on it that is a problem. */
.addons{list-style:none;margin:var(--space-3) 0 0;padding:var(--space-3) 0 0;border-top:1px solid var(--color-line-subtle);
  display:flex;flex-direction:column;gap:var(--space-2)}
.addon{display:flex;align-items:center;flex-wrap:wrap;gap:var(--space-1) var(--space-3);
  font-size:var(--font-size-sm);line-height:var(--font-line-sm);color:var(--color-text-secondary)}
.addon-name{color:var(--color-text-primary)}
.addon-fee{color:var(--color-text-tertiary)}
.addon-warn{color:var(--color-warning-text)}
.addon .btn{margin-left:auto}

/* ---- the parts table: one list, for the open variant ---- */
.parts-head{display:flex;align-items:baseline;gap:var(--space-2) var(--space-3);flex-wrap:wrap;padding:var(--space-4) var(--space-5) var(--space-3)}
.parts-head h2{margin:0;font-size:var(--font-size-md);line-height:var(--font-line-md);font-weight:var(--font-weight-semibold)}
.parts-sub{font-size:var(--font-size-sm);line-height:var(--font-line-sm);color:var(--color-text-tertiary)}
.parts-filters{display:flex;gap:var(--space-2);flex-wrap:wrap;margin-left:auto;align-self:center}
.parts-filters:empty{display:none}
.parts-body > .sk{margin:0 var(--space-5) var(--space-2)}
.parts-body > .pw-empty{margin:0 var(--space-5) var(--space-4)}
.prow:hover{background:var(--color-state-row-hover)}
.prow.in-use{background:var(--color-state-selected)}
.prow-btn{padding:0;border:0;background:none;font:inherit;color:var(--color-text-primary);text-align:left;cursor:pointer;border-radius:var(--radius-tiny)}
.prow-btn:hover{text-decoration:underline;text-underline-offset:2px}
.pcell-title{font-weight:var(--font-weight-regular)}
.pcell-title .pw-status{margin-left:var(--space-2)}
.prow.gone .pcell-title,.prow.gone .pcell-price{color:var(--color-text-secondary)}
.pcell-sup,.pcell-when,.pcell-use{white-space:nowrap}
.pcell-when strong{font-weight:var(--font-weight-regular)}
.pcell-when small{margin-left:var(--space-1);font-size:var(--font-size-sm);color:var(--color-text-tertiary)}
.pcell-cost{color:var(--color-text-secondary)}
.pcell-use{text-align:right}
.pcell-also{display:block;margin-top:var(--space-1);font-size:var(--font-size-xs);line-height:var(--font-line-xs);color:var(--color-text-tertiary)}
.ptable .pmore > td{padding-top:0}
.pmore-in{display:flex;flex-direction:column;gap:var(--space-2);padding:var(--space-1) 0 var(--space-2)}
.pmore-keep{margin:0;font-size:var(--font-size-sm);line-height:var(--font-line-sm);color:var(--color-text-secondary)}
.parts-foot{display:flex;align-items:center;gap:var(--space-2) var(--space-3);flex-wrap:wrap;
  padding:var(--space-3) var(--space-5);border-top:1px solid var(--color-line-subtle)}
.parts-foot .finds-more{margin:0}
.parts-note{font-size:var(--font-size-sm);line-height:var(--font-line-sm);color:var(--color-text-tertiary)}
.cell-lbl{display:none}

/* On a phone both tables become stacks of small cards: the header row goes,
   each row lays its cells out in a grid, and a short label stands in for a
   column name where the figure alone would not say what it is. */
@media (max-width:640px){
  .vtable,.vtable tbody,.ptable,.ptable tbody{display:block}
  .vtable thead,.ptable thead,.vcell-part{display:none}
  .cell-lbl{display:inline;color:var(--color-text-tertiary);font-size:var(--font-size-sm)}
  .vtable tr > :first-child,.vtable tr > :last-child,
  .ptable tr > :first-child,.ptable tr > :last-child{padding:0}

  .vrow{display:grid;grid-template-columns:minmax(0,1fr) auto var(--space-6);
    grid-template-areas:"name site chev" "verdict sug chev";gap:var(--space-1) var(--space-3);
    align-items:center;padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--color-line-subtle)}
  .vrow > *{padding:0;border:0}
  .vtable .vrow > th,.vtable .vrow > td{padding:0;border-bottom:0}
  .vcell-name{grid-area:name}
  .vcell-site{grid-area:site}
  .vcell-verdict{grid-area:verdict}
  .vcell-sug{grid-area:sug;font-size:var(--font-size-sm)}
  .vcell-chev{grid-area:chev;width:auto}
  .vdetail,.vdetail > td{display:block}
  .vdetail .variant{padding:var(--space-4)}
  .pw-eqsug{margin-left:0;align-items:flex-start;flex-basis:100%}
  .pw-eqsug-row{justify-content:flex-start}

  .parts-head{padding:var(--space-4) var(--space-4) var(--space-3)}
  .parts-filters{margin-left:0;flex-basis:100%}
  .prow{display:grid;grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:"title price" "sup cost" "when use" "q use";
    gap:var(--space-1) var(--space-3);align-items:center;padding:var(--space-3) var(--space-4);
    border-bottom:1px solid var(--color-line-subtle)}
  .ptable .prow > th,.ptable .prow > td{padding:0;border-bottom:0}
  .pcell-title{grid-area:title}
  .pcell-price{grid-area:price}
  .pcell-sup{grid-area:sup;font-size:var(--font-size-sm)}
  .pcell-cost{grid-area:cost;font-size:var(--font-size-sm)}
  .pcell-when{grid-area:when;font-size:var(--font-size-sm)}
  .pcell-q{grid-area:q}
  .pcell-use{grid-area:use;align-self:center}
  .pcell-use .btn{min-height:var(--size-control-lg);min-width:var(--size-control-lg)}
  .pmore,.pmore > td{display:block}
  .ptable .pmore > td{padding:0 var(--space-4) var(--space-3)}
  .parts-foot{padding:var(--space-3) var(--space-4)}
}

/* --- on-demand part search results --- */
.finds{margin-top:var(--space-3);display:flex;flex-direction:column;gap:var(--space-2)}
/* One row, columns that line up.
 *
 * The row used to be a grid inside a grid: an anchor with its own four
 * columns sitting in a two-column row. Nothing lined up between rows because
 * each anchor sized its own columns independently. It is one grid now, with
 * fixed tracks, so price sits under price and the quote under the quote
 * however long a title runs.
 */
.find-row{display:block}
/* Four columns, one number.
 *
 * The row carried a rank, the part's price, the price it implied we should
 * sell at, and a chip judging one against the other — three numbers to scan
 * past before reaching the one you came for. The list is sorted cheapest
 * first, so the rank was restating the order it already had, and the sell
 * price belongs beside what it is built on, inside the row you open.
 *
 * Price sits right, aligned, tabular: a column of figures is comparable at a
 * glance only when the digits line up. */
.find{
  display:grid;
  grid-template-columns:18px minmax(0,1fr) 92px 112px;
  grid-template-areas:"mark title when price"
    ".    meta  when price";
  column-gap:var(--space-4);row-gap:var(--space-1);align-items:start;
  padding:var(--space-3) var(--space-3);border-radius:var(--radius-control);
  background:var(--color-surface-canvas);border:1px solid transparent;
  width:100%;text-align:left;font:inherit;cursor:pointer;
  text-decoration:none;color:inherit;
  transition:border-color var(--dur-fast) var(--ease-color),background var(--dur-fast) var(--ease-color);
}
.find:hover{background:var(--color-state-row-hover)}
.find[aria-expanded="true"]{
  background:var(--color-surface-raised);border-color:var(--color-customer-solid);
  border-bottom-left-radius:0;border-bottom-right-radius:0;
}
/* Only the chosen part is marked, with a drawn tick — never the &#10003;
   entity, which is a font's idea of a check mark and differs on every one. */
.find-mark{
  grid-area:mark;align-self:center;display:inline-flex;align-items:center;
  color:var(--color-customer-text);
}
.find-title{
  grid-area:title;font-size:var(--font-size-md);font-weight:var(--font-weight-regular);color:var(--color-text-primary);
  line-height:var(--font-line-md);
}
.find-meta{
  grid-area:meta;font-size:var(--font-size-sm);color:var(--color-text-tertiary);
  display:flex;flex-wrap:wrap;gap:var(--space-1);align-items:center;row-gap:var(--space-1);line-height:var(--font-line-sm);}
.find-when{
  grid-area:when;font-size:var(--font-size-sm);color:var(--color-text-primary);
  display:flex;flex-direction:column;gap:1px;line-height:var(--font-line-sm);}
.find-when strong{font-weight:var(--font-weight-semibold);color:var(--color-text-primary)}
.find-when small{font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}
/* "How is this built?" — a drawn icon at 28, inside a .tipw whose note
   opens on hover and on focus. It was a text "?" at 16px: under the touch
   floor, and unreachable by keyboard. */
.q-why{width:var(--size-control-sm);color:var(--color-text-tertiary);vertical-align:middle}
.q-why:hover{color:var(--color-customer-text)}
.find-price{
  grid-area:price;text-align:right;align-self:center;
  font-size:var(--font-size-md);color:var(--color-text-primary);
  font-variant-numeric:tabular-nums;
}
.find-price small{display:block;font-size:var(--font-size-sm);font-family:var(--font-family-sans);font-size-adjust:none;color:var(--color-text-tertiary);text-align:right;line-height:var(--font-line-sm)}
/* Unavailable is said in a word (Sold Out), not in opacity: a dimmed row
   says nothing at all to a screen reader, and says "broken" to everyone else. */
.find.gone .find-title,.find.gone .find-price{color:var(--color-text-secondary)}
.find-note{font-size:var(--font-size-sm);color:var(--color-text-tertiary);padding:var(--space-1) 0 0;line-height:var(--font-line-sm)}

/* ---- pills ---- */
/* Status tag (fOS C, tag.s): 20 high, 11 / 500, a tint and its text. */

.skeleton{padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--color-surface-sunken)}
/* The shape of a list row and of a detail pane, in CSS rather than in a
   style= on every placeholder. */
.skeleton .sk:first-child{width:70%}
.skeleton .sk + .sk{width:45%}
.sk-title{width:50%;height:var(--space-6)}
.sk-line{width:30%}
.sk-block{height:var(--space-16);margin-top:var(--space-5)}
.sk-row{height:var(--size-control-lg)}
/* Skeleton (fOS): the shape of what is coming, on sunken, breathing. One
   declaration serves both registers — a gradient sweep ran the wrong way at
   night, because canvas is darker than sunken there. */
.sk{height:var(--space-3);border-radius:var(--radius-tiny);background:var(--color-surface-sunken);animation:sk-pulse calc(var(--dur-slow) * 4) ease-in-out infinite}
.sk + .sk{margin-top:var(--space-2)}
@keyframes sk-pulse{50%{opacity:.45}}
@keyframes spin{to{transform:rotate(360deg)}}

/* ---- cards used by settings & tables ---- */
/* .card / .field / .actions / .grid2 / .grid4 are gone: every screen that had
   them now uses .pw-card with its three slots and .fld / .fld-row. */
.pw-card .hint{font-size:var(--font-size-sm);color:var(--color-text-tertiary);margin-bottom:var(--space-4);max-width:66ch;line-height:var(--font-line-sm)}
/* A card body stacks: fields, rows and prose each need air beneath them,
   and .fld carries none of its own so it can sit in a grid that owns the
   gap. The last child never pushes against the foot. */
.pw-card__body > .fld,.pw-card__body > .fld-row,.pw-card__body > p,
.pw-card__body > .table-card,.pw-card__body > .pw-empty{margin-bottom:var(--space-4)}
.pw-card__body > *:last-child{margin-bottom:0}
/* Cards laid out in a grid get their gap from the grid, not from the
   stacking margin the vocabulary gives two cards in a column. */
.mgrid .pw-card + .pw-card,.sup-insights .pw-card + .pw-card{margin-top:0}
.diag-clock{margin-bottom:var(--space-2)}
/* A foot holding nothing but an alert that has not fired is an empty strip
   under the card. Older engines without :has simply keep the strip. */
.pw-card__foot:has(> .result:only-child:not(.ok):not(.err):not(.info)){display:none}
/* Field (fOS C): label above, 12 / 500; control 36 high, radius 4, control
   line; hover strengthens the line, focus is the one ring. The wrapper is
   .fld in the vocabulary block; these are the controls it does not wrap. */
.searchbar input,.cat-select,.tier-add input,.mnum,.pw-eqf{height:var(--size-control-md);border-radius:var(--radius-control);border:1px solid var(--color-line-control);background:var(--color-surface-raised);color:var(--color-text-primary);font:inherit;font-size:var(--font-size-md);padding:0 var(--space-3)}
.searchbar input:hover,.cat-select:hover,.tier-add input:hover,.mnum:hover,.pw-eqf:hover{border-color:var(--color-line-strong)}
/* The inline alert a card's own action answers into: role="status" in the
   markup, a tone from the outcome, cleared the moment the next action starts.
   Shaped like the page banner, sized to sit in a card's foot. */
.result{font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);line-height:var(--font-line-md);padding:var(--space-1-5) var(--space-3);border-radius:var(--radius-surface);display:none;max-width:70ch}
.result.ok{background:var(--color-success-tint);color:var(--color-success-text);display:block}
.result.err{background:var(--color-danger-tint);color:var(--color-danger-text);display:block}
.result.info{background:var(--color-customer-tint);color:var(--color-customer-text);display:block}

.scroll-pad{padding:var(--space-6);overflow-y:auto;height:100%}
.scroll-pad > .inner{max-width:67.5rem}

table{width:100%;border-collapse:collapse;font-size:var(--font-size-md)}
/* Table (fOS C): header 12 / 500 on panel, sticky; rows 44 with a hairline;
   hover only on rows that open something (doors), selected = accent 8 %. */
thead th{
  text-align:left;font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);color:var(--color-text-secondary);
  height:var(--size-control-md);padding:0 var(--space-4);border-bottom:1px solid var(--color-line-subtle);
  background:var(--color-surface-panel);white-space:nowrap;position:sticky;top:0;z-index:var(--z-sticky);line-height:var(--font-line-sm);}
tbody td{padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--color-line-subtle);vertical-align:middle}
tbody tr:last-child td{border-bottom:none}
.num{text-align:right;white-space:nowrap}
/* Figures are set in the same family as the words around them, with tabular
   digits so a column still reads as one column. They were monospace, and a
   price or a date in another typeface beside its label read as a mistake
   (Marcin, 3 Oct 2026). Schibsted Grotesk carries tnum. */
td.num{font-variant-numeric:tabular-nums}
.strong{color:var(--color-text-primary)}
.table-card{background:var(--color-surface-raised);border:1px solid var(--color-line-subtle);border-radius:var(--radius-control);overflow-x:auto}

/* ---- settings: tabs ---- */
/* Settings was one column of nine cards — you scrolled past the pricing model
   to reach the supplier list, and nothing told you the tier dictionary
   existed. Tabs turn "find the card" into "pick the subject"; they are the
   underline tabs of the vocabulary (.utabs), not a segmented control. */
.spanel{padding-top:var(--space-5)}

/* ---- settings: the tier dictionary ---- */
/* Three tiers side by side where there is room: the whole point of the screen
   is comparing them, and a column each makes "which one owns this word"
   answerable at a glance. */
.tier-cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:var(--space-6)}
.tier-col{margin-bottom:0}
.tier-col h4{font-size:var(--font-size-md);font-weight:var(--font-weight-semibold);color:var(--color-text-primary);margin-bottom:var(--space-1)}
.tier-col .tier-sub{font-size:var(--font-size-sm);color:var(--color-text-tertiary);margin-bottom:var(--space-2);line-height:var(--font-line-sm)}
.tier-words{margin-bottom:var(--space-2)}
/* Teal is a word the tool learned from a pinned part; a lined tag came with
   it. Neither is a judgement, so neither wears success or danger. */
.tier-learned{font-weight:var(--font-weight-semibold);color:var(--color-customer-text)}
.tier-removed{margin-top:var(--space-3)}
.tier-add{display:flex;gap:var(--space-2);align-items:center}
.tier-add input{flex:1;max-width:240px;height:var(--size-control-sm);font-size:var(--font-size-sm);padding:0 var(--space-2);line-height:var(--font-line-sm)}

/* ---- settings: read-only facts ---- */
.copyrow{display:flex;gap:var(--space-2);align-items:center}
.copyrow code{
  flex:1;padding:var(--space-2) var(--space-3);font-family:var(--font-family-mono);font-size:var(--font-size-sm);overflow-x:auto;white-space:nowrap;
  background:var(--color-surface-sunken);border-radius:var(--radius-surface);color:var(--color-text-primary);line-height:var(--font-line-sm);}

/* ---- pricing model grid ---- */
/* ---- settings: pricing model ----
   One card per kind of job for the device class on the tab strip. Each says
   its rule in a sentence, prices three real-sized parts with it, and shows
   what the shop charged — the number and its evidence side by side. */
.mintro{max-width:72ch;margin:0 0 var(--space-4);font-size:var(--font-size-md);line-height:var(--font-line-md);color:var(--color-text-secondary)}
.mintro strong{color:var(--color-text-primary)}
.mcalib{display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:var(--space-3);margin-bottom:var(--space-4)}
.mcalib small{display:block;margin-top:var(--space-1);font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}
/* Settings said as a sentence, the numbers inside it. */
.msentence{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);margin:0 0 var(--space-2);font-size:var(--font-size-md);color:var(--color-text-primary)}
.mnum-wrap{display:inline-flex;align-items:center;gap:var(--space-1)}
.munit{font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}
.mnum{width:4.5rem;height:var(--size-control-sm);font-size:var(--font-size-sm);padding:0 var(--space-1-5);line-height:var(--font-line-sm)}
.munsaved{font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);color:var(--color-warning-text);line-height:var(--font-line-sm)}

.mclasses{margin:var(--space-4) 0;width:fit-content;max-width:100%;overflow-x:auto;scrollbar-width:none}
.mclasses::-webkit-scrollbar{display:none}
.mclass-mark{font-size:var(--font-size-sm);padding:0 var(--space-1-5);border-radius:var(--radius-pill);background:var(--color-customer-tint);color:var(--color-customer-text);line-height:var(--font-line-sm)}

.mgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(18rem,100%),1fr));gap:var(--space-3)}
/* The tile itself is .pw-card.is-sunken; only the inside is local. */
.mcell{padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-3)}
.mcell-head{display:flex;align-items:center;flex-wrap:wrap;gap:var(--space-2);min-height:var(--size-control-sm)}
.mcell-head h4{font-size:var(--font-size-md);font-weight:var(--font-weight-semibold);color:var(--color-text-primary)}
.mcell-state{display:inline-flex;align-items:center;gap:var(--space-1)}
.mcell-count{margin-left:auto;font-size:var(--font-size-sm);color:var(--color-text-tertiary);white-space:nowrap;line-height:var(--font-line-sm)}
.mcell-rule{margin:0;font-size:var(--font-size-md);color:var(--color-text-secondary)}
.mcell-rule b{color:var(--color-text-primary);font-weight:var(--font-weight-semibold)}
.mrow{display:grid;grid-template-columns:1fr auto;align-items:center;gap:var(--space-1) var(--space-3)}
.mrow label{grid-column:1/-1;font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);color:var(--color-text-secondary);line-height:var(--font-line-sm)}

/* Which part the price is built from: a four-stop segmented control. */
.mlevel{display:flex;flex-direction:column;gap:var(--space-2)}
.mlevel-label{font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);color:var(--color-text-secondary);line-height:var(--font-line-sm)}
.mlevels{display:flex;width:100%}
.mlevels button{flex:1;justify-content:center;padding:0 var(--space-1)}
.mlevel-words{margin:0;font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}

/* Three example parts: a small table, no sticky head, compact rows. */
.mex{font-size:var(--font-size-sm);line-height:var(--font-line-sm)}
.mex thead th{position:static;height:auto;padding:var(--space-1) 0;background:none;font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}
.mex thead th + th{text-align:right}
.mex tbody td{padding:var(--space-2) 0}
.mex tbody td:first-child{text-align:left}
.mex-price{font-weight:var(--font-weight-semibold);color:var(--color-text-primary)}
.mcell-evidence{margin:0;font-size:var(--font-size-sm);line-height:var(--font-line-sm);color:var(--color-text-secondary)}
.mmore summary{font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);color:var(--color-customer-text);cursor:pointer;width:fit-content;line-height:var(--font-line-sm)}
.mmore[open] summary{margin-bottom:var(--space-2)}
.mmore .mrow{margin-bottom:var(--space-2)}
.mmore .hint{margin:0;font-size:var(--font-size-sm);line-height:var(--font-line-sm)}

/* The range input is the one control the token layer cannot reach, so it is
   painted here rather than left to the browser's blue. */
.mrow input[type=range]{
  -webkit-appearance:none;appearance:none;
  width:100%;height:var(--space-5);background:none;cursor:pointer;
}
.mrow input[type=range]::-webkit-slider-runnable-track{
  height:var(--space-1);border-radius:var(--radius-tiny);background:var(--color-line-default);
}
.mrow input[type=range]::-moz-range-track{
  height:var(--space-1);border-radius:var(--radius-tiny);background:var(--color-line-default);
}
.mrow input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;
  width:var(--space-4);height:var(--space-4);margin-top:calc(-1*var(--space-1-5));border-radius:50%;
  background:var(--color-customer-solid);border:2px solid var(--color-surface-raised);
}
.mrow input[type=range]::-moz-range-thumb{
  width:var(--space-4);height:var(--space-4);border-radius:50%;
  background:var(--color-customer-solid);border:2px solid var(--color-surface-raised);
}
/* The thumb is the control: it has to show the ring and the disabled state
   like every other one (fOS states matrix). */
.mrow input[type=range]:focus-visible{outline-offset:var(--space-0-5)}
.mrow input[type=range]:disabled{cursor:not-allowed}
.mrow input[type=range]:disabled::-webkit-slider-thumb{background:var(--color-text-disabled)}
.mrow input[type=range]:disabled::-moz-range-thumb{background:var(--color-text-disabled)}


/* ---- find a part ---- */
/* One search component (pw-search, lg) beside the one solid button. */
.searchbar{display:flex;gap:var(--space-2);margin-bottom:var(--space-3)}
.searchbar .pw-search{flex:1}

/* The read-out is a banner: it says what the sentence was understood to mean,
   and the tint says how sure that reading is. */
.parsed{margin-bottom:var(--space-4)}
.parsed .banner{margin-bottom:0}

/* The suggested price and the offer to add the repair, on a sunken tile. */
.track-bar{
  display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;
  padding:var(--space-3) var(--space-4);margin:0 0 var(--space-3);
}
.track-price{display:flex;flex-direction:column;gap:1px}
.track-price strong{font-size:var(--font-size-xl);font-weight:var(--font-weight-semibold);color:var(--color-text-primary);line-height:var(--font-line-xl)}
.track-price small{font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}
.track-note{flex:1;min-width:16rem;margin:0;font-size:var(--font-size-sm);color:var(--color-text-secondary);line-height:var(--font-line-sm)}

/* The shape of the results while they arrive (skeleton, fOS). */
.find-sk{display:flex;flex-direction:column;gap:var(--space-2);margin-top:var(--space-3)}
.find-sk > div{padding:var(--space-3);border-radius:var(--radius-control);background:var(--color-surface-canvas)}
.sk.is-short{width:40%}

/* ---- a draft PartWatch invented ---- */
.draft-bar{
  display:flex;align-items:center;gap:var(--space-4);flex-wrap:wrap;
  padding:var(--space-3) var(--space-4);margin-bottom:var(--space-4);
  border-radius:var(--radius-surface);
  background:var(--color-warning-tint);font-size:var(--font-size-md);color:var(--color-warning-text);
}
.draft-bar > span{flex:1;min-width:260px}

/* ---- the equation: Price - Part cost = Margin (Czesc G, pw-equation) ---- */
/* One sentence, not three boxes. Labels 12 / 500 above the value, values
   20 / 700 tabular so a column of figures compares by eye, operators drawn
   and aria-hidden — a reader should hear three labels and three numbers,
   not "minus equals". */
.pw-eq{display:flex;align-items:flex-start;flex-wrap:wrap;gap:var(--space-3) var(--space-4)}
.pw-eqc{display:flex;flex-direction:column;gap:var(--space-1);min-width:0}
.pw-eqc .pw-label,.pw-eqsug .pw-label{margin-bottom:0}
.pw-label small{font-weight:var(--font-weight-regular);color:var(--color-text-tertiary)}
.pw-eqv{display:inline-flex;align-items:center;gap:var(--space-1);min-height:var(--size-control-md);
  font-size:var(--font-size-lg);line-height:var(--font-line-lg);font-weight:var(--font-weight-regular);
  color:var(--color-text-primary);font-variant-numeric:tabular-nums slashed-zero}
.pw-eqm{font-size:var(--font-size-xl);line-height:var(--font-line-xl);font-weight:var(--font-weight-bold)}
.pw-eqf{width:6.5rem;height:var(--size-control-md);font-size:var(--font-size-lg);
  font-variant-numeric:tabular-nums;text-align:right;padding:0 var(--space-2)}
/* Provenance: where this number came from, under the number itself. */
.pw-eqs{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;
  min-height:var(--space-4);font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}
.pw-eqs:empty{display:none}
.pw-eqs .is-problem{color:var(--color-danger-text)}
.pw-op{align-self:flex-start;margin-top:calc(var(--space-5) + var(--space-2));color:var(--color-text-tertiary)}
/* The model's suggestion at the far end of the same line: the figure, how it
   is worked out, and the one solid button on the screen. */
.pw-eqsug{margin-left:auto;display:flex;flex-direction:column;align-items:flex-end;gap:var(--space-1)}
.pw-eqsug-row{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;justify-content:flex-end;min-height:var(--size-control-md)}
.pw-eqsug-row > b{font-size:var(--font-size-xl);line-height:var(--font-line-xl);font-weight:var(--font-weight-semibold);color:var(--color-customer-text)}
.pw-eqverdict{flex-basis:100%;display:flex;flex-direction:column;gap:var(--space-1)}
.eq-say{margin:0;font-size:var(--font-size-md);line-height:var(--font-line-md);color:var(--color-text-primary)}
.pw-eqsug .pw-why-d[open] > .pw-why{left:auto;right:0}
.pw-eqact{display:inline-flex;align-items:center;gap:var(--space-2)}
.eq-note{color:var(--color-text-tertiary);font-size:var(--font-size-sm);line-height:var(--font-line-sm)}
/* It wraps instead of ellipsing: the sentence explains the price, and a
   title= holding the rest of it is unreadable on a phone. */
.pw-why{font-size:var(--font-size-sm);color:var(--color-text-secondary);line-height:var(--font-line-sm)}
/* How the suggestion is worked out: one "?" beside it, the sentence on press.
   It was two lines of arithmetic under every variant. */
.pw-why-d{position:relative;display:inline-flex}
.pw-why-d > summary{list-style:none;display:inline-flex;align-items:center;justify-content:center;
  width:var(--space-6);height:var(--space-6);border-radius:var(--radius-pill);border:1px solid var(--color-line-control);
  font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);color:var(--color-text-secondary);cursor:pointer;line-height:1}
.pw-why-d > summary::-webkit-details-marker{display:none}
.pw-why-d > summary:hover{background:var(--color-state-hover)}
.pw-why-d[open] > summary{background:var(--color-customer-tint-strong);color:var(--color-customer-text);border-color:transparent}
.pw-why-d[open] > .pw-why{position:absolute;top:calc(100% + var(--space-2));left:0;z-index:var(--z-popover);
  width:min(28rem,80vw);padding:var(--space-3);background:var(--color-surface-overlay);border:1px solid var(--color-line-subtle);
  border-radius:var(--radius-surface);box-shadow:var(--shadow-2)}
.eq-quiet{color:var(--color-text-tertiary);flex-basis:100%;white-space:normal;min-width:0}
.detail-asof{color:var(--color-text-tertiary)}

/* Verdict: the word carries the colour, the figure stays neutral so it is
   still comparable with the figure in the row below it. */
.pw-verdict{display:inline-flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;
  font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}
.pw-verdict b{color:var(--color-text-primary)}

.find-filters{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin:var(--space-2) 0 var(--space-2)}
.find-count{font-size:var(--font-size-sm);color:var(--color-text-tertiary);margin-left:auto;line-height:var(--font-line-sm)}

@media (max-width:860px){
  /* One column, and the operators go: a minus between two figures stacked
     one above the other is noise, not arithmetic. */
  .pw-eq{grid-template-columns:1fr;gap:var(--space-3)}
  .pw-op{display:none}
  .find-row{grid-template-columns:1fr}
  .pw-eqf{width:7.5rem}
}

/* ---- sync ---- */
/* The bulk action bar (fOS §9): what is staged on the left, the acts on it
   on the right, on an accent tint so it reads as a bar above its table
   rather than as one more card. Never a border on one edge. */
.sync-bar{
  display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;
  padding:var(--space-2) var(--space-4);margin:var(--space-4) 0 var(--space-2);
  min-height:var(--size-control-lg);
  border-radius:var(--radius-surface) var(--radius-surface) 0 0;
  background:var(--color-customer-tint);color:var(--color-customer-text);
}
.sync-bar:first-child{margin-top:0}
.sync-count{font-size:var(--font-size-md);font-weight:var(--font-weight-semibold);color:inherit}
.sync-bar .pw-eyebrow{color:inherit;opacity:.8}
/* The bar sits on the table it acts on. */
.sync-bar + .table-card{border-top-left-radius:0;border-top-right-radius:0}
/* The shape of a staged table while it loads. */
.sync-sk{display:flex;flex-direction:column;gap:var(--space-2);padding:var(--space-3) 0}
/* A cell that carries the value being replaced — present, not competing. */
.cell-quiet{color:var(--color-text-tertiary)}

/* ---- the suggested price on a found part ---- */

/* ---- catalogue (manager) ---- */
/* One row of controls, all one height: the search, three lists that name
   themselves ("All categories"), the count and the view switch. */
.cat-bar{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-4)}
.cat-find{flex:1 1 20rem;min-width:14rem;max-width:32rem}
.cat-bar .fsel{flex:0 1 12rem}
.cat-count{display:flex;align-items:center;gap:var(--space-2)}
/* On a phone the lists take the full width; the count and the switch share
   the last row. */
@media (max-width:640px){
  .cat-find{max-width:none}
  .cat-bar .fsel{flex:1 1 100%}
}
.cat-select{padding:0 var(--space-2);max-width:260px}

/* Where a menu opens: the cell it belongs to, never inside the trigger.
   The surface itself is the shared pw-menu in the vocabulary block. */
.pw-anchor{position:relative}
.pw-anchor > .pw-menu{top:100%;right:0;left:auto}
/* On a results row the trigger sits in a wide button, so the menu hangs
   from the row's left edge instead of the trigger's right. */
.find-row.pw-anchor > .pw-menu{left:0;right:auto}

/* Inline table edit controls swap in place of the cell — matched height,
   so the row does not jump when the select appears. */
.cat-select-inline{height:var(--size-control-sm);font-size:var(--font-size-sm);padding:0 var(--space-1-5);max-width:220px;vertical-align:middle;line-height:var(--font-line-sm)}
/* A sortable header is a button, and the caret is drawn (fOS): the glyphs
   it replaces could not be sized, coloured or read out. */
.cat-table th.sortable{padding:0}
.cat-table th.sortable > button{
  display:inline-flex;align-items:center;gap:var(--space-1);width:100%;
  height:var(--size-control-md);padding:0 var(--space-4);
  font:inherit;font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);
  color:inherit;background:none;border:0;cursor:pointer;text-align:inherit;line-height:var(--font-line-sm);}
.cat-table th.sortable.num > button{justify-content:flex-end}
.cat-table th.sortable > button:hover{color:var(--color-text-primary)}
.cat-table th.sortable > button:focus-visible{outline-offset:-2px}
.cat-table th.sorted > button{color:var(--color-text-primary)}
.sort-mark{display:inline-flex;color:var(--color-customer-text)}
/* The title is a door (fOS UI link): body size, semibold, the accent on hover
   and nothing else — an underline and a wash together were two signals for
   one affordance. */
.cat-title{display:inline-block;max-width:18rem;white-space:normal;line-height:var(--font-line-md);
  height:auto;padding:0;border:0;background:none;font-size:var(--font-size-md);font-weight:var(--font-weight-regular);color:var(--color-text-primary);text-align:left;vertical-align:middle}
.cat-title:hover:not(:disabled){background:none;color:var(--color-customer-text)}
/* Category and device read at the same size as everything else; a long
   category list stays on one line and ends with an ellipsis. */
.cat-table td{font-size:var(--font-size-md)}
/* The title column is the one that reads; it gets the room, the category ends in an ellipsis. */
.cat-table th:first-child,.cat-table td:first-child{min-width:14rem}
.cat-table td,.cat-table thead th{padding-left:var(--space-3);padding-right:var(--space-3)}
.cat-table .pw-cell-edit{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:inline-block;max-width:14rem;line-height:var(--font-line-md)}
.cat-margin .pw-status,.cat-margin .row-rate{font-size:var(--font-size-sm);line-height:var(--font-line-sm)}
.cat-margin > span:last-child{white-space:nowrap;display:inline-flex;align-items:center;gap:var(--space-1)}
.cat-staged td{background:var(--color-state-selected)}
.cat-state{max-width:100%}
/* Group headings: a department, then its brand or series. The whole row is
   the fold button; the chevron turns, the count sits beside the name. */
.cat-group td{padding:0;background:var(--color-surface-canvas);border-top:1px solid var(--color-line-subtle)}
.cat-fold{display:flex;align-items:center;gap:var(--space-2);width:100%;
  min-height:var(--size-control-md);padding:0 var(--space-3);
  border:0;background:none;cursor:pointer;text-align:left;
  font:inherit;font-size:var(--font-size-md);font-weight:var(--font-weight-semibold);color:var(--color-text-primary);line-height:var(--font-line-md)}
.cat-fold:hover{background:var(--color-state-hover)}
.cat-fold:focus-visible{outline-offset:-2px}
.cat-fold svg{flex-shrink:0;color:var(--color-text-tertiary);transition:transform var(--dur-fast) var(--ease-color)}
.cat-fold[aria-expanded="false"] svg{transform:rotate(-90deg)}
.cat-fold .cnt{font-weight:var(--font-weight-regular);color:var(--color-text-tertiary);font-size:var(--font-size-sm)}
.cat-group.is-l2 td{background:var(--color-surface-raised)}
.cat-group.is-l2 .cat-fold{padding-left:var(--space-8);font-weight:var(--font-weight-regular);min-height:var(--size-control-sm)}

/* Column widths. The grip is an 8 px strip on a header's right edge, with a
   hairline that shows while it is hovered, focused or dragged. Once a width
   has been set the table is fixed layout, and long cells end in an ellipsis. */
.cat-table thead th{position:relative}
.col-grip{position:absolute;top:0;right:0;bottom:0;outline-offset:-2px;width:var(--space-2);cursor:col-resize;touch-action:none;z-index:1}
.col-grip::after{content:"";position:absolute;top:var(--space-2);bottom:var(--space-2);right:0;width:2px;border-radius:var(--radius-pill);background:transparent}
.col-grip:hover::after,.col-grip:focus-visible::after,.col-grip.is-dragging::after{background:var(--color-line-strong)}
.cat-table.is-sized{table-layout:fixed;min-width:0}
.cat-table.is-sized td,.cat-table.is-sized th{overflow:hidden;text-overflow:ellipsis}
.cat-table.is-sized th:first-child,.cat-table.is-sized td:first-child{min-width:0}
.cat-table.is-sized .cat-title{max-width:100%}
.cat-table.is-sized .pw-cell-edit{max-width:100%}

/* ---- supplier credentials ---- */
.creds-form{max-width:44rem}
.creds-actions{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-top:var(--space-3)}
/* The credentials drawer. Save runs a real login, so it needs room for an
   answer — a one-line "saved" is exactly the reassurance this replaces. */
.creds-panel{padding:var(--space-3) 0 var(--space-2);max-width:720px}
.creds-panel .creds-note{font-size:var(--font-size-sm);color:var(--color-text-tertiary);
  margin:var(--space-2) 0 0;line-height:var(--font-line-sm)}
/* The answer to "did that sign-in work": a status word and a sentence, on
   the sunken card. The tint used to carry the verdict on its own. */
.creds-verdict{display:grid;gap:var(--space-2);justify-items:start;margin-top:var(--space-3);
  padding:var(--space-3);max-width:44rem;font-size:var(--font-size-sm);line-height:var(--font-line-md)}
.creds-verdict p{margin:0;color:var(--color-text-primary)}

/* ---- discovery report ---- */
/* Twelve suppliers, twelve different sites. This is the answer for one of
   them, and it has to be readable as prose — it is a finding, not a table. */
.disco{margin-top:var(--space-3);padding:var(--space-3) var(--space-4);max-width:48rem}
.disco-delta{font-size:var(--font-size-sm);line-height:var(--font-line-md);margin-bottom:var(--space-3)}
.disco-verdict{font-size:var(--font-size-md);font-weight:var(--font-weight-semibold);color:var(--color-text-primary);line-height:var(--font-line-md);
  margin-bottom:var(--space-2)}
.disco-steps{list-style:none;margin:0 0 var(--space-3);padding:0}
/* A plain list, indented. The bullet was a middle-dot glyph pretending to
   be a marker; nothing is lost by letting the indent do the work. */
.disco-steps li{font-size:var(--font-size-sm);color:var(--color-text-primary);line-height:var(--font-line-sm);
  padding-left:var(--space-4)}
.disco-routes{width:100%;font-size:var(--font-size-sm);margin-bottom:var(--space-2);line-height:var(--font-line-sm)}
.disco-routes th{text-align:left;font-size:var(--font-size-sm);color:var(--color-text-tertiary);
  font-weight:var(--font-weight-semibold);padding:var(--space-1) var(--space-2) var(--space-1) 0;line-height:var(--font-line-sm)}
.disco-routes td{padding:var(--space-1) var(--space-2) var(--space-1) 0;color:var(--color-text-primary)}
.disco details{margin-top:var(--space-2)}
.disco summary{font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);color:var(--color-customer-solid);cursor:pointer;line-height:var(--font-line-sm)}
/* ---- suppliers: the comparison table ---- */
.sup-controls{display:flex;gap:var(--space-3);align-items:center;flex-wrap:wrap;margin-bottom:var(--space-4)}

.sup-table{width:100%;font-size:var(--font-size-md)}
/* Wide content scrolls inside its own container, never the page. */
.table-card:has(.sup-table),.table-card:has(.cat-table){overflow-x:auto}
/* A sortable header is a control, so it is a button inside the th and the
   direction is a drawn caret with aria-sort beside it. The old arrows were
   CSS content: invisible to a screen reader and unclickable by keyboard. */
.sup-table th.sortable{padding:0}
.sup-sort{display:inline-flex;align-items:center;gap:var(--space-1);width:100%;height:100%;
  padding:0 var(--space-4);background:none;border:0;font:inherit;color:inherit;
  font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);white-space:nowrap;cursor:pointer;line-height:var(--font-line-sm)}
.sup-sort:hover{color:var(--color-text-primary)}
.sup-sort:focus-visible{outline-offset:calc(-1*var(--space-0-5))}
.sup-table th.sorted .sup-sort{color:var(--color-text-primary)}
.sup-table th.sorted .ico{color:var(--color-customer-solid)}
.sup-table th.num,.sup-table td.num{text-align:right}
.sup-row{cursor:pointer}
.sup-row:hover td{background:var(--color-state-row-hover)}
.sup-row.open td{background:var(--color-state-selected)}

.sup-name{display:block;font-size:var(--font-size-md);font-weight:var(--font-weight-semibold);color:var(--color-text-primary)}
.sup-how{display:flex;align-items:center;gap:var(--space-1);font-size:var(--font-size-sm);
  color:var(--color-text-tertiary);margin-top:var(--space-1);line-height:var(--font-line-sm)}
/* Supplier favicon badge; the initial sits behind so a missing icon degrades
   to a letter chip, never a broken image. */
.sup-badge{
  position:relative;display:inline-grid;place-items:center;
  width:16px;height:16px;border-radius:var(--radius-tiny);overflow:hidden;
  background:var(--color-n-white);box-shadow:inset 0 0 0 1px var(--color-line-subtle);flex:none;
}
.sup-badge img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.sup-badge-letter{font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}

.cell-sub{display:block;font-size:var(--font-size-sm);color:var(--color-text-tertiary);margin-top:var(--space-1);line-height:var(--font-line-sm)}
.status-note{white-space:normal;max-width:34ch;margin-top:var(--space-1)}
/* Ten columns is a lot of table: "Not Known" broken across two lines reads
   as two separate facts. It is one. */
.unk{color:var(--color-text-tertiary);white-space:nowrap}
/* A value we do not hold says so, in the tertiary token. It used to be
   italic at 75 % opacity, which is a fade, not a state (doctrine §4b). */
.sup-unknown{color:var(--color-text-tertiary);white-space:nowrap}
.sup-contact{font-size:var(--font-size-sm);color:var(--color-text-tertiary);min-width:178px;line-height:var(--font-line-sm)}
.sup-contact a{color:var(--color-customer-solid)}

.divider-row td{padding-top:var(--space-4);font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}
.sup-detail-row td{background:var(--color-surface-panel);padding:0}
.sup-detail{padding:var(--space-4)}
/* How much this supplier matters, then what decides an order: three cards
   side by side, one column on a phone. */
.sup-kpis{margin-bottom:var(--space-4)}
/* Ordering and cover on the left, delivery beside them: delivery is the one
   card with a table in it, and squeezed into a third it wrapped every row. */
.sup-cards{display:grid;grid-template-columns:minmax(15rem,1fr) minmax(0,1.6fr);gap:var(--space-4);align-items:start}
.sup-col{display:grid;gap:var(--space-4);min-width:0}
.sup-col > .pw-card.sup-card{box-shadow:none;margin:0}
.sup-card .pw-card__body{padding:var(--space-3) var(--space-4) var(--space-4)}
.sup-dl{display:grid;gap:var(--space-3);margin:0}
.sup-dl > div{display:grid;grid-template-columns:7.5rem 1fr;gap:var(--space-2);align-items:baseline}
.sup-dl dt{font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}
.sup-dl dd{font-size:var(--font-size-md);color:var(--color-text-primary);line-height:var(--font-line-md);margin:0;min-width:0;overflow-wrap:anywhere}
.sup-dl dd a{color:var(--color-customer-text)}
.sup-dl small,.sup-ship-table small{display:block;font-size:var(--font-size-sm);color:var(--color-text-secondary);line-height:var(--font-line-sm);margin-top:var(--space-1)}
.sup-none,.sup-line{font-size:var(--font-size-sm);color:var(--color-text-secondary);line-height:var(--font-line-sm);margin:0}
.sup-sub{font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm);margin:var(--space-2) 0 0}
.sup-ship-table{width:100%;border-collapse:collapse;font-size:var(--font-size-sm);line-height:var(--font-line-sm)}
.sup-ship-table th{font-weight:var(--font-weight-regular);font-size:var(--font-size-sm);color:var(--color-text-tertiary);text-align:left;padding:0 var(--space-3) var(--space-2) 0;background:none;border:0;height:auto;text-transform:none;letter-spacing:0}
.sup-ship-table td{background:none;padding:var(--space-2) var(--space-3) var(--space-2) 0;border-top:1px solid var(--color-line-subtle);vertical-align:top;color:var(--color-text-primary);font-size:var(--font-size-sm)}
.sup-ship-table th:last-child,.sup-ship-table td:last-child{padding-right:0}
.sup-ship-table .sup-price{text-align:right;white-space:nowrap}
.sup-ship-table td:first-child{width:45%}
@media (max-width:860px){
  .sup-cards{grid-template-columns:1fr}
  .sup-dl > div{grid-template-columns:1fr;gap:var(--space-0-5)}
}
.sup-gaps{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);margin:var(--space-4) 0 0;font-size:var(--font-size-sm);color:var(--color-text-secondary);line-height:var(--font-line-sm)}
/* A marketplace seller, judgeable from the row where you meet them. */
.seller-chip{
  display:inline-flex;align-items:center;gap:var(--space-1);
  border:1px solid var(--color-line-subtle);border-radius:var(--radius-pill);
  padding:1px var(--space-2);cursor:pointer;font-weight:var(--font-weight-semibold);
  background:var(--color-surface-raised);color:var(--color-text-primary);
}
.seller-chip:hover{border-color:var(--color-customer-solid)}

/* Margin in the Catalogue table: the amount, the percentage, and the word
   that judges them. The figures carry no colour of their own — a coloured
   number is a judgement nobody can read out. */
.cat-margin{display:inline-flex;flex-direction:column;align-items:flex-end;line-height:var(--font-line-md);gap:1px}
.cat-margin strong{font-variant-numeric:tabular-nums;font-weight:inherit}

/* A metered supplier's month, on its card. */
.sup-budget{margin:var(--space-3) 0;padding:var(--space-3) var(--space-3);border:1px solid var(--color-line-subtle);border-radius:var(--radius-card);background:var(--color-surface-raised)}
/* The numbers sit above the bar, never inside it (fOS pw-meter). */
.sup-budget-row{display:flex;align-items:flex-end;gap:var(--space-3);flex-wrap:wrap;font-size:var(--font-size-md);margin-bottom:var(--space-2)}
.sup-budget-edit{flex:0 0 auto;width:6.5rem}
.sup-budget-edit input{height:var(--size-control-sm)}
@keyframes rowFlash{from{background:var(--color-customer-tint)}to{background:transparent}}
.row-flash > td{animation:rowFlash calc(var(--dur-base) * 8) var(--ease-exit)}

/* Shown only when the sticker price would have sent you to the wrong
   supplier — so it is advice, not a caption on the sort order. Quiet: it
   corrects a reading, it does not raise an alarm. */
.verdict-bar{
  display:flex;align-items:baseline;gap:var(--space-2);flex-wrap:wrap;
  margin:0 0 var(--space-2);padding:var(--space-2) var(--space-3);
  background:var(--color-surface-canvas);border:1px solid var(--color-line-subtle);
  border-radius:var(--radius-surface);
  font-size:var(--font-size-sm);line-height:var(--font-line-sm);color:var(--color-text-primary);
}
.verdict-tag{
  flex-shrink:0;padding:var(--space-0-5) var(--space-2);border-radius:var(--radius-pill);
  background:var(--color-surface-raised);border:1px solid var(--color-line-strong);
  color:var(--color-text-tertiary);
  font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);line-height:var(--font-line-sm);}

.disco pre{
  margin-top:var(--space-2);padding:var(--space-3);border-radius:var(--radius-surface);overflow-x:auto;
  background:var(--color-surface-sunken);font-size:var(--font-size-sm);line-height:var(--font-line-sm);
  color:var(--color-text-primary);white-space:pre;
}

/* ---- alerts ---- */
/* A card, not a door: nothing happens when you click the card itself, so it
   carries no hover. The buttons inside it are the affordance. */
.alerts{display:flex;flex-direction:column}
.alerts:has(> .alert){background:var(--color-surface-raised);border:1px solid var(--color-line-subtle);border-radius:var(--radius-card);box-shadow:var(--shadow-1);overflow:hidden}
.alerts > .alert{border:0;border-radius:0;box-shadow:none;margin:0}
.alerts > .alert + .alert{border-top:1px solid var(--color-line-subtle);margin-top:0}
.alerts > .banner{margin:var(--space-3) var(--space-4) 0}
.alert{
  padding:var(--space-4) var(--space-4);
  display:grid;grid-template-columns:auto 1fr auto;gap:var(--space-3);align-items:start;
}
/* The shape of the list while it loads. */
.alert-sk{display:flex;flex-direction:column;gap:var(--space-2)}
.alert-sk article{
  display:grid;grid-template-columns:auto 1fr;gap:var(--space-3);align-items:start;
  padding:var(--space-4);border:1px solid var(--color-line-subtle);
  border-radius:var(--radius-card);background:var(--color-surface-raised);
}
.sk.is-tile{width:var(--size-control-md);height:var(--size-control-md);border-radius:var(--radius-surface)}
/* The on/off switch. Green on, grey off — the shape everyone already knows
   from their phone, so it needs no label. */
.switch{
  position:relative;width:36px;height:20px;flex:none;
  border-radius:var(--radius-pill);border:0;padding:0;cursor:pointer;
  background:var(--color-line-default);
  transition:background-color var(--dur-fast) var(--ease-color);
}
.switch .knob{
  position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;
  background:var(--color-surface-raised);transition:transform var(--dur-fast) var(--ease-color);
  box-shadow:var(--shadow-1);
}
.switch.on{background:var(--color-success-solid)}
.switch.on .knob{transform:translateX(16px)}
.switch.busy{background:var(--color-state-disabled-bg);cursor:progress}
.switch.busy .knob{background:var(--color-text-disabled)}
.sup-th-on,td.sup-on{width:44px;padding-right:0}
td.sup-on{vertical-align:middle}
/* Off is a state the row says out loud (the status word beside the name),
   so the colour steps back to tertiary instead of fading the row out. */
.sup-row.off td,tr.off > td:not(.sup-on){color:var(--color-text-tertiary)}
.sup-row.off td strong,tr.off > td strong{font-weight:inherit;color:inherit}
/* Status (fOS C): a dot and a word, never colour alone. Roles carry the
   register token pair; the figure beside it stays neutral. */
.pw-status{display:inline-flex;align-items:center;gap:var(--space-1);font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);white-space:nowrap;color:var(--color-text-tertiary);line-height:var(--font-line-sm)}
.pw-status i{width:7px;height:7px;border-radius:50%;background:currentColor;flex:none}
.pw-status.is-success{color:var(--color-success-text)}
.pw-status.is-warning{color:var(--color-warning-text)}
.pw-status.is-danger{color:var(--color-danger-text)}
.pw-status.is-customer{color:var(--color-customer-text)}
.pw-status.is-internal{color:var(--color-internal-text)}
.pw-status.is-neutral{color:var(--color-text-tertiary)}
.row-rate{font-variant-numeric:tabular-nums;color:var(--color-text-tertiary)}
/* The title row with its quiet "more" menu. The menu itself is .pw-menu;
   this only says where it hangs from. */
.detail-head{display:flex;align-items:flex-start;gap:var(--space-2)}
.detail-head h1{flex:1;min-width:0}
.detail-menu{position:relative;flex:none;margin-top:var(--space-1)}
.detail-menu .pw-menu{right:0;top:calc(var(--size-control-md) + var(--space-1))}
.detail-menu [role="menuitem"] strong{font-weight:var(--font-weight-semibold)}
.detail-menu [role="menuitem"] span{font-size:var(--font-size-sm);color:var(--color-text-tertiary);
  white-space:normal;line-height:var(--font-line-sm)}
/* Back to the list is an arrow the other way round — the same drawing, not a
   second one invented for this button. */
.detail-back .ico{transform:scaleX(-1)}
.markets-wait{color:var(--color-text-tertiary);font-style:normal}
.markets-wait::before{content:"";display:inline-block;width:10px;height:10px;border:2px solid var(--color-line-subtle);
  border-top-color:var(--color-customer-solid);border-radius:50%;margin-right:var(--space-2);vertical-align:-1px;animation:spin calc(var(--dur-slow) * 3) linear infinite}
/* The drawn icon in its tile. Neutral unless the KIND carries a judgement —
   a fault is red, good news is green, and everything else is simply a
   subject, not a telling-off. */
.alert-icon{
  width:var(--size-control-md);height:var(--size-control-md);border-radius:var(--radius-tiny);
  display:grid;place-items:center;flex-shrink:0;
  background:var(--color-surface-sunken);color:var(--color-text-secondary);
}
.alert-icon.err{background:var(--color-danger-tint);color:var(--color-danger-text)}
.alert-icon.ok{background:var(--color-success-tint);color:var(--color-success-text)}
.alert-kind{margin-bottom:1px}
.alert-title{font-size:var(--font-size-md);font-weight:var(--font-weight-semibold);color:var(--color-text-primary);margin:0 0 var(--space-1);line-height:var(--font-line-md)}
/* A swapped part puts the old and the new on their own lines — the two
   have to sit one above the other to be compared at all. */
.alert-detail{font-size:var(--font-size-md);color:var(--color-text-primary);white-space:pre-line}
/* One row, the action that fixes it first. Stacked, the three buttons
   were three different widths and read as a ragged column. */
.alert-actions{display:flex;gap:var(--space-2);align-items:center;flex-wrap:wrap;
  justify-content:flex-end}
@media (max-width:640px){
  .alert{grid-template-columns:auto 1fr}
  .alert-actions{grid-column:1 / -1;justify-content:flex-start}
}

/* Banner (fOS C): a tint and its text, no border. Neutral unless the message
   carries a judgement — a running check is news, not a fault. */
.banner{
  background:var(--color-surface-sunken);color:var(--color-text-primary);
  border-radius:var(--radius-surface);padding:var(--space-2) var(--space-3);margin-bottom:var(--space-4);
  font-size:var(--font-size-sm);line-height:var(--font-line-sm);
}
.banner.is-danger{background:var(--color-danger-tint);color:var(--color-danger-text)}
.banner.is-warning{background:var(--color-warning-tint);color:var(--color-warning-text)}
.banner.is-info{background:var(--color-customer-tint);color:var(--color-customer-text)}
.banner.is-success{background:var(--color-success-tint);color:var(--color-success-text)}

/* Toast (fOS C): bottom right, ink, radius 8, a stack of three, 5 s and a
   pause while the pointer is over it; an Undo slot for the reversible
   actions of tier 1. Never for an error that needs a decision. */
.toasts{position:fixed;right:var(--space-4);bottom:var(--space-4);display:flex;flex-direction:column;gap:var(--space-2);z-index:var(--z-toast);pointer-events:none;max-width:min(26rem,calc(100vw - var(--space-8)))}
.toast{
  display:flex;align-items:center;gap:var(--space-3);pointer-events:auto;
  background:var(--color-internal-solid);color:var(--color-internal-on-solid);
  font-size:var(--font-size-sm);line-height:var(--font-line-sm);
  padding:var(--space-2) var(--space-2) var(--space-2) var(--space-4);border-radius:var(--radius-surface);box-shadow:var(--shadow-2);
  animation:toast-rise var(--dur-base) var(--ease-enter);
}
.toast.is-out{animation:toast-sink calc(var(--dur-base) * .7) var(--ease-exit) forwards}
.toast.is-danger{background:var(--color-danger-solid);color:var(--color-danger-on-solid)}
.toast.is-success{background:var(--color-success-solid);color:var(--color-success-on-solid)}
.toast > span{flex:1;min-width:0}
.toast .btn{height:calc(var(--space-6) + var(--space-0-5));padding:0 var(--space-2);background:transparent;border-color:transparent;color:var(--color-customer-line);font-size:var(--font-size-sm);line-height:var(--font-line-sm)}
.toast .btn:hover:not(:disabled){background:color-mix(in srgb,currentColor 12%,transparent)}
@keyframes toast-rise{from{opacity:0;transform:translateY(8px)}}
@keyframes toast-sink{to{opacity:0;transform:translateY(8px)}}

/* ============================================================ RESPONSIVE */

@media (max-width:1200px){
  /* The sections take their own row under the wordmark and the tools. */
  .topbar{display:flex;flex-wrap:wrap}
  .topbar .nav{order:10;flex-basis:100%;align-self:auto;margin:0 calc(-1 * var(--space-2))}
  .topbar .nav .utab{height:var(--size-control-lg)}
}
@media (max-width:1200px){
  .split{grid-template-columns:320px 1fr}
}
@media (max-width:860px){
  .topbar{padding:0 var(--space-4)}

  .split{grid-template-columns:1fr}
  .detail{display:none}
  .split.showing-detail .list-pane{display:none}
  .split.showing-detail .detail{display:block}
  /* One pane at a time needs a way back — Escape is not a touch gesture. */
  .detail-back{display:inline-flex}
}
@media (max-width:640px){
  /* The keycap is a desktop affordance; there is no "/" key to press here. */
  .scope .kc{display:none}
  .scope .pw-search.has-key input{padding-right:var(--space-3)}
  .detail{padding:var(--space-4)}
  /* The bar keeps two rows: wordmark and tools, then the sections. VAT and
     theme move into the account menu, Check Parts keeps its icon, and the
     price age says only how old. */
  .topbar .vat-switch,.topbar .tipw:has(.theme-toggle){display:none}
  .acct-phone{display:contents}
  #runNow{width:var(--space-8);padding:0;justify-content:center}
  #runNow .run-label{display:none}
  .freshness{width:auto;max-width:8.5rem}
  .freshness .pw-status{font-size:0;gap:0}   /* the dot stays, the word goes */
  .fr-long{display:none}
  .list-head{flex-wrap:wrap;min-height:0;padding:var(--space-2) var(--space-3)}

  /* Find a Part rows: four columns cannot share 375px — the title wrapped
     one word per line straight under the delivery column and the two
     overlapped. Stack instead: title, meta, then delivery and price on one
     closing line. */
  .find{
    grid-template-columns:18px minmax(0,1fr) auto;
    grid-template-areas:"mark title title"
      "mark meta  meta"
      "mark when  price";
    column-gap:var(--space-2);
  }
  .find-when{flex-direction:row;align-items:baseline;gap:var(--space-1);margin-top:var(--space-2)}
  .find-price{align-self:end;margin-top:var(--space-2)}

  /* A tap that lingers was selecting the row's text instead of reading as a
     press — these rows are buttons, not prose. */
  .list button,.find{-webkit-user-select:none;user-select:none}

  /* The sections overflow sideways on purpose; the fade says there is more
     to the right, which nothing else was saying. */
  .topbar .nav{
    -webkit-mask-image:linear-gradient(90deg,black 88%,transparent);
    mask-image:linear-gradient(90deg,black 88%,transparent);
  }

  /* ---- Suppliers (Sourcing) as cards ----
     Ten columns in a 340px window showed one and a half of them and hid the
     rest behind a sideways scroll nothing hinted at. Each row becomes a
     card: name on top, the eight facts as a labelled two-column grid, and
     contact + sync full-width at the bottom. Labels come from the cells'
     own data-label, so the JS row template serves both layouts. */
  .table-card:has(.sup-table){overflow-x:visible}
  .sup-table,.sup-table tbody{display:block}
  .sup-table thead{display:none}
  .sup-table tr.sup-row{
    display:grid;grid-template-columns:1fr 1fr;column-gap:var(--space-3);
    border:1px solid var(--color-line-subtle);border-radius:var(--radius-card);
    background:var(--color-surface-raised);padding:var(--space-3) var(--space-3);margin-bottom:var(--space-2);
  }
  .sup-table tr.sup-row td{display:block;border:0;padding:var(--space-2) 0}
  .sup-table td.sup-name-cell,
  .sup-table td.sup-contact,.sup-table td.sup-sync{grid-column:1 / -1}
  /* The switch sits top-right of the card, the name beside it. */
  .sup-table tr.sup-row{position:relative}
  .sup-table td.sup-on{position:absolute;top:12px;right:14px;width:auto;padding:0}
  .sup-table tr.sup-row td.sup-on[data-label]::before{display:none}
  .sup-table td.sup-name-cell{padding-right:var(--space-12)}
  .sup-table tr.sup-row td[data-label]::before{
    content:attr(data-label);
    display:block;font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);color:var(--color-text-tertiary);margin-bottom:var(--space-1);
  }
  .sup-table td.num{text-align:left}
  .sup-row:hover td,.sup-row.open td{background:none}
  .sup-row.open{border-color:var(--color-customer-solid)}
  /* The expanded detail reads as part of the card above it. */
  .sup-table tr.sup-detail-row{display:block;margin:calc(-1*var(--space-2)) 0 var(--space-2)}
  .sup-table tr.sup-detail-row>td{
    display:block;border:1px solid var(--color-customer-solid);border-top:0;
    border-radius:0 0 var(--radius-card) var(--radius-card);
    background:var(--color-surface-raised);padding:var(--space-3) var(--space-3);
  }
  .sup-row.open{border-radius:var(--radius-card) var(--radius-card) 0 0;margin-bottom:0}
  /* Part panel head: supplier name and its two buttons cannot share 300px —
     the name's min-width:0 let it shrink to nothing and its text rendered
     under the buttons. A width floor makes the buttons wrap to their own
     row instead. */
  .pp-head{flex-wrap:wrap;row-gap:var(--space-2)}
}

@media (prefers-reduced-motion:reduce){
    /* a reduced-motion request overrides every per-component motion */
  *{transition:none!important;animation:none!important}
}

/* The sign-in command handed back when this PartWatch has no screen of its
   own. A command you must retype is a command nobody runs. */
.signin-cmd {
  margin-top:var(--space-3); padding:var(--space-3); border:1px solid var(--color-line-subtle);
  border-radius:var(--radius-surface); background:var(--color-surface-canvas);
  display:flex; flex-wrap:wrap; gap:var(--space-2); align-items:center;
}
.signin-cmd code {
  font-family:ui-monospace, SFMono-Regular, Menlo, monospace; font-size:var(--font-size-sm);
  color:var(--color-text-primary); background:var(--color-surface-raised); padding:var(--space-1-5) var(--space-2);
  border:1px solid var(--color-line-subtle); border-radius:var(--radius-tiny);
  overflow-x:auto; max-width:100%;line-height:var(--font-line-sm);}
.signin-cmd .creds-note { flex-basis:100%; margin:0; }

/* Marketplace sellers: a register you read, not a roll call you scroll. */
.seller-stats { display:flex; gap:var(--space-2); flex-wrap:wrap; margin:var(--space-1) 0 var(--space-3); }
.seller-search{margin-bottom:var(--space-3)}
.seller-acts{white-space:nowrap}
.seller-head {
  font-size:var(--font-size-sm); font-weight:var(--font-weight-semibold); color:var(--color-text-tertiary);
  margin:var(--space-4) 0 var(--space-2);line-height:var(--font-line-sm);}

/* Suppliers: find one fast, and see what they stock without opening a row. */
.sup-toolbar {
  display:flex; flex-wrap:wrap; gap:var(--space-2); align-items:center;
  margin:var(--space-3) 0 var(--space-4);
}
.sup-search{flex:1 1 260px;min-width:200px}
.sup-classes{flex:0 1 auto}
.sup-sells { margin:var(--space-1) 0; }

/* The lens strip: the tabs take the width, the one solid action sits at the
   end of the same line. */
.sup-lensbar{display:flex;align-items:flex-end;gap:var(--space-3);margin-bottom:var(--space-4)}
.sup-lensbar .utabs{flex:1;min-width:0}
.sup-lens-lead{max-width:76ch}
.sup-findbox{flex:0 1 15rem}
.sup-conn-card{margin-bottom:var(--space-5)}
.sup-conn-state{white-space:nowrap}
.sup-conn-actions{white-space:nowrap}
.sup-login{display:block;margin-top:var(--space-1)}
.sup-key{font-variant-numeric:tabular-nums}
.sup-users-table{margin-bottom:var(--space-4)}
.user-actions{white-space:nowrap}
/* People in the Users table: their mark, then the name they go by with the
   sign-in name quieter beside it. */
.user-who{display:inline-flex;align-items:center;gap:var(--space-2)}
.user-avatar{display:grid;place-items:center;flex:none;width:var(--space-6);height:var(--space-6);border-radius:var(--radius-pill);
  overflow:hidden;background:var(--color-internal-solid);color:var(--color-internal-on-solid);
  font-size:var(--font-size-xs);font-weight:var(--font-weight-semibold);line-height:1}
.user-handle{color:var(--color-text-secondary)}
/* Profile: the photo at the size it is recognised, its actions and the name
   beside it; stacked on a phone. */
.profile{display:flex;align-items:flex-start;gap:var(--space-6)}
.profile-photo{display:grid;place-items:center;flex:none;width:var(--space-16);height:var(--space-16);border-radius:var(--radius-pill);
  overflow:hidden;background:var(--color-internal-solid);color:var(--color-internal-on-solid);
  font-size:var(--font-size-2xl);font-weight:var(--font-weight-semibold);line-height:1}
.profile-photo img{display:block;width:100%;height:100%;object-fit:cover}
.profile-side{flex:1;min-width:0;max-width:28rem}
.profile-actions{display:flex;flex-wrap:wrap;gap:var(--space-2)}
.profile-side .hint{margin:var(--space-2) 0 var(--space-4)}
@media (max-width:640px){.profile{flex-direction:column;gap:var(--space-4)}}
/* The link just made, ready to hand over: one card-white tile with a full
   hairline, the address in a read-only field beside its Copy button. */
.reset-link{margin-bottom:var(--space-4);padding:var(--space-3) var(--space-4);border:1px solid var(--color-line-subtle);border-radius:var(--radius-surface);background:var(--color-surface-canvas)}
.reset-link-head{margin:0 0 var(--space-2);font-weight:var(--font-weight-semibold);color:var(--color-text-primary)}
.reset-link-row{display:flex;gap:var(--space-2);align-items:center}
.reset-link-row input{flex:1;min-width:0;height:var(--size-control-sm);padding:0 var(--space-2);border:1px solid var(--color-line-control);border-radius:var(--radius-control);background:var(--color-surface-raised);color:var(--color-text-primary);font-family:var(--font-family-mono);font-size:var(--font-size-sm);line-height:var(--font-line-sm)}
.reset-link .hint{margin:var(--space-2) 0 0}
.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
.sup-insights{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));gap:var(--space-3);align-items:start;margin-bottom:var(--space-4)}
/* The last paragraph in a card body should not push against the foot. */
.sup-para-tail{margin-top:var(--space-3)}

/* A comparison table is not prose — it earns the width it needs. */
.scroll-pad > .inner-wide { max-width:90rem; }

/* Suppliers table, readable at a glance: room to breathe, a header that
   stays put while you scroll, and a hairline between rows rather than a
   grid of boxes. */
.sup-table td{vertical-align:top}
.sup-table .cell-sub {
  display:block; font-size:var(--font-size-sm); line-height:var(--font-line-sm);
  color:var(--color-text-tertiary); margin-top:var(--space-1);
}


/* Which catalogues answered — a glance, not a table. Each is a filter chip
   with the supplier's own mark inside it (pw-source), so pressing one is
   visibly the same act as pressing any other chip on the screen. */
.sup-strip { margin:var(--space-2) 0 var(--space-3); }
.chip .pw-source { color:inherit; font-size:inherit; font-weight:inherit; }

/* Help: a manual reads as prose, so it gets prose measure — unlike the
   comparison tables, which earn their width. */
.help-head { display:flex; align-items:flex-start; gap:var(--space-4); }
.help-head > div:first-child { flex:1; }
.help-sec { margin:var(--space-8) 0 0; max-width:74ch; }
.help-sec h3 {
  font-size:var(--font-size-md); line-height:var(--font-line-md);
  font-weight:var(--font-weight-semibold); color:var(--color-text-primary);
  margin:0 0 var(--space-2); padding-bottom:var(--space-2);
  border-bottom:1px solid var(--color-line-subtle);
}
.help-p { font-size:var(--font-size-md); line-height:var(--font-line-md); margin:0 0 var(--space-2); color:var(--color-text-primary); }
/* A word and what it means is a two-column table, and now says so. */
.help-terms { margin:var(--space-1) 0 0; table-layout:fixed; }
.help-terms thead th { position:static; background:none; padding-left:0; padding-right:var(--space-4); }
.help-terms tbody th {
  width:12rem; text-align:left; vertical-align:top;
  padding:var(--space-3) var(--space-4) var(--space-3) 0;
  font-size:var(--font-size-md); font-weight:var(--font-weight-semibold);
  color:var(--color-text-primary); border-bottom:1px solid var(--color-line-subtle);
}
.help-terms td {
  padding:var(--space-3) 0; vertical-align:top;
  font-size:var(--font-size-md); line-height:var(--font-line-md); color:var(--color-text-primary);
}
@media (max-width:860px) {
  .help-terms, .help-terms thead, .help-terms tbody, .help-terms tr,
  .help-terms th, .help-terms td { display:block; width:auto; }
  .help-terms thead { display:none; }
  .help-terms tbody th { border-bottom:0; padding:var(--space-3) 0 var(--space-1); }
  .help-terms td { padding-top:0; border-bottom:1px solid var(--color-line-subtle); padding-bottom:var(--space-3); }
}

/* Find a Part: results and the part you are looking at, side by side. The
   panel is the fOS drawer shape — a card, its head naming the source and
   holding every action, its body holding the one thing being looked at. */
.find-split { display:grid; grid-template-columns:1fr; gap:var(--space-5); align-items:start; }
.part-panel {
  position:sticky; top:var(--space-3);
  border:1px solid var(--color-line-subtle); border-radius:var(--radius-card);
  background:var(--color-surface-raised); box-shadow:var(--shadow-1);
  overflow:hidden; font-size:var(--font-size-md);
}
.pp-head { gap:var(--space-3); }
.pp-acts { display:flex; align-items:center; gap:var(--space-2); flex-wrap:wrap; }
.pp-shot { margin:0; }
.pp-note { font-size:var(--font-size-sm); color:var(--color-text-tertiary); line-height:var(--font-line-sm); margin:var(--space-2) 0 0; }
/* What the panel says just happened: a sunken tile between the head and the
   supplier's page, so the preview landing cannot wipe it. */

/* With the part panel open the results column is narrower, and the row's
   four-column grid ran out of room — the delivery note landed on top of the
   price. Below that width the meta line wraps under the title instead. */
.find-split.open .find-meta{flex-wrap:wrap;row-gap:var(--space-1)}
@media (max-width:1200px){
  .find-split.open .find{grid-template-columns:14px 1fr auto;
    grid-template-areas:"mark title price" ".    meta  price";gap:var(--space-2)}
  .find-split.open .find-price{font-size:var(--font-size-md)}
}

/* The supplier's own page, live. Tall on purpose: a product page needs room
   for the picture and the description, which is the point of showing it. */
/* The stage clips; the frame inside it is laid out wide and scaled down. */
.pp-stage {
  max-width:100%;
  height:74vh; min-height:520px; overflow:hidden;
  border:1px solid var(--color-line-default); border-radius:var(--radius-surface);
  background:var(--color-n-white); position:relative;
  /* Their page stays white in both registers because it is THEIR page. The
     frame is what tells a night-mode reader the white is quoted, not a bug —
     and the caption under it says so in words. */
  box-shadow:0 0 0 var(--space-1) var(--color-surface-panel);
}
.pp-frame {
  border:0; display:block; background:var(--color-n-white);
  transform-origin:0 0; height:74vh; min-height:520px;
}

/* Find a Part is two panes, not prose: it takes the window it is given.
   Capped at prose width the results column was cramped, and the supplier
   page beside it fell below its own mobile breakpoint. */
.scroll-pad > .inner-full { max-width:90rem; }
.find-split.open { grid-template-columns:minmax(0, 1fr) min(800px, 50%); } /* one step, not three: the panel takes half the width up to 800px */
@media (max-width:1200px) { .find-split.open { grid-template-columns:minmax(0, 1fr); } } /* minmax: same reason as the rule above */

/* What we know about a marketplace seller, shown before you judge them.
   A note inside a menu: it explains, it does not choose, so it wears the
   note's type rather than a menu item's. */
.seller-facts {
  display:flex; flex-direction:column; gap:var(--space-1);
  padding:var(--space-2); margin:0 0 var(--space-1);
  font-size:var(--font-size-sm); line-height:var(--font-line-sm);
  color:var(--color-text-tertiary);
  border-bottom:1px solid var(--color-line-subtle);
}
.seller-facts b { color:var(--color-text-primary); font-weight:var(--font-weight-semibold); }
.seller-facts .warn { color:var(--color-danger-text); }

/* A row can be wide; a row that is 1,740px wide cannot be read. The eye
   loses the line between a title on the left and its price at the far right,
   which is the oldest problem with wide tables. Capped at a distance the eye
   can travel — the column shrinks below this anyway when the panel opens. */
.finds { max-width:1180px; }


/* The alert list says when it is not showing everything — as the shared
   banner, because "we are showing you 200 of 343" is news, not a fault. */

/* How long ago this supplier's catalogue was actually read. Quiet, because it
   sits beside the status dot and must not compete with the supplier's name. */

/* The Last Sync column: when this supplier's catalogue was last read, what
   came back, and the way to read it again. Contact gets the room it was short
   of, and this sits at the end where a running total belongs. */
.sup-th-contact{min-width:178px}
.sup-sync{min-width:132px;white-space:nowrap}
.sync-when{display:block;font-weight:var(--font-weight-semibold);color:var(--color-text-primary)}

/* Twelve of these must not read as twelve calls to action, so it is the
   ghost button — quiet by token, never by opacity. */
.sup-refresh{margin-top:var(--space-2);white-space:nowrap}

/* What a pinned part just decided about the price. Sits directly under the
   part it came from, because that is the thing being judged. */
.pin-result{
  padding:var(--space-3) var(--space-4);margin:0;
  background:var(--color-surface-canvas);border-bottom:1px solid var(--color-line-subtle);
}
.pin-said{margin:0 0 var(--space-2);font-size:var(--font-size-md);color:var(--color-text-primary)}
.pin-numbers{display:flex;gap:var(--space-5);margin:0 0 var(--space-3);flex-wrap:wrap}
.pin-numbers dt{
  font-size:var(--font-size-sm);font-weight:var(--font-weight-semibold);
  color:var(--color-text-tertiary);line-height:var(--font-line-sm);}
.pin-numbers dd{
  margin:var(--space-1) 0 0;font-size:var(--font-size-xl);line-height:var(--font-line-xl);font-weight:var(--font-weight-semibold);color:var(--color-text-primary);
}
.pin-numbers .pin-suggested{color:var(--color-customer-text)}
.pin-note{margin:var(--space-2) 0 0;font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}

/* The ! / ? marks. The value stays in the cell; the qualifier waits behind a
   mark you reach when you want it. Hover, keyboard focus and a tap all open
   the note — title= is unreadable on a phone and unreachable from a keyboard. */
.mark-wrap{position:relative;display:inline-block;margin-left:var(--space-1);vertical-align:1px}
/* The mark that IS the cell. No value sits beside it to lend it weight, so it
   carries none of the "footnote" restraint: full size, full contrast, no
   left margin pushing it away from a value that is not there. */
.mark-only{margin-left:0}
/* Ring and glyph are one colour, always: the border is currentColor, so
   setting border-color separately here is what pulled them apart. Size stays
   close to the inline mark — standing alone it needs contrast, not bulk. */
.mark-only .mark{width:13px;height:13px;font-size:8.5px}

/* Small on the page, still reachable with a finger: the ring shrinks, the
   thing you can hit does not. */
.mark::before{content:'';position:absolute;inset:calc(-1*var(--space-3))}
.sup-table td.num .mark-only{display:inline-block}
/* The value and its mark are one thing; broken across two lines the mark
   reads as a second value. Ten columns will not fit a laptop whatever we do,
   so the table scrolls inside its card rather than squeezing a cell until its
   own footnote falls off the bottom. */
.sup-table td > strong:first-child,
.sup-table td > span:first-child{white-space:nowrap}
.sup-table th:nth-child(2),.sup-table td:nth-child(2),
.sup-table th:nth-child(3),.sup-table td:nth-child(3),
.sup-table th:nth-child(4),.sup-table td:nth-child(4),
.sup-table th:nth-child(5),.sup-table td:nth-child(5){min-width:104px}
.sup-table th:nth-child(6),.sup-table td:nth-child(6){min-width:120px}
.sup-table th:nth-child(7),.sup-table td:nth-child(7){min-width:74px}
.mark{
  width:var(--space-3);height:var(--space-3);padding:0;
  /* border is currentColor — never set border-color on a variant, or the ring
     and the symbol inside it stop matching. */
  display:inline-grid;place-items:center;position:relative;
  background:none;border:1px solid currentColor;border-radius:50%;
  font:inherit;font-size:8px;font-weight:var(--font-weight-semibold);line-height:1;
  cursor:help;transition:color var(--dur-fast) var(--ease-color);
}
/* Both marks are quiet. A gap in what we know is not an error — thirty red
   circles down a column read as thirty faults, and the table stops being
   scannable for the ones that matter. Warm marks the gap; it still stands out
   from the neutral note without shouting. */
/* Both marks are quiet. A gap in what we know is not an error — thirty red
   circles down a column read as thirty faults. Warm marks the gap. */
.mark-note{color:var(--color-text-tertiary)}
.mark-gap{color:var(--color-warning-text)}
/* Rest, row-hover and hover are three colours of one ring — never three
   opacities of one colour (doctrine §4b). */
.sup-row:hover .mark-note{color:var(--color-text-secondary)}
.mark-note:hover,.mark-note:focus-visible{color:var(--color-text-primary)}
.mark-gap:hover,.mark-gap:focus-visible{color:var(--color-warning-solid)}

.mark-pop{
  position:absolute;z-index:var(--z-popover);left:50%;top:calc(100% + 7px);
  transform:translateX(-50%);
  width:max-content;max-width:250px;
  padding:var(--space-2) var(--space-3);
  background:var(--color-surface-overlay);color:var(--color-text-primary);
  border:1px solid var(--color-line-default);border-radius:var(--radius-surface);
  box-shadow:var(--shadow-2);
  font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);line-height:var(--font-line-sm);font-style:normal;
  white-space:normal;text-align:left;
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity var(--dur-fast) var(--ease-color),visibility var(--dur-fast) var(--ease-color);
}
.mark-wrap:hover .mark-pop,
.mark-wrap:focus-within .mark-pop{opacity:1;visibility:visible}
/* Near the right edge it would fall off the table. */
.sup-table td:nth-last-child(-n+3) .mark-pop{left:auto;right:0;transform:none}

/* The contact column carries real addresses now, so it has to be able to
   break one without pushing the table sideways. */
.contact-mail,.contact-tel{
  display:block;color:var(--color-customer-solid);text-decoration:none;line-height:var(--font-line-md);
  /* break-word, not anywhere:"support@lcd4laptop.co.u / k" is worse than a
     slightly wider column. */
  overflow-wrap:break-word;
}
.contact-mail:hover,.contact-tel:hover{text-decoration:underline}


/* What you know about a supplier and their site never will. Sits above the
   actions in the expanded row, because writing it is the reason you opened it. */
.sup-notes{
  margin-top:var(--space-4);padding-top:var(--space-4);border-top:1px solid var(--color-surface-sunken);
}
.sup-notes .fld{max-width:none}
.sup-notes-box{min-height:5.5rem}
.sup-notes-foot{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-top:var(--space-2)}
.sup-notes-when{font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}

/* The share beside the count. "461" is a mystery on its own; "461 · 49%" is
   half our shop — so both are the value, and neither hides behind a mark. */
.cover-pct{
  margin-left:var(--space-2);font-size:var(--font-size-sm);
  color:var(--color-text-tertiary);font-variant-numeric:tabular-nums;line-height:var(--font-line-sm);}

/* Open It, beside what was just created. */
.pin-actions{display:flex;gap:var(--space-2);align-items:center;flex-wrap:wrap;margin-top:var(--space-3)}

/* A part row opens onto the few facts that decide an order. It used to be a
   link straight off to the supplier — the wrong default when the question is
   "should I buy this one", which is answered here. */
.find-open{
  padding:var(--space-3) var(--space-4) var(--space-4);margin-bottom:var(--space-1);
  background:var(--color-surface-raised);
  border:1px solid var(--color-customer-solid);border-top:0;
  border-bottom-left-radius:var(--radius-control);border-bottom-right-radius:var(--radius-control);
}
/* What this part would have us charge. It used to be set off by a 1px rule
   down its left edge, which is the single-edge accent the doctrine bans and
   which read, at a glance, as a coloured callout. Spacing does the job. */
.pd-sell dd{color:var(--color-customer-text)}
.pd-facts{display:flex;flex-wrap:wrap;gap:var(--space-2) var(--space-6);margin:0 0 var(--space-3)}
.pd-facts dd{margin:var(--space-1) 0 0;font-size:var(--font-size-md);color:var(--color-text-primary)}
.pd-actions{display:flex;gap:var(--space-2);align-items:center;flex-wrap:wrap}

/* The line above the parts list: what we buy, or that nothing is chosen. */
.part-line{display:inline-flex;align-items:center;gap:var(--space-2);flex-wrap:wrap}
.part-line.none{color:var(--color-text-tertiary);font-size:var(--font-size-sm);line-height:var(--font-line-sm)}
.chosen-note{color:var(--color-text-tertiary);font-size:var(--font-size-sm);line-height:var(--font-line-sm)}
/* The supplier's own page, and the seller judgement, live in the opened
   row's action line — both are .btn now. */
.pd-note{flex:1;min-width:14rem;font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}

/* What the job leaves us at this part's cost — the question the old "Try It"
   button asked by writing into a field at the top of the screen and making
   you undo it afterwards. Opening the row is the question; this is the
   answer. No edge rule dividing it off: it is another fact in the same list. */
.pd-earn dd + dd{margin-top:var(--space-1)}

/* The rest of the parts that fit. The list opens on five; this is how you see
   the twenty-two the header already told you about. A centred button, not a
   full-width dashed box — that shape means "drop something here". */
.finds-more{display:flex;justify-content:center;margin-top:var(--space-2)}

/* ---- Stage 3: dialog ---- */
/* Tier-2 confirmation (fOS §8 / design-system .dlg): the only thing on screen
   until it is answered. <dialog>.showModal() is doing the work — the scrim,
   the focus trap and "a click outside does not dismiss" are its behaviour,
   not ours. 25rem is the fOS 400px for this dialog, written in rem because a
   px literal is what the raw-value gate counts; capped to the viewport so it
   survives a 360px phone. */
.dlg{
  /* The reset's margin:0 beats the UA's margin:auto, which is what centres a
     modal <dialog> — without these two it opened in the top-left corner. */
  inset:0;margin:auto;
  width:min(25rem, 92vw);padding:var(--space-5);
  background:var(--color-surface-overlay);color:var(--color-text-primary);
  border:1px solid var(--color-line-subtle);border-radius:var(--radius-window);
  box-shadow:var(--shadow-3);font-family:var(--font-family-sans);
  font-size:var(--font-size-md);line-height:var(--font-line-md);
  z-index:var(--z-dialog);
}
.dlg::backdrop{background:var(--color-surface-scrim)}
.dlg-title{
  margin:0 0 var(--space-2);font-size:var(--font-size-lg);
  line-height:var(--font-line-lg);font-weight:var(--font-weight-semibold);
}
.dlg-body{margin:0 0 var(--space-4);color:var(--color-text-secondary)}
/* Cancel left, the verb and its noun right: the destructive answer is never
   the one under the cursor by accident. */
.dlg-actions{display:flex;justify-content:flex-end;gap:var(--space-2)}
.dlg-actions .dlg-danger{
  background:var(--color-danger-solid);color:var(--color-danger-on-solid);
  border-color:transparent;
}
.dlg-actions .dlg-danger:hover{background:var(--color-danger-text)}
/* ---- end Stage 3 ---- */

/* ---- the sign-in page ----
   It used to re-declare the card, the field and the button in its own
   <style>. Now it uses the same three components as every other screen and
   this block says only where they sit — which is the whole difference
   between a page that follows the system and a page that copies it. */
.signin{display:grid;place-items:center;min-height:100dvh;background:var(--color-surface-canvas);position:relative;overflow:clip}
.signin-slash{right:-2vw;bottom:-6vh;width:38vmax}
.signin-card{width:min(23.75rem,92vw);position:relative}
.signin-card .pw-card__body{padding:var(--space-8)}
.signin-card h1{
  font-size:var(--font-size-xl);line-height:var(--font-line-xl);font-weight:var(--font-weight-semibold);
  letter-spacing:-.01em;color:var(--color-text-primary);margin:var(--space-6) 0 var(--space-1);
}
.signin-lead{font-size:var(--font-size-md);color:var(--color-text-tertiary);margin-bottom:var(--space-6)}
.signin-card .brand-lockup{align-items:flex-start;min-height:0}
.signin-card .brand-logo{height:26px;width:auto}
.signin-card .fld{margin-bottom:var(--space-3)}
.signin-card .btn{width:100%;justify-content:center;margin-top:var(--space-2)}
.signin-card .banner{margin-bottom:var(--space-4)}
/* The two quiet ways out of the card: a lost password, and back to sign in. */
.signin-forgot{margin-top:var(--space-4);font-size:var(--font-size-sm);color:var(--color-text-secondary);line-height:var(--font-line-sm)}
.signin-forgot summary{cursor:pointer;color:var(--color-customer-text);width:fit-content}
.signin-forgot p{margin:var(--space-2) 0 0}
.signin-foot{margin:var(--space-4) 0 0;font-size:var(--font-size-sm);line-height:var(--font-line-sm)}
.signin-foot a{color:var(--color-customer-text)}

/* ============================================================ VOCABULARY */
/* Stage 5 (20 Sep 2026): the components fOS names, plus PartWatch's own
   section (Część G). Templates move onto these; the rules they retire go. */

/* Tag: a fact about a thing — 20 high, 11 / 500, a tint and its text, no
   hover. is-md is 24 high. A dot inside says which register the fact belongs
   to; a button inside removes it (16 px round, a drawn cross). */
.pw-tag{display:inline-flex;align-items:center;gap:var(--space-1);height:var(--space-5);padding:0 var(--space-2);border-radius:var(--radius-pill);font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);white-space:nowrap;vertical-align:middle;background:var(--color-neutral-tint);color:var(--color-neutral-text);line-height:var(--font-line-sm)}
.pw-tag.is-md{height:var(--space-6);font-size:var(--font-size-sm);line-height:var(--font-line-sm)}
.pw-tag.is-neutral{background:var(--color-neutral-tint);color:var(--color-neutral-text)}
.pw-tag.is-customer{background:var(--color-customer-tint);color:var(--color-customer-text)}
.pw-tag.is-success{background:var(--color-success-tint);color:var(--color-success-text)}
.pw-tag.is-warning{background:var(--color-warning-tint);color:var(--color-warning-text)}
.pw-tag.is-danger{background:var(--color-danger-tint);color:var(--color-danger-text)}
.pw-tag.is-internal{background:var(--color-internal-tint);color:var(--color-internal-text)}
.pw-tag.is-ink{background:var(--color-internal-solid);color:var(--color-internal-on-solid)}
.pw-tag.is-line{background:var(--color-surface-raised);color:var(--color-text-primary);box-shadow:inset 0 0 0 1px var(--color-line-subtle)}
.pw-tag i{width:var(--space-1-5);height:var(--space-1-5);border-radius:50%;background:currentColor;flex:none}
.pw-tag button{width:var(--space-4);height:var(--space-4);margin-right:calc(-1*var(--space-1));padding:0;border:0;border-radius:50%;background:none;color:inherit;display:inline-grid;place-items:center;cursor:pointer}
.pw-tag button:hover{background:color-mix(in srgb,currentColor 15%,transparent)}
.pw-tag button svg{width:calc(var(--space-2) + var(--space-0-5));height:calc(var(--space-2) + var(--space-0-5));stroke-width:2}
/* The 12-hue palette for facts that are not judgements (grade, category). */
.pw-tag.hue-red{background:var(--color-tag-red-bg);color:var(--color-tag-red-tx)}
.pw-tag.hue-orange{background:var(--color-tag-orange-bg);color:var(--color-tag-orange-tx)}
.pw-tag.hue-amber{background:var(--color-tag-amber-bg);color:var(--color-tag-amber-tx)}
.pw-tag.hue-lime{background:var(--color-tag-lime-bg);color:var(--color-tag-lime-tx)}
.pw-tag.hue-green{background:var(--color-tag-green-bg);color:var(--color-tag-green-tx)}
.pw-tag.hue-teal{background:var(--color-tag-teal-bg);color:var(--color-tag-teal-tx)}
.pw-tag.hue-cyan{background:var(--color-tag-cyan-bg);color:var(--color-tag-cyan-tx)}
.pw-tag.hue-blue{background:var(--color-tag-blue-bg);color:var(--color-tag-blue-tx)}
.pw-tag.hue-indigo{background:var(--color-tag-indigo-bg);color:var(--color-tag-indigo-tx)}
.pw-tag.hue-violet{background:var(--color-tag-violet-bg);color:var(--color-tag-violet-tx)}
.pw-tag.hue-plum{background:var(--color-tag-plum-bg);color:var(--color-tag-plum-tx)}
.pw-tag.hue-rose{background:var(--color-tag-rose-bg);color:var(--color-tag-rose-tx)}

/* Counter inside a chip or a tab. On ink it derives from currentColor — the
   recipe fOS adopts from PartWatch (Część G). */
.cnt{font-size:var(--font-size-sm);padding:0 var(--space-1-5);border-radius:var(--radius-pill);background:var(--color-surface-sunken);color:var(--color-text-secondary);font-weight:var(--font-weight-medium);line-height:var(--space-4)}
[aria-selected="true"] .cnt,.nav-item[aria-current="page"] .cnt{background:color-mix(in srgb,currentColor 18%,transparent);color:inherit}

/* Stat pill: a counted fact — the figure primary, the label tertiary. */
.pw-stat{display:inline-flex;align-items:center;gap:var(--space-1);height:var(--size-control-sm);padding:0 var(--space-3);border-radius:var(--radius-pill);border:1px solid var(--color-line-subtle);font-size:var(--font-size-sm);color:var(--color-text-tertiary);white-space:nowrap;line-height:var(--font-line-sm)}
.pw-stat b{color:var(--color-text-primary)}

/* The only two label treatments: above a control, above a value. */
.pw-label{display:block;font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);color:var(--color-text-secondary);margin-bottom:var(--space-1);line-height:var(--font-line-sm)}
.pw-eyebrow{display:block;font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}

/* Card: raised, hairline, radius 12, three slots. is-sunken is the inner
   tile (radius 8, on canvas); is-link is a card that opens something. */
.pw-card{background:var(--color-surface-raised);border:1px solid var(--color-line-subtle);border-radius:var(--radius-card);box-shadow:var(--shadow-1);overflow:hidden}
.pw-card__head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);flex-wrap:wrap;padding:var(--space-4) var(--space-4) 0}
.pw-card__head h3,.pw-card__head h4{font-size:var(--font-size-md);font-weight:var(--font-weight-semibold);margin:0}
.pw-card__body{padding:var(--space-4)}
.pw-card__foot{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;padding:var(--space-2) var(--space-4);border-top:1px solid var(--color-line-subtle);font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}
.pw-card.is-sunken{background:var(--color-surface-canvas);border-radius:var(--radius-surface);box-shadow:none}
.pw-card.is-link{cursor:pointer;transition:border-color var(--dur-fast) var(--ease-color)}
.pw-card.is-link:hover{border-color:var(--color-line-strong)}
.pw-card + .pw-card{margin-top:var(--space-6)}
/* A table in a card is the card's body, not a second box inside it (fOS: a
   card never nests a card). The head keeps its hairline only over a list. */
.pw-card__body .table-card{border:0;border-radius:0;background:none;overflow-x:auto}
.pw-card__head:has(+ .pw-card__body > .table-card:first-child){border-bottom:1px solid var(--color-line-subtle);padding-bottom:var(--space-3)}
.pw-card__head + .pw-card__body{padding-top:var(--space-2)}

/* Button variants fOS names and PartWatch lacked. ghost is transparent with
   accent text (a bordered secondary is plain .btn); danger is the tint;
   is-armed is the six-second second click of tier 1; is-loading keeps the
   width and puts the spinner inside. */
.btn-ink{background:var(--color-internal-solid);color:var(--color-internal-on-solid);border-color:transparent}
.btn-ink:hover:not(:disabled){background:color-mix(in oklab,var(--color-internal-solid) 90%,var(--color-text-primary))}
.btn-ghost{background:transparent;border-color:transparent;color:var(--color-customer-text)}
.btn-ghost:hover:not(:disabled){background:var(--color-state-hover)}
.btn-danger{background:var(--color-danger-tint);color:var(--color-danger-text);border-color:transparent}
.btn-danger:hover:not(:disabled){background:color-mix(in oklab,var(--color-danger-tint) 85%,var(--color-danger-solid))}
.btn.is-armed,.btn.is-armed:hover:not(:disabled){background:var(--color-danger-solid);color:var(--color-danger-on-solid);border-color:transparent}
.btn-lg{height:var(--size-control-lg);padding:0 var(--space-4)}
.btn.is-loading{color:transparent;position:relative;pointer-events:none}
.btn.is-loading::after{content:"";position:absolute;left:50%;top:50%;width:calc(var(--space-3) + var(--space-0-5));height:calc(var(--space-3) + var(--space-0-5));margin:calc(-1*(var(--space-1-5) + 1px)) 0 0 calc(-1*(var(--space-1-5) + 1px));border:2px solid var(--color-line-default);border-top-color:var(--color-customer-solid);border-radius:50%;animation:spin calc(var(--dur-slow) * 3) linear infinite}
.btn-primary.is-loading::after,.btn-ink.is-loading::after{border-color:color-mix(in srgb,var(--color-internal-on-solid) 35%,transparent);border-top-color:var(--color-internal-on-solid)}
.btn-customer.is-loading::after{border-color:color-mix(in srgb,var(--color-customer-on-solid) 35%,transparent);border-top-color:var(--color-customer-on-solid)}
/* Loading is not disabled: the button keeps its surface while it works, so
   the spinner is drawn on teal or ink, never white on the disabled grey. */
.btn.is-loading:disabled{background:var(--color-surface-raised);border-color:var(--color-line-control);cursor:progress}
.btn-primary.is-loading:disabled{background:var(--color-internal-solid);border-color:transparent}
.btn-customer.is-loading:disabled{background:var(--color-customer-solid);border-color:transparent}
.btn-ink.is-loading:disabled{background:var(--color-internal-solid);border-color:transparent}
/* A check in progress is a state, not an invitation: the run button steps
   down from solid teal to a quiet sunken pill with its label readable and
   a small spinner beside it, so nothing tempts a second click. */
#runNow.is-loading,#runNow.is-loading:disabled{background:var(--color-surface-sunken);color:var(--color-text-secondary);border-color:transparent;box-shadow:none;cursor:progress}
#runNow.is-loading::after{position:static;margin:0 0 0 var(--space-1);width:var(--space-3);height:var(--space-3);border-width:2px;border-color:var(--color-line-default);border-top-color:var(--color-customer-solid);order:2}
#runNow.is-loading .ico{display:none}
.btn .ico{flex:none}

/* Field, the fOS way: .fld wraps a label, a control and its help; the error
   replaces the help and says what to do. Optional is marked, required is not. */
.fld{display:block;min-width:0}
.fld > span:first-child,.fld > label:first-child{display:block;font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);color:var(--color-text-secondary);margin-bottom:var(--space-1);line-height:var(--font-line-sm)}
.fld > span:first-child em{font-style:normal;font-weight:var(--font-weight-regular);color:var(--color-text-tertiary)}
.fld input:not([type=checkbox]):not([type=radio]):not([type=range]),.fld select,.fld textarea{width:100%;height:var(--size-control-md);border-radius:var(--radius-control);border:1px solid var(--color-line-control);background:var(--color-surface-raised);color:var(--color-text-primary);font:inherit;font-size:var(--font-size-md);padding:0 var(--space-3)}
.fld textarea{height:auto;min-height:5.5rem;padding:var(--space-2) var(--space-3);line-height:var(--font-line-md);resize:vertical}
.fld input:hover,.fld select:hover,.fld textarea:hover{border-color:var(--color-line-strong)}
.fld small{display:block;font-size:var(--font-size-sm);color:var(--color-text-tertiary);margin-top:var(--space-1);line-height:var(--font-line-sm)}
.fld.err input,.fld.err select,.fld.err textarea{border-color:var(--color-danger-solid)}
.fld.err small{color:var(--color-danger-text)}
.fld input:disabled,.fld select:disabled,.fld textarea:disabled{background:var(--color-state-disabled-bg);color:var(--color-text-disabled);border-color:transparent}
.fld.dis > span:first-child{color:var(--color-text-disabled)}
.fld-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(13.75rem,1fr));gap:var(--space-3) var(--space-4)}
/* A currency prefix, a unit suffix, a figure that lines up. */
.fwrap{position:relative;display:flex;align-items:center}
.fwrap .pfx{position:absolute;left:var(--space-3);font-size:var(--font-size-md);color:var(--color-text-tertiary);pointer-events:none}
.fwrap .pfx ~ input{padding-left:var(--space-6)}
.fld .num{text-align:right;font-variant-numeric:tabular-nums}

/* Search: a field with the magnifier in it; sm 28 / md 36 / lg 44. The
   keycap sits at the right edge. */
.pw-search{position:relative;display:block;min-width:0}
.pw-search > .ico{position:absolute;left:var(--space-3);top:50%;transform:translateY(-50%);color:var(--color-text-tertiary);pointer-events:none}
.pw-search input{width:100%;height:var(--size-control-md);border-radius:var(--radius-control);border:1px solid var(--color-line-control);background:var(--color-surface-raised);color:var(--color-text-primary);font:inherit;font-size:var(--font-size-md);padding:0 var(--space-3) 0 var(--space-8)}
.pw-search input:hover{border-color:var(--color-line-strong)}
.pw-search input::placeholder{color:var(--color-text-tertiary)}
.pw-search.is-sm input{height:var(--size-control-sm);font-size:var(--font-size-sm);line-height:var(--font-line-sm)}
.pw-search.is-lg input{height:var(--size-control-lg);font-size:var(--font-size-lg);padding-left:var(--space-10);line-height:var(--font-line-lg)}
.pw-search.is-lg > .ico{left:var(--space-4)}
.pw-search .kc{position:absolute;right:var(--space-2);top:50%;transform:translateY(-50%)}
.pw-search.has-key input{padding-right:var(--space-10)}
/* Keycap (fOS): a word in a key, the bottom edge weighted. */
.kc{display:inline-flex;align-items:center;height:var(--space-5);padding:0 var(--space-1-5);border:1px solid var(--color-line-default);border-bottom-width:2px;border-radius:var(--radius-tiny);background:var(--color-surface-raised);font:inherit;font-size:var(--font-size-xs);font-weight:var(--font-weight-medium);color:var(--color-text-secondary);pointer-events:none;line-height:var(--font-line-sm)}

/* Menu and note: one overlay surface, two jobs. A menu chooses (items 36
   high, arrows, Escape returns focus); a note explains a value. */
.pw-menu,.pw-note{position:absolute;z-index:var(--z-popover);background:var(--color-surface-overlay);border:1px solid var(--color-line-default);border-radius:var(--radius-surface);box-shadow:var(--shadow-2)}
.pw-menu{min-width:12.5rem;max-width:20rem;padding:var(--space-1)}
.pw-menu [role="menuitem"],.pw-menu-item{display:flex;flex-direction:column;justify-content:center;gap:1px;width:100%;min-height:var(--size-control-md);text-align:left;font:inherit;font-size:var(--font-size-md);color:var(--color-text-primary);background:none;border:0;border-radius:var(--radius-tiny);padding:var(--space-2) var(--space-3);cursor:pointer}
.pw-menu [role="menuitem"]:hover,.pw-menu-item:hover{background:var(--color-state-hover)}
.pw-menu [role="menuitem"]:focus-visible{outline-offset:-1px}
.pw-menu [role="menuitem"][aria-checked="true"],.pw-menu-item.active{background:var(--color-state-selected)}
.pw-menu [role="menuitem"][data-danger]{color:var(--color-danger-text)}
.pw-menu .mh,.pw-menu-now{font-size:var(--font-size-sm);color:var(--color-text-tertiary);padding:var(--space-2) var(--space-3) var(--space-1);line-height:var(--font-line-sm)}
.pw-menu hr{border:0;border-top:1px solid var(--color-line-subtle);margin:var(--space-1) 0}
.pw-note{width:max-content;max-width:17.5rem;padding:var(--space-2) var(--space-3);font-size:var(--font-size-sm);font-weight:var(--font-weight-regular);line-height:var(--font-line-md);color:var(--color-text-primary);white-space:normal;text-align:left}
.pw-note b{display:block;font-size:var(--font-size-sm);color:var(--color-text-tertiary);margin-bottom:var(--space-1);line-height:var(--font-line-sm)}

/* Section tabs: text with a 2 px position mark on the bar (allowed, 19 Sep).
   A segmented control stays for a mode switch (VAT, theme, language). */
.utabs{display:flex;gap:var(--space-1);border-bottom:1px solid var(--color-line-subtle);overflow-x:auto;scrollbar-width:none}
.utabs::-webkit-scrollbar{display:none}
.utabs [role="tab"],.utabs .utab{position:relative;flex:none;height:var(--size-control-md);padding:0 var(--space-2);border:0;background:transparent;color:var(--color-text-secondary);font:inherit;font-size:var(--font-size-md);font-weight:var(--font-weight-medium);display:inline-flex;align-items:center;gap:var(--space-2);cursor:pointer;white-space:nowrap}
.utabs [role="tab"]:hover,.utabs .utab:hover{color:var(--color-text-primary)}
.utabs [role="tab"][aria-selected="true"],.utabs .utab[aria-current]{color:var(--color-text-primary)}
.utabs [role="tab"][aria-selected="true"]::after,.utabs .utab[aria-current]::after{content:"";position:absolute;left:var(--space-2);right:var(--space-2);bottom:-1px;height:var(--space-0-5);background:var(--color-customer-solid);border-radius:1px}
.utabs [role="tab"]:focus-visible{outline-offset:-1px}

/* Source: a supplier's own square mark and their name. Round is a person. */
.pw-source{display:inline-flex;align-items:center;gap:var(--space-2);min-width:0;font-size:var(--font-size-md);font-weight:var(--font-weight-regular);color:var(--color-text-primary)}
.pw-source > span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pw-source.is-off{color:var(--color-text-tertiary)}
.sup-badge.is-md{width:20px;height:var(--space-5)}
.sup-badge.is-md .sup-badge-letter{font-size:var(--font-size-sm);line-height:var(--font-line-sm)}

/* Meter: a share, the numbers above it, never inside. */
.pw-meter{height:var(--space-1-5);border-radius:var(--radius-pill);background:var(--color-surface-sunken);overflow:hidden}
.pw-meter i{display:block;height:100%;border-radius:var(--radius-pill);background:var(--color-customer-solid);transition:width var(--dur-slow) var(--ease-color)}
.pw-meter.is-warning i{background:var(--color-warning-solid)}
.pw-meter.is-danger i{background:var(--color-danger-solid)}

/* Delta: direction is the chevron; colour only when the caller says the
   direction is good or bad. */
.pw-delta{display:inline-flex;align-items:center;gap:var(--space-1);font-variant-numeric:tabular-nums;color:var(--color-text-primary);white-space:nowrap}
.pw-delta .ico{width:var(--space-3);height:var(--space-3)}
.pw-delta.is-good{color:var(--color-success-text)}
.pw-delta.is-bad{color:var(--color-danger-text)}
.pw-delta.is-flat{color:var(--color-text-tertiary)}
.pw-delta small{color:var(--color-text-tertiary);font-size:var(--font-size-sm);line-height:var(--font-line-sm)}

/* Empty, error, loading (fOS): left-aligned, a title with a period, one
   line, the action that mirrors the title. Filters hide data → Clear filters. */
.pw-empty{display:grid;gap:var(--space-2);justify-items:start;padding:var(--space-5) var(--space-4);border:1px solid var(--color-line-subtle);border-radius:var(--radius-card);background:var(--color-surface-raised);font-size:var(--font-size-md);color:var(--color-text-secondary)}
.pw-empty > .ico{color:var(--color-text-tertiary)}
.pw-empty b{font-size:var(--font-size-md);font-weight:var(--font-weight-semibold);color:var(--color-text-primary)}
.pw-empty p{margin:0 0 var(--space-1)}
.pw-empty.is-error b{color:var(--color-danger-text)}
.pw-empty.is-quiet{border:0;background:transparent;padding:var(--space-8) var(--space-4);justify-items:center;text-align:center}

/* Inline editor: a cell that becomes its control in place. */
.pw-cell-edit{display:inline-flex;align-items:center;gap:var(--space-2);max-width:15rem;font:inherit;font-size:var(--font-size-md);color:var(--color-text-primary);background:none;border:0;border-radius:var(--radius-control);padding:var(--space-1) var(--space-2);margin:calc(-1*var(--space-1)) calc(-1*var(--space-2));cursor:text;text-align:left}
.pw-cell-edit:hover{box-shadow:inset 0 0 0 1px var(--color-line-default)}

/* A row of chips, a row of tags. */
.pw-chips{display:flex;flex-wrap:wrap;gap:var(--space-2);align-items:center}
.pw-tags{display:inline-flex;flex-wrap:wrap;gap:var(--space-1);align-items:center}

/* Honest number (fOS .kpi + the clock and the door). */
.pw-kpi{background:var(--color-surface-raised);border:1px solid var(--color-line-subtle);border-radius:var(--radius-card);padding:var(--space-3) var(--space-4);min-width:0}
.pw-kpi b{display:block;font-size:var(--font-size-2xl);line-height:var(--font-line-2xl);font-weight:var(--font-weight-bold);letter-spacing:-.01em;color:var(--color-text-primary)}
.pw-kpi a b{color:inherit}
.pw-kpi > span{font-size:var(--font-size-sm);color:var(--color-text-tertiary);line-height:var(--font-line-sm)}
.pw-kpi .clk{display:block;font-size:var(--font-size-sm);color:var(--color-text-tertiary);margin-top:var(--space-1);line-height:var(--font-line-sm)}
.pw-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(9.5rem,1fr));gap:var(--space-2)}

/* ============================================================ EVERY WIDTH */
/* fOS v3.27. R2: under 860 the first column of a wide table stays put while
   the rest scroll. R9: a phone's page gutter is 16. R8: a finger is not a
   cursor - on touch every control is at least 36 high, and a switch keeps a
   hit area bigger than its drawing. */
@media (max-width:860px){
  .cat-table th:first-child,.cat-table td:first-child{position:sticky;left:0;z-index:1;background:var(--color-surface-raised)}
  .cat-table thead th:first-child{background:var(--color-surface-panel);z-index:2}
}
@media (max-width:640px){
  .scroll-pad{padding:var(--space-4)}
  /* A name in a scrolling table wraps to two lines, not one word per line. */
  .table-card td:first-child{min-width:11rem}
  /* The three figures at the head of a service: the price is read first and
     is the longest, so it takes the row; the other two share the next. */
  .pw-kpis{grid-template-columns:repeat(2,minmax(0,1fr))}
  .pw-kpis > :first-child{grid-column:1 / -1}
}
@media (pointer:coarse){
  .btn-sm,.chip{height:var(--size-control-md)}
  .vat button,.mclass,.mlevels button,.lang button,.cat-view button{height:calc(var(--size-control-md) - 2 * var(--space-0-5))}
  .pw-cell-edit{min-height:var(--size-control-md)}
  .switch::after{content:"";position:absolute;inset:calc(-1 * var(--space-2))}
}

/* ---- the phone's tab bar (fOS section 9, "Tab bar") ----
   Under 860 the sections leave the top bar for a strip at the bottom of the
   screen: four items and More, icon over label, the current one tinted -
   never an edge mark. It is the shell's last grid row, so nothing scrolls
   under it and the last row of a list is never hidden. */
.tabbar{display:none}
@media (max-width:860px){
  .app{grid-template-rows:auto minmax(0,1fr) auto;grid-template-areas:"topbar" "main" "tabbar"}
  .topbar .nav{display:none}
  .tabbar{
    grid-area:tabbar;display:grid;grid-template-columns:repeat(5,minmax(0,1fr));
    padding-bottom:env(safe-area-inset-bottom);
    background:var(--color-surface-raised);border-top:1px solid var(--color-line-subtle);
  }
  .tabbar-more{display:grid;position:relative}
  .tabbar-more > .pw-menu{bottom:calc(100% + var(--space-1));right:var(--space-1)}
  .tabbar-item{
    position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-1);
    min-height:var(--size-control-lg);margin:var(--space-1);border:0;border-radius:var(--radius-control);
    background:none;color:var(--color-text-secondary);font:inherit;font-size:var(--font-size-sm);line-height:var(--font-line-sm);
    cursor:pointer;
  }
  .tabbar-item[aria-current="page"]{background:var(--color-customer-tint);color:var(--color-customer-text)}
  /* The count sits on the icon's shoulder, out of the label's way. */
  .tabbar-item .cnt{position:absolute;top:var(--space-1);left:calc(50% + var(--space-3))}
  .toasts{bottom:calc(var(--size-header) + env(safe-area-inset-bottom) + var(--space-4))}
}

/* ---- AI agents (Settings) and the approval page ---- */
.agent-switch,.consent-row{display:flex;align-items:flex-start;gap:var(--space-3);margin:0 0 var(--space-4);cursor:pointer}
.agent-switch input,.consent-row input{margin-top:var(--space-1)}
.agent-switch small,.consent-row small{display:block;margin-top:var(--space-1);font-size:var(--font-size-sm);line-height:var(--font-line-sm);color:var(--color-text-secondary)}
.agent-cap{max-width:24rem}
.agent-url{margin:var(--space-2) 0 var(--space-4)}
.agent-guides{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(16rem,100%),1fr));gap:var(--space-4)}
.agent-guides h4{margin:0 0 var(--space-1);font-size:var(--font-size-md);font-weight:var(--font-weight-semibold)}
.agent-guides p{margin:0;font-size:var(--font-size-sm);line-height:var(--font-line-sm);color:var(--color-text-secondary)}
.agent-guides code,.agent-guides a{font-size:var(--font-size-sm)}
.agent-guides a{color:var(--color-customer-text)}
.agent-new{margin-top:var(--space-4)}
.agent-scopes{display:flex;flex-wrap:wrap;gap:var(--space-2) var(--space-4);margin:0 0 var(--space-4);font-size:var(--font-size-md)}
.agent-scopes label{display:inline-flex;align-items:center;gap:var(--space-2)}
.consent{margin-top:var(--space-4)}
.consent .hint{margin:0 0 var(--space-4)}
.consent-actions{display:flex;justify-content:flex-end;gap:var(--space-2)}
.consent-actions .btn{width:auto;margin:0}

/* The delivery table lives inside the supplier table, and the outer table's
   column widths and phone rules (no head, 104px columns) reached into it. */
.sup-table .sup-ship-table th,.sup-table .sup-ship-table td{min-width:0}
/* On a phone each delivery service is a small block: name and price on one
   line, when it arrives and the cut-off under them, the note last. */
@media (max-width:860px){
  .sup-table .sup-ship-table thead{display:none}
  .sup-ship-table tr{display:grid;grid-template-columns:1fr auto;column-gap:var(--space-3);
    padding:var(--space-2) 0;border-top:1px solid var(--color-line-subtle)}
  .sup-ship-table td{display:block;border:0;padding:0;width:auto}
  .sup-table .sup-ship-table td:first-child{width:auto}
  .sup-ship-table td.sup-price{grid-column:2;grid-row:1}
  .sup-ship-table td[data-label]{grid-column:1 / -1;color:var(--color-text-secondary)}
  .sup-ship-table td[data-label]:empty{display:none}
  .sup-ship-table td[data-label]::before{content:attr(data-label) " ";color:var(--color-text-tertiary)}
}

/* A filter select (fOS "the filter row", ui.css .fsel): a filter chip with a
   drawn chevron. The native select fills the chip, transparent, so the chip's
   own border, surface and active fill are what shows; the system arrow was a
   heavy black glyph that matched nothing else on the screen. */
.fsel{position:relative;display:inline-flex;align-items:center;height:var(--size-control-sm);max-width:100%;
  border:1px solid var(--color-line-control);border-radius:var(--radius-control);background:var(--color-surface-raised);
  color:var(--color-text-primary);transition:background-color var(--dur-fast) var(--ease-color),border-color var(--dur-fast) var(--ease-color)}
.fsel:hover{background:var(--color-state-hover)}
.fsel[data-active="true"]{background:var(--color-customer-tint-strong);color:var(--color-customer-text);border-color:transparent}
.fsel-native{appearance:none;width:100%;height:100%;min-width:0;max-width:100%;
  padding:0 calc(var(--space-2) + var(--space-4)) 0 var(--space-2);border:0;border-radius:inherit;background:transparent;
  color:inherit;font:inherit;font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);line-height:var(--font-line-sm);
  cursor:pointer;text-overflow:ellipsis}
.fsel-chev{position:absolute;right:var(--space-2);width:var(--size-icon-sm);height:var(--size-icon-sm);fill:none;stroke:currentColor;
  stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;pointer-events:none}
/* Focus is the chip's own frame, as on a field. */
.fsel:focus-within{border-color:var(--color-state-focus);box-shadow:inset 0 0 0 1px var(--color-state-focus)}
.fsel-native:focus-visible{box-shadow:none;outline-color:transparent}
.fsel-native option{color:var(--color-text-primary);background:var(--color-surface-raised)}
@media (pointer:coarse){.fsel{height:var(--size-control-md)}}
