/* ============================================================================
   AFTERSHOCK PC · INVENTORY & PRODUCTION — design system, fifth pass (2026-07-24)
   Reference point: the polished inventory SaaS (Unleashed / inFlow / Cin7 Core).
   White cards float on a soft cool-grey canvas; a white sidebar + topbar frame
   it; ONE brand accent (Aftershock red) carries links, focus, primary acts and
   the "you are here" state. Rounded, softly elevated, icon-led — friendly and
   legible, not the old hairline-dense bench.

   Type    Geist (all UI; hierarchy = weight 800/700/600/500) with a modern
           system fallback (Segoe UI Variable), never Trebuchet, never Inter.
           Geist Mono is reserved for HASHES and payloads only — SKUs, serials
           and money wear the sans with tabular figures, because every coding
           mono slashes its zero and that reads as a defect on a picking sheet.
           Base 14.5px: the 13.5px ramp was unreadable at 100% zoom.
   Color   two themes over one token set (:root = light, [data-theme=dark]).
           Calm neutrals so the red reads premium, not alarming; soft-tinted
           status pills (moss/ochre/rust/slate) for state.
   Shape   9px controls / 14px cards, soft diffuse shadows, rounded pills.
   Rules   one accent · tabular numerals for data · comfortable density.
   Contrast: text pairs checked ≥ 4.5:1 on both grounds.
   ========================================================================== */

:root {
  /* Canvas & surfaces — soft cool neutrals; white cards float on a tinted room */
  --bg: #f4f6fa;            /* the room — soft cool grey */
  --panel: #ffffff;         /* cards, sidebar, topbar */
  --panel2: #eef1f6;        /* wells, hovers */
  --panel3: #f7f9fc;        /* table-head tint, subtle wells */
  --border: #e6e9f0;        /* soft hairline */
  --border-strong: #d3d8e2; /* control edges */
  --text: #171a23;          /* rich ink, faint cool cast */
  --muted: #69707f;         /* cool grey */
  --faint: #99a1b1;         /* placeholders, resting icons */

  --accent: #d51f27;        /* Aftershock red — links, focus, the ONE accent */
  --accent-hover: #b81820;  /* darker for text-on-wash + button hover */
  --accent-deep: #b3141c;   /* primary button fill */
  --accent-ink: #ffffff;    /* text on red fills */
  --accent-wash: #fdeceD;   /* soft red tint — active nav, module icons */
  --accent-wash-bd: #f6d3d5;
  --ink-btn: #171a23;       /* inverted slips (toast) */
  --ink-btn-ink: #f4f6fa;

  --ok: #1c7d43;   --ok-bg: #e6f6ec;   --ok-bd: #b7e2c5;   /* moss */
  --warn: #8a6100; --warn-bg: #fbf0d5; --warn-bd: #eed699; /* ochre */
  --bad: #c62828;  --bad-bg: #fdeaea;  --bad-bd: #f3c8c8;  /* rust */
  --bad-solid: #d32f2f;
  --info: #1f5fbf; --info-bg: #e8f0fd; --info-bd: #c1d6f4; /* slate */
  --chip: #eef1f6;

  --shadow: rgba(23, 27, 42, 0.10);
  --shadow-sm: 0 1px 2px rgba(23,27,42,0.06), 0 1px 1px rgba(23,27,42,0.04);
  --shadow-md: 0 6px 20px rgba(23,27,42,0.09), 0 2px 6px rgba(23,27,42,0.05);
  --shadow-lg: 0 20px 46px rgba(23,27,42,0.20);
  --ring: 0 0 0 3px rgba(213,31,39,0.16);
  --overlay: rgba(18, 20, 30, 0.44);
  --selection: #fbd7d9;
  --accent-wash-solid: #fdeced;

  /* Geist (Vercel, 2023) — drawn for interfaces and dense data rather than for
     marketing pages, with numerals that line up. Chosen over the usual default
     because its zero is a plain oval: the previous mono slashed its zero, which
     is a coding convention and reads as a defect on a picking sheet.
     Geist Mono is kept for hashes only, where character-cell alignment is the
     whole point; SKUs and serials now wear the sans with tabular figures. */
  /* Geist first, then the CJK faces (2026-09-21). Geist has no Chinese glyphs,
     so without these Windows substitutes something at random and the Chinese
     comes out in a different face on every machine. Latin still renders in
     Geist — a browser falls through this list PER GLYPH, not per string. */
  --cjk: "Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", "Source Han Sans SC";
  --sans: "Geist", system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI", var(--cjk), Arial, sans-serif;
  --mono: "Geist Mono", ui-monospace, "Cascadia Mono", Menlo, Consolas, var(--cjk), monospace;
  --r-sm: 6px;
  --r: 9px;
  --r-lg: 14px;
  --r-pill: 999px;
  color-scheme: light;
}

:root[data-theme="dark"] {
  --bg: #0e1015;            /* soft near-black, faint blue cast */
  --panel: #171a21;         /* cards, sidebar, topbar */
  --panel2: #1f232d;
  --panel3: #13161c;
  --border: #262b36;
  --border-strong: #363c49;
  --text: #e7e9ee;          /* cool light ink */
  --muted: #99a1b1;
  --faint: #666e7e;

  --accent: #ff5158;        /* signal red, brightened for dark ground */
  --accent-hover: #ff6f75;
  --accent-deep: #d81f2a;
  --accent-ink: #ffffff;
  --accent-wash: #2a1517;   /* deep red tint */
  --accent-wash-bd: #4a2024;
  --ink-btn: #e7e9ee;       /* inverted slips flip to paper */
  --ink-btn-ink: #14161c;

  --ok: #6ed49a;   --ok-bg: #12251a;   --ok-bd: #274632;
  --warn: #e6bd63; --warn-bg: #2a2210; --warn-bd: #4f3f1c;
  --bad: #ff8a8a;  --bad-bg: #2c1416;  --bad-bd: #562227;
  --bad-solid: #c0392f;
  --info: #7fb0f2; --info-bg: #121d2e; --info-bd: #2a3f5f;
  --chip: #1f232d;
  --shadow: rgba(0, 0, 0, 0.5);
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.4);
  --shadow-md: 0 6px 20px rgba(0,0,0,0.45), 0 2px 6px rgba(0,0,0,0.35);
  --shadow-lg: 0 20px 46px rgba(0,0,0,0.6);
  --ring: 0 0 0 3px rgba(255,81,88,0.24);
  --overlay: rgba(0, 0, 0, 0.6);
  --selection: #4a2024;
  color-scheme: dark;
}

* { box-sizing: border-box; }
/* `hidden` MUST WIN. Any component rule that sets `display` on an element the JS
   later hides (`.pick-search { display: flex }`, `.pick-chosen { display: flex }`)
   silently defeats the attribute — which is how the product picker ended up
   showing an empty search box under every line it had already filled. One rule
   here instead of remembering it at every call site. */
[hidden] { display: none !important; }
html, body { margin: 0; height: 100%; }
/* UI SCALE. One number, and the hook for a density preference if that ever
   becomes a setting. It was 0.9 — one Chrome notch shrunk into the stylesheet —
   and that was a mistake twice over: everything read small, and because `zoom`
   multiplies the CSS pixel, every px cap in this file (page width, breakpoints)
   silently bound ~11% earlier than its number said. Browser zoom is the user's
   control, not ours; we ship at 1 and let them turn the dial. */
body { zoom: 1; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 14.5px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-optical-sizing: auto;
}

::selection { background: var(--selection); }

/* Codes (SKU, serial, id) wear the SANS with tabular figures and a little extra
   tracking — they read as codes and their digits still line up, without the
   slashed zero every coding mono ships. True hashes keep a real monospace,
   where per-character alignment is the entire point. */
.num { font-variant-numeric: tabular-nums; }
.mono { font-variant-numeric: tabular-nums; font-feature-settings: "zero" 0; letter-spacing: 0.01em; font-weight: 500; }

/* A SKU IS SELECTED BY ITSELF (Aaron B, 2026-09-22: "double-clicking a SKU also
   highlights the surrounding product/category text"). A code sits inside a cell
   that also carries a name and a category, and a double-click takes a word — so
   grabbing a SKU to paste into a supplier's site took a careful drag every
   time, fifty times a day. `user-select: all` means one click takes the WHOLE
   code and nothing either side of it. Codes only: a payload dump is text to
   read, not an identifier to lift, so it keeps ordinary word selection. */
td .mono, th .mono, .kicker + .mono, .pd-title .mono { user-select: all; -webkit-user-select: all; }
.ledger-raw, .ledger-raw .mono { user-select: text; -webkit-user-select: text; }
.hash, code, pre { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* ---- Headings inside content ----
   Seventy `<h3>`s in this app ran on the browser default, `1.17em`, which at
   14.5px body is 16.965px and inside a report head 21.75px. Those are type
   sizes nobody chose, and they were showing up in the audit as fractional
   leaf sizes. Declared here, near the top, ON PURPOSE: every namespaced rule
   later in the file (`.danger-zone h3` red, `.ins-chart-head h3` 13px) has the
   same specificity and therefore still wins on source order. */
.card h3, .card-head h3 { font-size: 14px; font-weight: 600; color: var(--text); letter-spacing: -0.004em; margin: 0 0 8px; }

.app { display: flex; min-height: 100vh; min-height: 100dvh; }

/* ---- Sidebar: brand, then the module tree — icon-led, always all six ---- */
.sidebar {
  width: 238px; flex-shrink: 0;
  background: var(--panel); border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  padding: 14px 14px 16px; overflow-y: auto;
  scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent;
  /* The rail owns one viewport and scrolls INSIDE it (align-self kills the
     flex stretch that would otherwise defeat sticky). Without this the aside
     grows to the height of the page, and "bottom left" means the bottom of a
     3000px document — the theme flip and the user chip were only reachable by
     scrolling the whole app to its end. */
  align-self: flex-start; position: sticky; top: 0; height: 100vh; height: 100dvh;
}
.sidebar::-webkit-scrollbar { width: 10px; }
.sidebar::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 10px; border: 3px solid var(--panel); }
.sidebar::-webkit-scrollbar-track { background: transparent; }

/* The QUAKE mark (owner, 2026-09-24), then a hairline separating brand from the
   tree. The mark is stacked — the ring over the word — so it is sized by its
   height; the ring and letters take the theme ink, the arrow and dot stay red. */
.brand { display: block; padding: 6px 8px 14px; margin-bottom: 9px; color: var(--text); text-decoration: none; border-bottom: 1px solid var(--border); }
.brand .quake-mark { height: 60px; width: auto; display: block; }

#nav { display: flex; flex-direction: column; flex: 1; gap: 2px; }

/* Every nav row: icon slot + label, comfortable target, rounded */
#nav a {
  display: flex; align-items: center; gap: 11px;
  padding: 8px 11px; border-radius: var(--r);
  color: var(--muted); text-decoration: none;
  font-size: 14.5px; font-weight: 500; line-height: 1.25;
  transition: background 0.12s ease, color 0.12s ease;
}
#nav a:hover { color: var(--text); background: var(--panel2); }
#nav .nav-ic { flex: none; width: 20px; height: 20px; display: grid; place-items: center; color: var(--faint); transition: color 0.12s ease; }
#nav .nav-ic svg { width: 20px; height: 20px; }
#nav a:hover .nav-ic { color: var(--muted); }
#nav .nav-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Home — the top entry */
#nav a.nav-home { color: var(--text); font-weight: 600; margin-bottom: 2px; }
#nav a.nav-home.active { color: var(--accent); background: var(--accent-wash); }
#nav a.nav-home.active .nav-ic { color: var(--accent); }

/* Module rows: icon + name + disclosure chevron; open module goes ink + red icon.
   Clicking a module navigates to its entry page — the router re-renders the
   tree with that module expanded, so there's no accordion state to manage. */
#nav a.nav-mod { margin-top: 7px; color: var(--muted); font-weight: 600; }
#nav a.nav-mod .nav-label { font-size: 14.5px; font-weight: 700; letter-spacing: -0.006em; }
#nav a.nav-mod .chev { flex: none; width: 15px; height: 15px; color: var(--faint); transition: transform 0.16s ease, color 0.12s ease; }
#nav a.nav-mod .chev svg { width: 15px; height: 15px; }
#nav a.nav-mod.open { color: var(--text); }
#nav a.nav-mod.open .nav-ic { color: var(--accent); }
#nav a.nav-mod.open .chev { transform: rotate(90deg); color: var(--muted); }

/* Sub-pages: indented under the open module, no icon, accent pill when active */
#nav a.nav-sub { padding: 6px 11px 6px 43px; font-size: 14px; font-weight: 500; color: var(--muted); }
#nav a.nav-sub:hover { color: var(--text); background: var(--panel2); }
#nav a.nav-sub.active { color: var(--accent); background: var(--accent-wash); font-weight: 600; }
/* A live count on a nav row — earned by pages where the number is the reason to
   click. Red because every one of them is a decision nobody has made yet. */
#nav a.nav-sub .nav-count {
  margin-left: auto; font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums;
  background: var(--bad-bg); border: 1px solid var(--bad-bd); color: var(--bad);
  border-radius: var(--r-pill); padding: 0 7px; line-height: 17px;
}
#nav a.nav-sub .nav-count[hidden] { display: none; }

#nav a.disabled { color: var(--faint); cursor: default; pointer-events: none; }
#nav a.disabled:hover { background: none; }
.soon { margin-left: auto; font-size: 10px; font-weight: 600; background: var(--chip); border: 1px solid var(--border); border-radius: var(--r-pill); padding: 1px 8px; color: var(--muted); }

/* ---- Top bar ---- */
.content { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar { height: 58px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; padding: 0 24px; background: var(--panel); gap: 20px; }
.integrity { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.topbar-search { position: relative; flex: 1; max-width: 520px; }
.topbar-search input { width: 100%; background: var(--panel2); border: 1px solid transparent; border-radius: var(--r-pill); padding: 9px 16px; color: var(--text); font: inherit; font-size: 14px; transition: background .12s, border-color .12s, box-shadow .12s; }
.topbar-search input::placeholder { color: var(--faint); }
.topbar-search input:focus { outline: none; background: var(--panel); border-color: var(--accent); box-shadow: var(--ring); }
.search-results { position: absolute; top: 48px; left: 0; right: 0; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--shadow-lg); max-height: 62vh; overflow-y: auto; display: none; z-index: 40; padding: 6px; }
.search-results.show { display: block; }
/* WHAT KIND OF THING each block of results is (owner, 2026-09-27: "Systems",
   "Pick list" were hard to see). Full text colour, heavier, with a rule above
   every block after the first, so the eye finds the group before the rows. */
.sr-group { font-size: 11.5px; font-weight: 800; color: var(--text); padding: 11px 10px 5px; text-transform: uppercase; letter-spacing: .06em; }
.sr-item + .sr-group { border-top: 1px solid var(--border); margin-top: 6px; }
.sr-group .muted { font-weight: 600; letter-spacing: 0; }
.sr-item { display: flex; align-items: baseline; gap: 10px; padding: 7px 11px; border-radius: var(--r); color: var(--text); text-decoration: none; font-size: 14px; }
/* The keyboard cursor is a CLASS, not :hover. With both, wherever the mouse
   happens to be resting fights the arrow keys and two rows light up at once. */
.sr-item:hover, .sr-item.on { background: var(--panel2); }
.sr-item .sr-text { min-width: 0; flex: 1; }
.sr-item .sr-label { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sr-item .sr-sub { display: block; font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sr-item .sr-type { font-size: 11.5px; font-weight: 600; color: var(--muted); white-space: nowrap; }
/* Never truncate in silence: this row says how much was left out and where. */
.sr-more { display: block; padding: 9px 11px; margin-top: 4px; border-top: 1px solid var(--border); color: var(--muted); font-size: 12.5px; text-decoration: none; }
.sr-more:hover, .sr-more.on { background: var(--panel2); color: var(--text); }
.sr-empty { padding: 14px; color: var(--muted); font-size: 14px; }
/* The full results page */
/* R-UI-1: the results were nine page-wide boxes stacked down the screen, one
   of them around a single row. Groups flow instead — a grid, so a two-hit
   group is a two-hit-sized box and reading order stays the server's relevance
   order (row-major), which CSS `columns` would have scrambled. */
/* min() inside the minmax, because a bare 430px track does not shrink: on a
   375px phone the grid kept its 430 and pushed the page 69px sideways. */
.sr-groups { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(430px, 100%), 1fr)); gap: 0 14px; align-items: start; }
.sr-basis { max-width: 74ch; margin: 4px 0 0; }
.sr-hit-foot { padding: 9px 2px 2px; color: var(--muted); font-size: 12.5px; }
/* WAS `.sp-row`, which specs.js also owns — and the specs form's two-column
   grid is 2,400 lines further down the file, so it won the tie and had been
   laying out every global-search result. `sr-` is the search surfaces' own
   prefix; the dropdown already uses it. */
.sr-hit { display: flex; align-items: baseline; gap: 12px; padding: 9px 4px; border-top: 1px solid var(--border); color: var(--text); text-decoration: none; }
.sr-hit:first-of-type { border-top: 0; }
.sr-hit:hover { background: var(--panel2); }
/* On the page the sub is a second LINE, not a tail on the first: a SKU run on
   after the product name reads as part of the name. */
.sr-hit .sr-text { min-width: 0; flex: 1; }
.sr-hit .sr-sub { display: block; font-size: 12.5px; color: var(--muted); margin-top: 1px; }
.sr-count { margin: 4px 0 0; }
.search { background: var(--panel); border: 1px solid var(--border-strong); border-radius: var(--r); padding: 8px 12px; color: var(--text); font: inherit; min-width: 240px; margin-bottom: 12px; }
.search::placeholder { color: var(--faint); }
.search:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
/* NO PAGE-WIDTH CAP. This was 1340px, then 1880px, and both were the same bet:
   guess the widest useful page and letterbox every monitor bigger than the
   guess. On a 2560 screen the 1880 cap parked ~600px of dead grey to the right
   of every screen in the app. A wide monitor is bought to be used. Width is now
   governed where it belongs — per component, in ch/minmax — so tables and
   boards run edge to edge while prose and lone inputs stay a readable measure. */
.view { padding: 26px 32px 90px; width: 100%; max-width: none; }

/* ---- Page head: kicker + heavy title, hairline under ---- */
/* THE RED KICKER IS MASTHEAD-ONLY. The one accent was doing decoration on 24
   in-card tiles, and a small-caps eyebrow repeated on every box is the AI-look
   tell itself. A hue that means "you are here / this is the act" cannot also
   mean "this box has a label". Accent and the wide 0.11em tracking now belong
   to the page masthead; anywhere else a kicker reads as the quiet label it
   actually is. A masthead this rule misses goes muted — quiet, never wrong. */
.kicker { font-size: 11.5px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }
.page-head .kicker, .cat-detail-head .kicker { letter-spacing: 0.11em; color: var(--accent); }
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 20px; padding-bottom: 14px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.page-head h1 { font-size: 25px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.15; margin: 3px 0 0; }
.page-head .muted { font-size: 14px; }
.row-gap { display: flex; gap: 8px; flex-wrap: wrap; }

/* ---- Stat tiles: the first one dominates (hierarchy = an opinion) ---- */
.stats, .stats4, .tr-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; margin-bottom: 22px; }
/* R-UI-1: a single tile is the width of a tile. `auto-fit` stretches one child
   across the whole page, which is a statistic pretending to be a banner. */
.stats4.one { grid-template-columns: minmax(190px, 300px); }
/* A form or a list that is three fields wide should be three fields wide. */
.card.narrow, .modal.narrow { max-width: 620px; }
.stats > .stat-card:first-child { grid-column: span 2; }
.stats > .stat-card:first-child .stat-num { font-size: 37px; }
/* …but a strip of five is not a strip of four. The double-width first tile is
   hierarchy while there is room for it; at five tiles it is six cells in a
   five-column grid, and the last tile drops onto a row by itself, which reads
   as a mistake rather than as an opinion. The kit view of a product record is
   where this showed up. Four tiles remains the number to aim for. */
.stats:has(> .stat-card:nth-child(5)) > .stat-card:first-child { grid-column: auto; }
.stats:has(> .stat-card:nth-child(5)) > .stat-card:first-child .stat-num { font-size: 28px; }
.stat-card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); padding: 15px 17px 14px; box-shadow: var(--shadow-sm); }
.stat-label { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.stat-num { font-family: var(--sans); font-size: 28px; font-weight: 700; letter-spacing: -0.022em; margin-top: 3px; line-height: 1.1; font-variant-numeric: tabular-nums; }
.stat-sub { font-size: 13px; color: var(--muted); margin-top: 3px; }

/* ---- Home: the module switchboard ---- */
.mod-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-bottom: 22px; }
@media (max-width: 1100px) { .mod-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .mod-grid { grid-template-columns: 1fr; } }
.mod-card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); padding: 17px 18px 15px; cursor: pointer; display: flex; flex-direction: column; box-shadow: var(--shadow-sm); transition: border-color 0.13s, box-shadow 0.13s, transform 0.13s; }
.mod-card:hover { border-color: var(--border-strong); box-shadow: var(--shadow-md); transform: translateY(-2px); }
.mod-ic { width: 38px; height: 38px; border-radius: 11px; background: var(--accent-wash); color: var(--accent); display: grid; place-items: center; margin-bottom: 12px; }
.mod-ic svg { width: 21px; height: 21px; }
.mod-name { font-size: 17px; font-weight: 700; letter-spacing: -0.015em; color: var(--text); text-decoration: none; }
.mod-card:hover .mod-name { color: var(--accent); }
.mod-job { font-size: 13.5px; color: var(--muted); margin-top: 2px; }
.mod-stat { font-size: 13px; color: var(--muted); margin-top: 9px; }
.mod-stat:empty { display: none; }
.mod-links { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 12px; padding-top: 11px; border-top: 1px solid var(--border); font-size: 13.5px; }
.mod-links a { text-decoration: none; font-weight: 500; }
.mod-links a:hover { text-decoration: underline; text-underline-offset: 2px; }

/* ---- Home dashboard: sales KPI row + stock-on-hand bars ----
   Stat tiles, not charts: three headline numbers whose job is magnitude, so
   they get figures, not marks. The bars below use ONE hue (the brand accent)
   because the reader is comparing sizes across warehouses, not telling
   identities apart — a colour each would imply a meaning that isn't there. */
.kpi-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-bottom: 6px; }
@media (max-width: 900px) { .kpi-row { grid-template-columns: 1fr; } }
.kpi { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); padding: 15px 18px 14px; box-shadow: var(--shadow-sm); }
/* The twin of `.stat-label`: one tile-label style across both facts strips. */
.kpi-label { font-size: 12.5px; font-weight: 600; color: var(--muted); letter-spacing: 0; }
/* Proportional figures: at display size tabular-nums makes a number look loose. */
.kpi-value { font-family: var(--sans); font-size: 32px; font-weight: 800; letter-spacing: -0.028em; line-height: 1.12; margin-top: 5px; font-variant-numeric: normal; }
.kpi-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 7px; font-size: 13px; }
.kpi-basis { font-size: 12.5px; margin: 0 2px 22px; }
.delta { font-size: 12.5px; font-weight: 700; white-space: nowrap; }
.delta.up { color: var(--ok); }
.delta.down { color: var(--bad); }
.delta.flat { color: var(--muted); font-weight: 500; }

/* The margin strip (0077) — sell, cost, margin, margin % for one order line.
   Hairline grid rather than four cards: these four numbers are ONE sentence
   and spacing them apart invites reading them as unrelated stats. `.at-risk`
   is the ochre a swap leaves behind until sales answers it; `.unknown` is the
   deliberately quieter type an "at least / at most" figure gets, because a
   bound is not a measurement and must not be dressed as one. */
.mgn { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--border);
       border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; }
.mgn-cell { background: var(--panel); padding: 11px 14px 10px; min-width: 0; }
.mgn-cell.hero { background: var(--panel2, var(--panel)); }
.mgn-label { display: block; font-size: 11.5px; font-weight: 700; color: var(--muted); letter-spacing: 0.04em; text-transform: uppercase; }
.mgn-num { display: block; font-size: 21px; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; margin-top: 3px; line-height: 1.15; }
.mgn-num.unknown { color: var(--muted); font-weight: 600; font-size: 18px; }
.mgn-sub { display: block; font-size: 12px; color: var(--muted); margin-top: 3px; }
.mgn.at-risk { border-color: var(--warn-bd); }
.mgn.at-risk .mgn-cell.hero { background: var(--warn-bg); }
@media (max-width: 760px) { .mgn { grid-template-columns: 1fr 1fr; } }

/* The order's margin, as one line under its total. Cost, margin and percentage
   are a footnote to the total — the same size as it, on the same row — not a
   panel competing with the lines above them. */
.tot-margin { display: flex; align-items: baseline; justify-content: flex-end; flex-wrap: wrap; gap: 6px 20px;
              padding: 0 14px 13px; font-variant-numeric: tabular-nums; }
.tot-part { display: inline-flex; align-items: baseline; gap: 7px; }
/* Sentence case, like every other tile label (P0-1): uppercase is for `thead th`
   and the masthead kicker only. */
.tot-k { font-size: 12.5px; font-weight: 600; color: var(--muted); letter-spacing: 0; text-transform: none; }
.tot-v { font-family: var(--sans); font-size: 15px; font-weight: 700; letter-spacing: -0.01em; }
.tot-note { flex-basis: 100%; text-align: right; font-size: 12px; color: var(--muted); }
.tot-margin.at-risk .tot-v { color: var(--warn); }

/* Review-and-price: the three answers to a swap, as pickable rows. */
.opt { display: grid; grid-template-columns: 18px 1fr auto; gap: 12px; align-items: start; width: 100%;
       border: 1px solid var(--border); border-radius: var(--r); padding: 12px 14px; margin-bottom: 8px;
       cursor: pointer; background: var(--panel); text-align: left; font: inherit; color: inherit; }
.opt:hover { border-color: var(--border-strong); }
.opt.sel { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.opt-title { font-weight: 600; font-size: 14px; }
.opt-desc { color: var(--muted); font-size: 12.5px; margin-top: 2px; }
.opt-num { font-variant-numeric: tabular-nums; font-weight: 700; font-size: 15px; white-space: nowrap; }
.opt-radio { width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid var(--border-strong); margin-top: 3px; }
.opt.sel .opt-radio { border-color: var(--accent); border-width: 5px; }

.wh-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.wh-head h2 { margin: 0; }
.sub { font-size: 12.5px; font-weight: 500; text-transform: none; letter-spacing: 0; margin-top: 3px; }
.kpi-total { font-family: var(--sans); font-size: 26px; font-weight: 800; letter-spacing: -0.025em; margin: 12px 0 16px; }
.kpi-total .muted { font-size: 13.5px; font-weight: 500; letter-spacing: 0; }

.wh-pick { position: relative; }
.wh-pick > summary { list-style: none; cursor: pointer; }
.wh-pick > summary::-webkit-details-marker { display: none; }
.wh-menu { position: absolute; right: 0; top: calc(100% + 6px); z-index: 30; min-width: 264px; background: var(--panel); border: 1px solid var(--border-strong); border-radius: var(--r); box-shadow: var(--shadow-md); padding: 6px; }
.wh-opt { display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: var(--r-sm); font-size: 13.5px; cursor: pointer; }
.wh-opt:hover { background: var(--panel2); }
.wh-opt input { margin: 0; flex: none; }
.wh-menu-foot { display: flex; gap: 6px; padding: 7px 6px 3px; margin-top: 4px; border-top: 1px solid var(--border); }

.wh-bars { display: flex; flex-direction: column; gap: 2px; }
.wh-row { display: grid; grid-template-columns: 150px 1fr 150px; align-items: center; gap: 14px; padding: 7px 8px; border-radius: var(--r-sm); }
.wh-row:hover, .wh-row:focus-visible { background: var(--panel2); outline: none; }
.wh-name { font-size: 14px; min-width: 0; }
.wh-name .sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Thin mark, square at the baseline, 4px rounded at the data end. */
.wh-track { height: 10px; background: var(--panel2); border-radius: 0 5px 5px 0; }
.wh-fill { height: 100%; background: var(--accent-deep); border-radius: 0 4px 4px 0; min-width: 2px; transition: width 0.18s ease; }
.wh-val { text-align: right; font-size: 14.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.wh-val .sub { font-weight: 500; }
/* Stock between two of our own branches. Not an alarm — it is normal and it is
   ours — so it reads as a quiet annotation, never in the destructive red. */
.wh-transit { margin: -2px 0 10px; line-height: 1.45; max-width: 74ch; }
.wh-transit b { color: var(--text); font-variant-numeric: tabular-nums; }
.wh-coming { white-space: nowrap; }
@media (max-width: 720px) { .wh-row { grid-template-columns: 110px 1fr; } .wh-row .wh-val { grid-column: 2; text-align: left; } }

/* ---- The shared filter bar (util.js filterBar/wireFilters) ----
   Every filter on screen with its own label, on one wrapping row, with the
   active ones repeated as removable chips underneath. The chips are not
   decoration: the standing complaint is not being able to see what a list is
   hiding, and a chip both says it and lifts it in one click. */
.fb { padding: 11px 13px; }
.fb-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
.fb-f { display: flex; flex-direction: column; gap: 4px; font-size: 12px; }
.fb-f > span { color: var(--muted); font-weight: 600; letter-spacing: 0.01em; }
.fb-f select, .fb-f input { width: auto; min-width: 130px; max-width: 220px; }
.fb-search { position: relative; flex: 1 1 260px; min-width: 220px; display: flex; align-items: center; }
.fb-search svg { position: absolute; left: 10px; width: 15px; height: 15px; color: var(--muted); pointer-events: none; }
.fb-search input { width: 100%; padding-left: 32px; }
.fb-reset.on { border-color: var(--accent-deep); color: var(--accent-deep); }
.fb-chips { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-top: 9px; }
.fb-chips:empty { display: none; }
/* Compact mode reuses the ERP controls; results alone reload beneath this stable header. */
html:has(.prt) { scrollbar-gutter: stable; }
.fb.fb-compact { padding:0; min-width:0; max-width:100%; }
.fb-compact .fb-list-card > .card-head { padding:12px 16px !important; min-width:0; gap:12px; }
.fb-compact .fb-search-tools { display:flex; align-items:center; gap:10px; flex:1 1 180px; min-width:0; max-width:670px; margin-inline:auto; }
.fb-compact .fb-search { flex:1 1 120px; min-width:0; }
.fb-compact [data-fb-toggle] { flex:none; white-space:nowrap; }
.fb-compact .fb-search input { min-width:0; }
.fb-compact [data-list-actions] { align-items:center; min-width:0; }
.fb-compact .fb-advanced { padding:16px; margin-bottom:12px; }
.fb-compact .fb-advanced .prt-transfer-tools { margin:0 0 0 auto; flex-wrap:nowrap; }
.fb-list-card:has(.prt-list-content > .prt-note:last-child) { padding-bottom:0; }
.fb-compact .fb-advanced[hidden] { display:none; }
.fb-compact .fb-chips:not(:empty) { padding:0 16px 12px; margin:0; }
.fb-compact [data-list-results] { min-width:0; overflow-x:auto; }
.fb-compact .prt-list-content { margin:0; }
.fb-compact .fb-f { min-width:0; max-width:100%; }
.fb-compact .fb-f select, .fb-compact .fb-f input { min-width:0; max-width:100%; }
@media(max-width:720px){
  .fb-compact .fb-search-tools { max-width:none; }
  .fb-compact [data-list-actions] { margin-left:0; }
  .fb-compact .fb-advanced .fb-f { flex:1 1 180px; }
}
.fb-count { margin-right: 2px; }
.fb-foot { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 9px 13px; flex-wrap: wrap; }
.fb-foot b { color: var(--text); font-variant-numeric: tabular-nums; }
@media (max-width: 720px) {
  .fb-f select, .fb-f input { max-width: none; width: 100%; }
  .fb-f { flex: 1 1 45%; }
}

/* ---- choiceDialog: more than two ways out ----
   Stacked full-width buttons, not a row: these are genuinely different
   decisions with different consequences, and a row of three equal buttons
   reads as "pick any". The hint under each says what it will do. */
/* A titled-option list. Not scoped to .modal-choice any more: a FORM field can
   be a choice of three things with a sentence each, and it wants the same
   stack. (Clearance's "why is it going to clearance", 2026-09-16.) */
.choice-list { display: flex; flex-direction: column; gap: 7px; margin: 4px 0 2px; }
.choice { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%; text-align: left;
  padding: 11px 13px; border: 1px solid var(--border-strong); border-radius: var(--r); background: var(--panel);
  color: var(--text); font: inherit; cursor: pointer; }
.choice:hover { background: var(--panel2); }
.choice.on { border-color: var(--accent-deep); background: var(--panel2); box-shadow: inset 2px 0 0 var(--accent-deep); }
.choice-label { font-weight: 650; font-size: 14px; }
.choice-hint { color: var(--muted); font-size: 12.5px; line-height: 1.4; }
.choice.danger .choice-label { color: var(--bad); }

/* ---- Products & stock: filter bar, expandable warehouse split ---- */
.filter-bar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; padding: 11px 13px; }
.filter-bar .search { flex: 1; min-width: 210px; }
.filter-bar select { width: auto; min-width: 120px; max-width: 210px; }

/* ---- The products browser's toolbar ----
   Every filter is on screen — hiding them behind a button meant you couldn't
   see what was narrowing the list without opening a drawer. What made the old
   bar a wall wasn't the number of controls, it was that nine bare dropdowns
   ran together with nothing naming them. So: one even grid, each control under
   its own label, on a tinted ground that separates the filters from the search
   above them. Category contributes one select per level and the row reflows.
   Sort left the bar entirely — it lives on the column headers, where the thing
   being sorted already has a name. */
.browse { padding: 0; overflow: visible; }
.browse-top { display: flex; align-items: center; gap: 9px; padding: 12px 14px; }

.browse-search { position: relative; flex: 1; min-width: 200px; display: flex; align-items: center; }
.browse-search svg { position: absolute; left: 12px; width: 17px; height: 17px; color: var(--faint); pointer-events: none; }
.browse-search input { padding: 10px 12px 10px 37px; background: var(--panel3); border-color: var(--border); font-size: 14.5px; }
.browse-search input:focus { background: var(--panel); border-color: var(--accent); }
.browse-search input::-webkit-search-cancel-button { cursor: pointer; }

/* One even grid: every filter visible, each under its own label. The tint is
   what separates "narrowing the list" from the search box above it. */
.browse-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); gap: 13px 14px; padding: 14px; border-top: 1px solid var(--border); background: var(--panel3); }
/* The category levels are children of #pf-cats but must lay out as members of
   the grid itself, so the row stays even as levels appear and disappear. */
#pf-cats { display: contents; }
.pf-cat-level { display: contents; }

/* A filter you can type at. It has to READ as the select beside it — same
   height, same box, same label — or the row stops looking like one control
   strip. The hit list floats, so opening it never shoves the table down. */
.combo { position: relative; }
.combo-in { width: 100%; min-width: 0; padding: 8px 11px; font-size: 14px; }
.combo-list { position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + 4px);
  min-width: 232px; max-height: 340px; overflow: auto; background: var(--panel);
  border: 1px solid var(--border-strong); border-radius: var(--r); box-shadow: var(--shadow-md);
  padding-bottom: 4px; }
.combo-list .pick-label { font-size: 13.5px; }
.combo-list .pick-desc { font-size: 12px; }
.browse-f { display: flex; flex-direction: column; gap: 5px; min-width: 0; font-size: 13px; }
/* Was 11.5px/700 UPPERCASE with 0.04em tracking — ten small-caps labels in a
   row, the third place in the app wearing a convention that is meant to live
   in `thead th` and the masthead kicker only. These are the same 12px/600
   sentence-case labels the shared `.fb-f` bar uses on every other list. */
.browse-f > span { font-size: 12px; font-weight: 600; color: var(--muted); letter-spacing: 0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.browse-f select { width: 100%; min-width: 0; padding: 8px 11px; font-size: 14px; }
/* Category is the axis the catalogue is navigated by — its label carries the
   accent so the cascade reads as one control, not three loose dropdowns. */

/* Always present and always clickable — a greyed-out Reset reads as broken.
   It goes accent while anything is narrowing the list, which is also how the
   toolbar says "you are not looking at everything". */
.browse-reset { padding: 9px 14px; color: var(--muted); white-space: nowrap; }
.browse-reset:hover { color: var(--text); }
.browse-reset.on { color: var(--accent); border-color: var(--accent-wash-bd); background: var(--accent-wash); }
.browse-reset.on:hover { border-color: var(--accent); }

@media (max-width: 720px) {
  .browse-top { flex-wrap: wrap; }
  .browse-search { flex-basis: 100%; }
}

/* ---- Sorting lives on the header it applies to ---- */
th.sortable { cursor: pointer; user-select: none; }
th.sortable > span { border-bottom: 1px dotted transparent; }
th.sortable:hover { color: var(--text); }
th.sortable:hover > span { border-bottom-color: var(--border-strong); }
th.sortable.on { color: var(--accent); }
.sort-ar { font-style: normal; display: inline-block; width: 10px; margin-left: 3px; font-size: 11px; }

/* ---- A row's category, shown as the whole shelf rather than its last word ---- */
.crumb { display: inline-flex; align-items: baseline; flex-wrap: wrap; gap: 1px; font-size: 13px; }
.crumb-b { font: inherit; background: none; border: none; padding: 0; cursor: pointer; color: var(--muted); text-align: left; border-radius: 2px; }
/* A button is shrink-to-fit, which means it sizes to its text on ONE line and
   then spills out of the column — "Panel Prints — Front" hung 55px into the
   next cell. Capped to the cell, the words wrap inside it like text should. */
.crumb-b { max-width: 100%; }
.crumb-b.leaf { color: var(--text); font-weight: 600; }
.crumb-b:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
/* The name and its state marks sit on one line; the brand falls beneath. */
.item-id { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* A rule where the table changes subject: what it IS · how many · what it cost.
   Quieter than a full border — it groups without drawing a second table. */
th.bk, td.bk { border-left: 1px solid var(--border); }
thead th.bk { border-left-color: var(--border-strong); }

/* ---- The products table earns a tighter density than the rest of the app ----
   Fourteen columns in Advanced is the widest thing here, and the app-wide
   13/16px cell padding alone spent ~450px of it on whitespace. Tightened only
   for this table, so nothing else in the app changes weight. */
#pf-results th, #pf-results td { padding: 9px 10px; }
#pf-results thead th { padding: 8px 10px; }
#pf-results td:first-child, #pf-results th:first-child { padding-left: 12px; }
/* Actions only appear on hover, so the column should claim the room they need
   and not a pixel more. */
#pf-results .row-acts { padding-left: 4px; padding-right: 12px; }
#pf-results .row-acts .btn { padding: 3px 8px; }
/* Money and quantities are short and read better without a wide gutter. */
#pf-results td.num, #pf-results th.r { padding-left: 8px; }

/* The note says what the numbers below MEAN, so it needs to read as a caption
   with room around it — not as a paragraph wedged against the header row.
   Three separate statements were run together on one line; the scope is the
   sentence you read, the basis is the footnote under it. */
.table-note { padding: 13px 16px; font-size: 13.5px; line-height: 1.5; color: var(--text); border-bottom: 1px solid var(--border); }
/* Two hundred and ten words of glossary printed above the table EVERY time it
   renders, which is a wall a daily user reads once and scrolls past forever
   after. Folded: what is on the screen unprompted is the one sentence that
   changes with the filter — which branch these figures cover. The definitions
   are one click away and, unlike the `title=` on each column header, they are
   reachable on a tablet. Capped at 92ch: a definition read across 1,120px
   loses its place between one line and the next. */
.table-note .basis { display: block; margin-top: 4px; font-size: 12.5px; line-height: 1.5; color: var(--muted); max-width: 92ch; }
.table-note .basis > summary { cursor: pointer; color: var(--muted); width: fit-content; font-weight: 600; }
.table-note .basis > summary:hover { color: var(--text); }
.table-note .basis[open] > summary { margin-bottom: 4px; }
.table-foot { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-top: 1px solid var(--border); font-size: 13.5px; }
#pf-results { transition: opacity 0.12s; }
/* Fixed layout: the widths in the colgroup pages.js emits are the whole truth,
   so nothing is re-solved from content when a column appears. A header label
   wraps rather than being cut; a cell that outgrows its column ends in an
   ellipsis rather than climbing over its neighbour (the full value is in the
   row you open, and in the title on the money cells). */
#pf-results table { table-layout: fixed; }
#pf-results thead th { white-space: normal; }
#pf-results td { overflow: hidden; text-overflow: ellipsis; }
#pf-results .split-cell { overflow: visible; }

.expand-cell { width: 26px; padding-right: 0 !important; }
/* Drag a document's line, or a build's part, by its handle (Q4, 2026-09-25). */
.drag-h { cursor: grab; color: var(--muted); padding-right: 6px; user-select: none; font-size: 13px; }
.drag-h:active { cursor: grabbing; }
tr.dragging { opacity: .45; }
tr.drop-before > td { box-shadow: inset 0 2px 0 var(--accent); }
tr.drop-after > td { box-shadow: inset 0 -2px 0 var(--accent); }
.row-x { background: none; border: none; color: var(--faint); cursor: pointer; font-size: 13px; line-height: 1; padding: 3px 2px; border-radius: var(--r-sm); }
.row-x:hover { color: var(--accent); background: var(--accent-wash); }
tr.prow { cursor: pointer; }
.row-acts { white-space: nowrap; text-align: right; }
/* ROW ACTIONS ARE VISIBLE. The fade-in belongs to the PRODUCTS BROWSER alone —
   hundreds of rows, where a button on every one is noise — so it is opted into
   with `.prow`, never inherited.
   It used to be the other way round: `.row-acts .btn { opacity: 0 }` was the
   default and only `tr.prow:hover` turned it back on. Any other table that
   reused the class got controls that were laid out, hit-testable, and
   completely transparent — invisible even on hover, because the one selector
   that could reveal them required a class those rows never had. The Kinds tab
   and the branches list both shipped that way. It cost the owner six rounds of
   "I can't edit a kind" on 2026-08-27 against a screen whose buttons were
   always in the DOM, which is why every check I ran said they were fine.
   A control that exists but cannot be seen is worse than one that is missing. */
tr.prow .row-acts .btn { opacity: 0; transition: opacity 0.1s; }
tr.prow:hover .row-acts .btn, tr.prow:focus-within .row-acts .btn { opacity: 1; }
@media (hover: none) { tr.prow .row-acts .btn { opacity: 1; } }
/* An available qty at or under its reorder point — the number itself carries it. */
.warn-t { color: var(--warn); font-weight: 700; }

.split-row > .split-cell { background: var(--panel3); padding: 10px 14px 12px 40px !important; }
.split-table { margin: 0; width: auto; min-width: 460px; }
.split-table th { font-size: 11.5px; padding: 2px 12px 4px 0; }
.split-table td { padding: 4px 12px 4px 0; border: none; font-size: 13.5px; }

/* ---- The open build on a sales order / quote ----------------------------
   The parts of one machine, edited in place. It was a card inside a table cell
   inside a card, with a second bordered search box parked under it — three
   frames to say one thing (owner, 2026-09-14: "that table is gross ... just
   make that search box look like a line item again"). It is the same nested
   table a kit split uses now, and the search is the last ROW of it. */
.bp-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 14px; flex-wrap: wrap; margin-bottom: 8px; }
.bp-table tfoot td { padding-top: 7px; border-top: 1px solid var(--border); font-size: 12px; }
.bp-table tbody tr:hover > td { background: var(--panel2); }
.bp-table .expand-cell { width: 26px; }
/* The document table lets SKU and Name wrap, because those columns carry
   70-character product names and it would rather grow tall than scroll. Inside
   a build the list is already indented and only ever a dozen rows — wrapping
   there makes eight parts read like sixteen. Named rather than `.split-table`
   so it outweighs `.card.np td:nth-child(n)`, which is what was wrapping it. */
.card.np table.bp-table th, .card.np table.bp-table td { white-space: nowrap; }
.card.np table.bp-table td:nth-child(3) { white-space: normal; min-width: 18ch; }

/* The add line. A dashed frame says "this row is not a part yet" without
   putting a second box on the screen; focus makes it solid, like any field. */
.bp-addbox { display: flex; flex-direction: column; min-width: 340px; }
.bp-addline {
  display: flex; align-items: center; gap: 8px; padding: 1px 9px; background: var(--panel);
  border: 1px dashed var(--border-strong); border-radius: var(--r-sm);
}
.bp-addline:focus-within { border-style: solid; border-color: var(--accent); box-shadow: var(--ring); }
.bp-plus { flex: none; color: var(--faint); font-weight: 700; font-size: 15px; line-height: 1; }
.bp-addbox input.bp-add {
  flex: 1 1 auto; min-width: 0; border: none; background: none; border-radius: 0;
  padding: 6px 0; font-size: 13.5px;
}
.bp-addbox input.bp-add:focus { box-shadow: none; }
.bp-addbox .pick-cat { border: none; border-left: 1px solid var(--border); background: none; max-width: 150px; height: 26px; }
/* In flow, not floating: `.card.np` scrolls sideways, which makes it a clipping
   box — an absolutely-placed dropdown inside one loses its bottom half. */
.bp-results {
  margin-top: 5px; background: var(--panel);
  border: 1px solid var(--border-strong); border-radius: var(--r); box-shadow: var(--shadow-sm);
}

/* ---- Category manager: tree on the left, one category's detail on the right ---- */
.cat-layout { display: grid; grid-template-columns: 340px 1fr; gap: 14px; align-items: start; }
@media (max-width: 900px) { .cat-layout { grid-template-columns: 1fr; } }
.cat-tree-card { padding: 0; position: sticky; top: 14px; }
.cat-search { padding: 11px 12px; border-bottom: 1px solid var(--border); }
.cat-search .search { width: 100%; }
.cat-tree { max-height: 68vh; overflow-y: auto; padding: 6px; }
.cat-node { display: flex; align-items: center; gap: 7px; padding: 6px 9px; border-radius: var(--r-sm); cursor: pointer; font-size: 14px; }
.cat-node:hover { background: var(--panel2); }
.cat-node:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.cat-node.on { background: var(--accent-wash); }
.cat-node.on .cat-name { color: var(--accent); font-weight: 700; }
/* Indent comes from the row's own depth (inline), not from a per-level class. */
.cat-node.sub { font-size: 13.5px; }
.cat-node.sub .cat-name { color: var(--muted); }
.cat-node.sub.on .cat-name { color: var(--accent); }
.cat-tw { width: 16px; flex: none; background: none; border: none; color: var(--faint); font-size: 11px; cursor: pointer; padding: 0; line-height: 1; }
.cat-tw.leaf { cursor: default; }
.cat-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.cat-count { flex: none; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; background: var(--chip); border-radius: var(--r-pill); padding: 1px 8px; }
/* Adding a sub-category is a per-row action, not something buried in a form. */
.cat-add { flex: none; width: 21px; height: 21px; border-radius: var(--r-sm); border: 1px solid var(--border-strong); background: var(--panel); color: var(--muted); font-size: 14px; line-height: 1; cursor: pointer; opacity: 0; transition: opacity .12s, color .12s, border-color .12s; }
.cat-node:hover .cat-add, .cat-node:focus-within .cat-add, .cat-node.on .cat-add { opacity: 1; }
.cat-add:hover { color: var(--accent); border-color: var(--accent); }
@media (hover: none) { .cat-add { opacity: 1; } }
.sub-list { display: flex; flex-wrap: wrap; gap: 7px; }
.sub-pill { display: inline-flex; align-items: center; gap: 8px; font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-pill); padding: 5px 6px 5px 13px; color: var(--text); }
.sub-pill:hover { border-color: var(--accent); color: var(--accent); }
.sub-n { background: var(--panel); border-radius: var(--r-pill); padding: 1px 9px; font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

.cat-detail { min-height: 320px; }
.cat-detail-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 16px; padding-bottom: 14px; border-bottom: 1px solid var(--border); }
.cat-title { font-size: 21px; font-weight: 800; letter-spacing: -0.02em; margin: 2px 0 4px; }
.cat-empty { padding: 40px 12px; }
.cat-sec { margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--border); }
.cat-sec h3 { font-size: 14px; font-weight: 800; margin: 0 0 3px; }
.cat-sec .sub { margin-bottom: 10px; }
.danger-sec { border-top-color: var(--bad-bd); }
/* The live duplicate verdict — the whole point of the page, so it gets colour. */
.cat-check { font-size: 13px; margin-top: 5px; min-height: 16px; color: var(--muted); }
.cat-check.ok { color: var(--ok); }
.cat-check.warn { color: var(--warn); }
.cat-check.bad { color: var(--bad); font-weight: 600; }
.tidy-card { border-color: var(--warn-bd); background: var(--warn-bg); color: var(--text); margin-bottom: 14px; }
.tidy-list { list-style: none; margin: 9px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.tidy-list li { display: flex; align-items: center; gap: 8px; font-size: 13.5px; }

/* ---- Transfer orders: a document, laid out like a purchase order ---- */
.tr-route { display: grid; grid-template-columns: 1fr auto 1fr 1.3fr 190px; gap: 18px; align-items: end; }
@media (max-width: 1100px) { .tr-route { grid-template-columns: 1fr 1fr; } .tr-route .tr-arrow { display: none; } }
@media (max-width: 620px) { .tr-route { grid-template-columns: 1fr; } }
.tr-leg label { display: block; font-size: 12px; font-weight: 700; color: var(--muted); letter-spacing: 0.02em; margin-bottom: 6px; }
.tr-leg .sub { margin-top: 5px; }
.tr-arrow { font-size: 21px; color: var(--faint); padding-bottom: 9px; }
.tr-lines { margin-top: 4px; }

.tr-steps { display: flex; flex-wrap: wrap; gap: 7px; margin: -4px 0 16px; }
.tr-step { font-size: 12px; font-weight: 700; padding: 5px 13px; border-radius: var(--r-pill); border: 1px solid var(--border); background: var(--panel); color: var(--faint); }
.tr-step.done { color: var(--ok); border-color: var(--ok-bd); background: var(--ok-bg); }
.tr-step.on { color: var(--accent-ink); background: var(--accent-deep); border-color: var(--accent-deep); }
.tr-step.bad { color: var(--bad); border-color: var(--bad-bd); background: var(--bad-bg); }

/* P0-3: the grid now comes from `.stats` above — this is the third facts strip
   folded into the two the app is allowed to have. Only the bits that are about
   ITS content stay here. */
.tr-summary { align-items: stretch; }
@media (max-width: 900px) { .tr-summary { grid-template-columns: 1fr; } }
.tr-summary .card { margin-bottom: 0; }
.tr-summary .kpi-total { margin: 6px 0 4px; font-size: 25px; }
.tr-route-card { display: flex; align-items: center; gap: 20px; }
.tr-route-card .tr-leg b { font-size: 20px; font-weight: 800; letter-spacing: -0.02em; display: block; margin-top: 3px; }
.banner.warn { background: var(--warn-bg); border: 1px solid var(--warn-bd); color: var(--text); border-radius: var(--r); padding: 11px 13px; margin-bottom: 14px; }

/* ---- Goods receipt: the warehouse bench, not the purchasing desk ----
   Bigger hit targets than the rest of the app on purpose — this screen gets
   used standing up, one-handed, sometimes wearing gloves, on a tablet. */
.sec-head { font-size: 13px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin: 22px 0 10px; }

.rcv-head { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 16px; align-items: start; }
@media (max-width: 860px) { .rcv-head { grid-template-columns: 1fr; } }
.rcv-head .field { margin-bottom: 0; }

.rcv-table td { padding-top: 9px; padding-bottom: 9px; }
/* The counted box is the one thing on this page that matters. Make it look
   like it: empty by default, big enough to hit, and loud when it's over. */
.rcv-qty { width: 96px; text-align: right; font-size: 16px; font-weight: 700; padding: 9px 10px; font-variant-numeric: tabular-nums; }
.rcv-qty:placeholder-shown { font-weight: 400; }
.rcv-qty.over { border-color: var(--bad-bd); background: var(--bad-bg); color: var(--bad); }
.rcv-table .rcv-lot, .rcv-table .rcv-note { width: 130px; }

/* A scan landed on this row — a half-second pulse so the receiver can keep
   their eyes on the box, not the screen. */
@keyframes rcv-flash { from { background: var(--accent-wash); } to { background: transparent; } }
tr.flash > td { animation: rcv-flash 0.6s ease-out; }

.rcv-ex { display: grid; grid-template-columns: 1.3fr 1.3fr 90px 2fr auto; gap: 12px; align-items: end; padding: 14px 16px; border-bottom: 1px solid var(--border); }
@media (max-width: 1000px) { .rcv-ex { grid-template-columns: 1fr 1fr; } .rcv-ex .span-2 { grid-column: 1 / -1; } }
.rcv-ex .field { margin-bottom: 0; }
.rcv-ex .ex-del { align-self: end; margin-bottom: 1px; }
/* ---- SERIALS AT GOODS-IN (Ch 22 §0.2, 0295) ----
   A serial-tracked line carries its serial list on a row of its own directly
   under it (R-UI-2). Count first and the list draws that many empty boxes; leave
   the count blank and the serials are the count. */
.rcv-head.rs-scanning { border-color: var(--accent); box-shadow: var(--ring); }
.rcv-qty.derived { color: var(--ok); border-style: dashed; }
.rs-from { display: block; font-size: 11px; font-weight: 600; color: var(--ok); margin-top: 2px; white-space: nowrap; }
.rs-tag { display: inline-flex; font-size: 12px; font-weight: 700; border-radius: 999px; padding: 2px 9px; white-space: nowrap;
  background: var(--accent-wash); border: 1px solid var(--accent-wash-bd); color: var(--accent-hover); }
tr.rcv-tracked > td { border-bottom: 0; }
tr.rcv-tracked.rs-on > td:first-child { box-shadow: inset 4px 0 0 var(--accent); }
tr.rs-row > td { padding: 0 12px 14px; }
.rs { border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--panel3); padding: 11px 13px; }
.rs.on { border-color: var(--accent); background: var(--panel); box-shadow: var(--ring); }
.rs-head, .rs-idle { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.rs-idle { font-size: 13px; }
.rs-acts { margin-left: auto; display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.rs-live { font-size: 12px; font-weight: 700; color: var(--accent-hover); white-space: nowrap; }
.rs-pill { display: inline-flex; font-size: 12px; font-weight: 700; border-radius: 999px; padding: 2px 9px; border: 1px solid; white-space: nowrap; }
.rs-pill.ok { background: var(--ok-bg); border-color: var(--ok-bd); color: var(--ok); }
.rs-pill.warn { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--warn); }
.rs-pill.bad { background: var(--bad-bg); border-color: var(--bad-bd); color: var(--bad); }
.rs-pill.info { background: var(--info-bg); border-color: var(--info-bd); color: var(--info); }
.rs-chips { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 6px; margin-top: 10px; }
.rs-chip { display: flex; align-items: center; gap: 7px; min-width: 0; border: 1px solid var(--ok-bd); background: var(--ok-bg);
  border-radius: var(--r); padding: 4px 6px 4px 8px; }
.rs-chip .n { font-size: 11px; font-weight: 800; color: var(--ok); min-width: 16px; font-variant-numeric: tabular-nums; }
.rs-chip .b { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
.rs-chip .s { font-family: var(--mono); font-size: 12.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rs-chip .t { font-size: 10px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; }
.rs-chip .x { border: 0; background: none; color: var(--faint); cursor: pointer; font: inherit; font-size: 15px; line-height: 1; padding: 0 2px; }
.rs-chip .x:hover { color: var(--bad); }
.rs-chip.list { border-style: dashed; border-color: var(--info-bd); background: var(--panel); }
.rs-chip.list .n, .rs-chip.list .t { color: var(--info); }
.rs-chip.list .s { color: var(--muted); }
.rs-chip.ticked .t { color: var(--ok); }
.rs-chip.offlist { border-color: var(--warn-bd); background: var(--warn-bg); }
.rs-chip.offlist .n, .rs-chip.offlist .t { color: var(--warn); }
.rs-chip.fresh { animation: rs-in .6s ease-out; }
@keyframes rs-in { from { box-shadow: 0 0 0 4px var(--ok-bd); } to { box-shadow: none; } }
.rs-chip.slot { border: 1px dashed var(--border-strong); background: transparent; }
.rs-chip.slot .n, .rs-chip.slot .s { color: var(--faint); font-weight: 500; }
.rs-chip.slot.next { border: 2px solid var(--accent); background: var(--panel); }
.rs-chip.slot.next .n, .rs-chip.slot.next .s { color: var(--accent); font-weight: 700; }
.rs-msg { margin-top: 9px; border-radius: var(--r); padding: 7px 11px; font-size: 13px; font-weight: 600; border: 1px solid;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.rs-msg .btn { margin-left: auto; }
.rs-msg.ok { background: var(--ok-bg); border-color: var(--ok-bd); color: var(--ok); }
.rs-msg.bad { background: var(--bad-bg); border-color: var(--bad-bd); color: var(--bad); }
.rs-msg.info { background: var(--info-bg); border-color: var(--info-bd); color: var(--info); }
.rs-msg.warn { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--warn); }
#rcv-scan-msg .rs-msg { margin-top: 8px; }
.rs-list { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border-strong); max-width: 640px; }
.rs-list textarea { width: 100%; font-family: var(--mono); font-size: 13px; }
/* The goods receipt on the order: the serials a line brought in. */
.po-drop .rs-open { margin-left: 4px; }
.rs-unit-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 6px; }
.rs-unit-list a { font-family: var(--mono); font-size: 12.5px; font-weight: 600; }

/* One delivery, one card. Stacked, newest first. */
.grn-ex > td { background: var(--warn-bg); }
/* A fully un-booked delivery line: still on the record, plainly no longer counted. */
.grn-card .card-head .btn.sm { margin-left: 8px; }

/* THE LINE IS THE DOCUMENT (2026-08-19). A purchase order's deliveries used to be
   a second stack of cards repeating the same products; they are footnotes on the
   line now, and the arrived COUNT is the control that changes them. */
/* When each stack of a rolling line lands (0139) — a footnote on the line,
   the same weight as the deliveries that have already happened below it. */

.po-drops { margin-top: 4px; }
.po-drop { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.po-drop.gone { opacity: 0.6; }
.po-drop s { opacity: 0.7; }
/* A number you can click. Reads as data until you go near it, because it IS the
   data — dressing it as a button would hide the figure behind a verb. */
.btn-num {
  background: none;
  border: 1px solid transparent;
  border-radius: 4px;
  padding: 1px 5px;
  margin: -1px -5px;
  color: inherit;
  font: inherit;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.btn-num:hover { border-color: var(--border-strong); background: var(--panel2); }
.btn-num:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---- Branch closure: consequences stated before the buttons ---- */
/* Category and Sub-category are two columns, because a shelf and a sub-shelf are
   things you scan and sort on separately. A third tier stacks UNDER its sub:
   it qualifies that sub rather than standing beside it, and stacking keeps the
   column narrow instead of letting "DDR4 8GB › Corsair" set the table's width. */
/* `.card.np td` is two classes and an element, so a bare `.cat-cell` lost to it
   and the Sub-category column stayed `nowrap` — which is exactly how "Panel
   Prints — Front" came to hang out of its column. Named at the same weight. */
.card.np td.cat-cell, .cat-cell { white-space: normal; min-width: 9ch; max-width: 22ch; }
.cat-cell .sub-sub { margin-top: 2px; padding-left: 9px; position: relative; }
.cat-cell .sub-sub::before {
  content: "";
  position: absolute; left: 1px; top: 0.15em; bottom: 0.55em;
  border-left: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  width: 5px;
}
.cat-cell .sub-sub .crumb-b { font-size: 11.5px; color: var(--muted); }

.danger-zone { border-color: var(--bad-bd); }
.danger-zone h3 { font-size: 14px; font-weight: 800; margin: 0 0 4px; color: var(--bad); }
.wh-blockers { margin: 6px 0 0; padding-left: 20px; color: var(--muted); font-size: 12.5px; }
.wh-blockers li { margin-bottom: 3px; }

/* ---- Kit editor: the chosen kit item reads as a decision, not a text field ---- */
.kit-chosen { display: flex; align-items: center; gap: 10px; font-size: 17px; margin: 4px 0 6px; }
.kit-chosen b { font-weight: 800; letter-spacing: 0.01em; }

/* ---- Cards: a boundary only where grouping needs one ---- */
.cols { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-start; }
.cols > .card { flex: 1; min-width: 260px; }
.card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); padding: 19px 21px; margin-bottom: 14px; box-shadow: var(--shadow-sm); }
/* ---- One table system, so no page has to invent its own ----
   A table may be WIDER than its card; it may never be NARROWER than it can be
   read. Three rules hold that line, and they are why adding or removing columns
   (Advanced mode, a permission-gated action column) no longer wrecks a page:
     1. Nothing breaks mid-word — see the `th, td` rule below the tables heading.
     2. Every text column carries a floor in `ch`. When the floors stop fitting,
        the card scrolls sideways instead of crushing all columns at once.
     3. The scrollbar is visible, because a table that scrolls has to LOOK like
        one — a clipped right edge with no affordance is what reads as broken. */
.card.np { padding: 0; overflow-x: auto; scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
.card.np::-webkit-scrollbar { height: 10px; }
.card.np::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 10px; border: 3px solid var(--panel); }
.card.np::-webkit-scrollbar-track { background: transparent; }
.card.np table { min-width: 100%; }
.card.np th, .card.np td { white-space: nowrap; }
.card.np td:nth-child(2), .card.np td:nth-child(3) { white-space: normal; min-width: 11ch; }
/* A SKU is one token, so it is not broken while it fits — and almost all of them
   fit, which keeps the column scannable. But "there is room on the page" stopped
   being true: 213 active SKUs are really product names, one of them 70
   characters, and `nowrap` let a single row demand ~500px and crush Name into a
   one-word-per-line ribbon. Past the cap it wraps instead of bulldozing the
   table — worst visible when sorting by value, because the dearest parts
   (servers, GPU nodes) are exactly the ones with quote references for SKUs. */
.card.np td.sku-cell {
  white-space: normal;
  min-width: 11ch;
  max-width: 28ch;
}
.card.np td:nth-child(4) { white-space: normal; min-width: 9ch; }

/* ---- Named beats counted ----
   The three rules above pick a column's behaviour by POSITION, which is a guess
   that is right most of the time and wrong the rest. A cell that says what it is
   overrides the guess — but only if it is written at the SAME weight, because
   `.card.np td` is two classes and an element and a bare `.payload` is not. That
   ordering trap is what left the ledger's JSON on `nowrap`, 599px of it hanging
   across the hash column. Any page can now say `wrap` or `nowrap` on a cell and
   be obeyed. */
.card.np td.wrap { white-space: normal; }
.card.np td.nowrap { white-space: nowrap; }
/* JSON and hashes have no words, so this is the one place a mid-token break is
   the RIGHT answer — with a floor, so the column cannot be crushed to a thread. */
.card.np td.payload { white-space: pre-wrap; overflow-wrap: anywhere; min-width: 26ch; }

/* Build grid (Build-a-PC, models): QUAKE "SKUs" run to sentence length, so
   the part gets ONE wide flexible column that wraps — it never dictates the
   table, and the slot search inside it always has room. Numeric columns hold
   steady on the right. 880px is the floor at which all six columns still read;
   it clears a 1280px laptop (sidebar + page + card padding) with no inner
   scrollbar — the host form has to be a .with-parts card, not the 560px one. */
.card.np.bg-table table { table-layout: fixed; min-width: 880px; }
.card.np.bg-table th:nth-child(2) { width: 150px; }
.card.np.bg-table th:nth-child(3) { width: 100px; }
.card.np.bg-table th:nth-child(4) { width: 120px; }
.card.np.bg-table th:nth-child(5) { width: 90px; }
.card.np.bg-table th:nth-child(6) { width: 46px; }
.card.np.bg-table td:nth-child(1), .card.np.bg-table td:nth-child(2) { white-space: normal; }
.bg-table .bg-slot-search { width: 100%; max-width: 420px; }
.card.np table { margin: 0; }
/* A CARD TITLE IS A TITLE. This one base rule was putting a muted uppercase
   eyebrow on 61+ cards — the single biggest generated-looking thing in the
   app, because a screen of identical small-caps labels has no hierarchy in it
   at all. The small-caps convention survives in exactly two places now:
   `thead th` and the masthead `.kicker`. */
.card h2 { font-size: 15px; font-weight: 700; color: var(--text); margin: 0 0 10px; letter-spacing: -0.005em; text-transform: none; }
/* `gap` was missing, and it showed the moment a card head grew a caption beside
   its title: "ASSEMBLYnot on the floor". `space-between` puts no space between
   ADJACENT items, only around the free space. */
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; padding: 14px 16px 0; }
.card-head h2, .card-head h3 { margin: 0; }
/* The same row for a card that already carries its own padding. `.card-head`
   brings 14/16 of its own, which is right above a `.card.np` wrapping a
   table edge-to-edge and wrong inside an ordinary `.card` — the heading ends up
   indented past the body underneath it. `.pad` is a no-op class, so
   `.card.pad` is just `.card` and gets this one. R-UI-2: the button that acts
   on a card lives in that card's head, so every padded card needs a head it can
   sit in. */
.pad-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.pad-head h2, .pad-head h3 { margin: 0; }

/* ---- Tables: dense, hairline rows, actions surface on hover ---- */
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 13px 16px; border-bottom: 1px solid var(--border); vertical-align: top; }
/* No cell anywhere in the app breaks a word mid-letter. `overflow-wrap: anywhere`
   was the whole bug: it tells the browser a column's MINIMUM width is one
   character, so instead of overflowing (and scrolling) the browser cheerfully
   crushes the column into a vertical ribbon of letters and squeezes every other
   column with it. `break-word` breaks only a token that could never fit on its
   own line, and it does not lower the column's minimum. */
th, td { overflow-wrap: break-word; word-break: normal; hyphens: none; }
thead th { color: var(--muted); font-weight: 700; font-size: 11.5px; letter-spacing: 0.05em; text-transform: uppercase; background: var(--panel3); border-bottom: 1px solid var(--border); padding: 10px 14px; }
tbody tr:last-child td { border-bottom: none; }
tbody tr.click, tbody tr.rowlink { cursor: pointer; }
tbody tr:hover { background: var(--panel2); }
td.r, th.r { text-align: right; font-variant-numeric: tabular-nums; }
.row-actions { visibility: hidden; }
tr:hover .row-actions { visibility: visible; }
/* A bench tablet has no hover, so a hidden row action is simply gone. */
@media (pointer: coarse) { .row-actions { visibility: visible; } }
.muted { color: var(--muted); }
/* ---- The three utilities that retire inline type and rhythm ----
   1,249 inline `style=` attributes were mostly `font-size:12.5px` and
   `margin-top:12px` typed out by hand, which is how the app ended up with
   12.6px and 10.8px in it. After these, an inline `style` may carry a COMPUTED
   value (`width:${w}px`, `--pct:${p}%`) and nothing else. */
.small { font-size: 13px; }
.tiny { font-size: 12px; color: var(--muted); }
.stack > * + * { margin-top: 12px; }
.neg { color: var(--bad); }
.hash { color: var(--accent); font-family: var(--mono); }
.payload { font-family: var(--mono); font-size: 12px; color: var(--muted); white-space: pre-wrap; word-break: break-word; max-width: 300px; }
.total { text-align: right; font-family: var(--sans); font-size: 17px; font-weight: 700; padding: 12px 14px; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.empty { color: var(--muted); padding: 26px; text-align: center; }
.loading { color: var(--muted); padding: 44px; text-align: center; }
.card.err { border-color: var(--bad); color: var(--bad); }

/* ---- Chips & badges: soft pills, quiet, state only ---- */
.chip { display: inline-block; background: var(--chip); border: 1px solid var(--border); border-radius: var(--r-pill); padding: 2px 9px; font-size: 12px; font-weight: 500; color: var(--muted); }
a.chip { text-decoration: none; }
a.chip:hover { color: var(--text); border-color: var(--border-strong); }
.badge { display: inline-flex; align-items: center; border-radius: var(--r-pill); padding: 2px 10px; font-size: 12px; font-weight: 600; border: 1px solid; letter-spacing: .01em; }
.badge.g { background: var(--chip); border-color: var(--border); color: var(--muted); }
.badge.b { background: var(--info-bg); border-color: var(--info-bd); color: var(--info); }
.badge.y { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--warn); }
.badge.ok { background: var(--ok-bg); border-color: var(--ok-bd); color: var(--ok); }
.badge.r { background: var(--bad-bg); border-color: var(--bad-bd); color: var(--bad); }

/* ---- Buttons: signal red for the main act, soft & rounded otherwise ---- */
.btn { font: inherit; font-size: 14px; font-weight: 600; cursor: pointer; border-radius: var(--r); border: 1px solid var(--border-strong); background: var(--panel); color: var(--text); padding: 9px 17px; text-decoration: none; display: inline-flex; align-items: center; gap: 6px; box-shadow: var(--shadow-sm); transition: border-color 0.12s, background 0.12s, box-shadow 0.12s, filter 0.12s, transform 0.06s; }
.btn:hover { border-color: var(--border-strong); background: var(--panel2); }
.btn:active { transform: translateY(1px); }
.btn.primary { background: var(--accent-deep); border-color: var(--accent-deep); color: var(--accent-ink); box-shadow: 0 1px 2px rgba(179,20,28,0.35), 0 3px 10px rgba(179,20,28,0.20); }
.btn.primary:hover { filter: brightness(1.09); }
.btn.danger { color: var(--bad); border-color: var(--bad-bd); background: var(--panel); }
.btn.danger:hover { border-color: var(--bad); background: var(--bad-bg); }
.btn.sm { padding: 4px 11px; font-size: 13px; }
/* Used in the category tree and the embellishment record before it existed as
   a rule, so eleven buttons meant to be tiny rendered full size. Defined once
   rather than deleted: a reorder arrow inside a table row genuinely is smaller
   than a row action. */
.btn.xs { padding: 2px 8px; font-size: 12px; font-weight: 600; border-radius: var(--r-sm); }
.btn:focus-visible { outline: none; box-shadow: var(--ring); }
a { color: var(--accent); }

/* ---- A menu hung off a button (the ⋯ overflow) ----
   Same machinery as .wh-pick, generic: records that earn a place on the page
   but not a card's worth of it. A tick marks the ones currently shown. */
.pop-pick { position: relative; }
.pop-pick > summary { list-style: none; cursor: pointer; }
.pop-pick > summary::-webkit-details-marker { display: none; }
.btn.icon { padding: 9px 12px; font-size: 15px; line-height: 1; }
.pop-menu { position: absolute; right: 0; top: calc(100% + 6px); z-index: 30; min-width: 244px; background: var(--panel); border: 1px solid var(--border-strong); border-radius: var(--r); box-shadow: var(--shadow-md); padding: 6px; }
.pop-title { font-size: 11.5px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); padding: 5px 8px 6px; }
.pop-item { display: flex; align-items: baseline; gap: 9px; width: 100%; padding: 7px 8px; border: 0; background: none; border-radius: var(--r-sm); font: inherit; font-size: 13.5px; color: var(--text); text-align: left; cursor: pointer; }
.pop-item:hover { background: var(--panel2); }
.pop-item:focus-visible { outline: none; box-shadow: var(--ring); }
.pop-item .tick { flex: none; width: 11px; font-size: 11px; color: var(--accent-deep); }
.pop-item .sub { margin-top: 2px; }
a.pop-item { text-decoration: none; }
.pop-item.danger { color: var(--bad); }
.pc-rec[hidden] { display: none; }

/* ---- The pick desk: ONE screen, six sections ----
   The strip used to be pills (.pf-views) that were really links to three
   different pages. It is .tabs now — the control this app already uses for
   "sections of one page" (P0-4) — because that is what they became.
   The tabs are still <a href> so a section can be linked to and shared; the
   desk cancels the navigation and swaps the body. */
.tabs a.tab { text-decoration: none; }
/* A tab the current search finds nothing on stays readable and stays clickable:
   greying it out IS the answer to "is it over there?", so it has to be legible,
   not hidden. */
.pick-tabs .tab.nil { opacity: 0.5; }
.pick-search { display: flex; align-items: center; gap: 10px; margin: 0 0 14px; }
.pick-search input { flex: 1; max-width: 460px; }
/* A hidden section keeps its DOM, and everything it had open in it. */
.pd-body[hidden] { display: none; }

/* WHERE IT IS — one card, above every tab, naming every place the search
   turns up. It exists because a digit on a tab you have to click is not an
   answer to "where is this build" (owner, 2026-09-22). */
.pd-where { margin-bottom: 14px; }
.pd-where ul { list-style: none; margin: 0; padding: 0; }
.pd-where li { display: flex; align-items: baseline; gap: 10px; padding: 8px 19px;
  border-top: 1px solid var(--border); font-size: 14px; cursor: pointer; }
.pd-where li:hover { background: var(--panel2); }
.pd-where li.here { cursor: default; }
.pd-where li.here:hover { background: transparent; }
.pd-where li .badge { flex: 0 0 76px; text-align: center; }
.pd-where li .what { flex: 1; min-width: 0; }
.pd-where li .where { font-size: 12.5px; white-space: nowrap; }

/* ---- Forms ---- */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
/* Even tracks. A ceiling in the track (minmax(210px, 380px)) looks right until
   the row can't fit a whole number of them — grid fills greedily, so the LAST
   column comes out short and the row reads misaligned. The ceiling lives on the
   containers instead; here the tracks just share what they're given. */
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px 14px; }
/* A FIELD HAS A MEASURE. Once the page stopped being capped, a lone input in a
   wide card — notes, an address line — grew to a 1900px slot nobody can aim at.
   In a grid the track already bounds it, so the cap is lifted back off there. */
.field > input, .field > select, .field > textarea { max-width: 774px; }
.form-grid .field > input, .form-grid .field > select, .form-grid .field > textarea { max-width: none; }
label { font-size: 13px; color: var(--muted); font-weight: 600; }

/* Bill-to / deliver-to, side by side — on an order form and in its dialog.
   They stack below 720px because a postcode field 90px wide is unusable. */
/* auto-fit, not a fixed 1fr 1fr: the sales-order form puts THREE panels in this
   row (details · bill to · deliver to) and the edit dialog puts two. Both want
   as many columns as the surface can hold at a usable width, and one column
   under ~330px, which the old hard-coded 720px query got wrong for the 3-up. */
.so-addr-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); gap: 14px; align-items: start; }
.so-addr-box { border: 1px solid var(--border); border-radius: var(--r); padding: 12px 14px 2px; background: transparent; }
.so-addr-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; min-height: 28px; }
.so-addr-head h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); margin: 0; }
/* HOW IT LEAVES (0159) — delivery or collection, ticked where the delivery
   address is already being answered. It sits hard right beside Same as
   billing rather than being spread by the head's space-between, because the
   two controls are one decision: where does this go, and who takes it. */
.so-freight { display: inline-flex; align-items: center; gap: 7px; margin-left: auto; padding: 4px 9px 4px 7px;
  border: 1px solid var(--border-strong); border-radius: var(--r-sm); font-size: 12.5px; cursor: pointer;
  white-space: nowrap; user-select: none; }
.so-freight input { width: auto; padding: 0; margin: 0; }
.so-freight.on { background: var(--ok-bg); border-color: var(--ok-bd); color: var(--ok); font-weight: 600; }
.so-freight:has(input:disabled) { opacity: .55; cursor: default; }
/* Suburb / state / postcode read as one line the way an address is written —
   auto-fit would stack them in a 310px column. */
.so-addr-loc { grid-template-columns: minmax(0, 1.7fr) minmax(0, 0.8fr) minmax(0, 1fr); gap: 10px; }

input, select, textarea { font: inherit; font-size: 14px; background: var(--panel); color: var(--text); border: 1px solid var(--border-strong); border-radius: var(--r); padding: 10px 12px; width: 100%; transition: border-color .12s, box-shadow .12s; }
/* THE TWO LEDGERS SIT SIDE BY SIDE in half a page each, so their action cells
   have about 140px before the table starts scrolling sideways. The invoice side
   carries one button more than the received side (Pay, as well as Match and the
   void/remove ✕), so its buttons are allowed to wrap onto a second line rather
   than push the invoice number down to one word per row. */
.po-page .card.np.po-ledger td:last-child { white-space: normal; }
.po-ledger td:last-child .btn { margin-bottom: 2px; }

/* A delivery's own lines, expanded inside the received ledger (0140).
   A GRID, not a nested <table>: a table inside a <td> gets its header row
   duplicated into the body by the HTML parser, and the fix is to stop nesting
   tables rather than to fight it. Inset and quieter than its parent so it reads
   as detail OF the row above, not a second list competing with it. */
.po-ledger .grn-items > td { padding: 0 10px 10px; }
.grn-lines { background: var(--panel2); border-radius: var(--r); padding: 2px 0; }
.grn-line { display: grid; grid-template-columns: 1fr 90px 110px; gap: 10px; padding: 6px 10px; align-items: baseline; font-size: 12.5px; }
.grn-line + .grn-line { border-top: 1px solid var(--border); }
.grn-line.head { font-size: 10.5px; font-weight: 700; color: var(--muted); letter-spacing: 0.04em; text-transform: uppercase; }
.grn-line em { display: block; font-style: normal; font-size: 11.5px; }

/* The rolling-arrival repeater (0139) reads as a sentence rather than a row of
   labelled boxes, so the fields sit inline with the words that give them their
   meaning and wrap as a paragraph would. */
.rp-sentence { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 10px; font-size: 13.5px; color: var(--muted); }
.rp-sentence input, .rp-sentence select { width: auto; padding: 6px 8px; font-size: 13.5px; }
.rp-sentence input[type="date"] { width: auto; }
.rp-sentence button { margin-left: auto; }

/* A TICK IS NOT A TEXT BOX. The rule above is written for text inputs and it
   also lands on checkboxes and radios, which then render as full-width bordered
   boxes with 10px of padding — shoving whatever sits beside them off the edge of
   its container. Every checkbox in the app used to patch `width: auto` locally
   (.sp-chk input, and so on); the ones that forgot were silently broken. Reset
   it once, here, where the damage is done. */
input[type="checkbox"], input[type="radio"] {
  width: auto; padding: 0; border: none; border-radius: 0; background: none;
  flex: none; transition: none;
}
input::placeholder, textarea::placeholder { color: var(--faint); }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }

/* ---- Bars (home value-by-warehouse) ---- */
.bar-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; font-size: 14px; }
.bar-label { width: 46px; color: var(--muted); font-size: 12.5px; font-weight: 600; }
.bar { flex: 1; height: 6px; background: var(--panel2); border-radius: var(--r); overflow: hidden; }
.bar-fill { display: block; height: 100%; background: var(--accent-deep); }
.bar-val { width: 96px; text-align: right; font-variant-numeric: tabular-nums; font-size: 13px; }

/* ---- Banners ---- */
.banner { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-radius: var(--r); border: 1px solid var(--border); margin-bottom: 14px; font-weight: 500; background: var(--panel); font-size: 14px; box-shadow: var(--shadow-sm); }
.banner.ok { background: var(--ok-bg); border-color: var(--ok-bd); color: var(--ok); }
.banner.bad { background: var(--bad-bg); border-color: var(--bad-bd); color: var(--bad); }
/* Information, not a warning: "the carrier API is not connected", "3 metrics
   are not measured". Slate, so it cannot be mistaken for something to act on. */
.banner.info { background: var(--info-bg); border-color: var(--info-bd); color: var(--text); }

/* Batch instructions: a work document, not a card */
.instructions { border-left: 2px solid var(--border-strong); padding: 6px 14px; margin: 0 0 14px; font-size: 14.5px; }
.instructions .kicker { display: block; margin-bottom: 1px; }

/* Cycle stats strip (work order detail) */
.cycle-strip { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.cycle-cell { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r); padding: 8px 12px; display: flex; align-items: baseline; gap: 10px; font-size: 13px; }
.cycle-stage { font-weight: 600; font-size: 13px; }

/* App-wide integrity banner (hidden unless the ledger fails to verify). */
/* The boot gate. Lives outside .app so it can be shown while the app itself is
   still hidden — an app drawn without permissions is a screen with every button
   missing, which reads as a broken feature rather than a missing answer. */
#boot-block { position: fixed; inset: 0; z-index: 9999; display: grid; place-items: center; background: var(--bg); padding: 24px; }
.boot-block-box { max-width: 52ch; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r); padding: 22px 24px; box-shadow: var(--shadow-sm); }
.boot-block-box h1 { font-size: 19px; margin: 0 0 10px; }
.boot-block-box p { margin: 0 0 10px; font-size: 14px; line-height: 1.5; }
.boot-block-box .btn { margin-top: 4px; }
/* R-UI-1: the sign-in card is two short fields wide, not an essay wide. */
.boot-block-box.login { max-width: 32ch; }
.boot-block-box.login p.muted { margin-bottom: 18px; }
.boot-block-box.login .field { margin-bottom: 12px; }
.boot-block-box.login .btn { width: 100%; justify-content: center; margin-top: 6px; }
.boot-block-box.login .field-error:empty { display: none; }

.integrity-banner { display: none; }
.integrity-banner.show { display: block; background: var(--bad-bg); border-bottom: 2px solid var(--bad); color: var(--bad); padding: 10px 28px; font-size: 14px; }
.integrity-banner.show a { color: var(--bad); font-weight: 700; text-decoration: underline; margin-left: 6px; }
/* Out-of-date is not a fault — it is ochre, and it carries the fix as a button
   rather than asking the reader to know about Ctrl+Shift+R. */
.integrity-banner.show.stale { background: var(--warn-bg); border-bottom-color: var(--warn); color: var(--warn); display: flex; align-items: center; gap: 10px; }
.integrity-banner.show.stale .btn { margin-left: auto; color: var(--warn); border-color: var(--warn-bd); background: var(--panel); }
.grow { flex: 1; }
.dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
.dot.ok { background: var(--ok); }
.dot.bad { background: var(--bad); }

/* ---- Toast: inverted slip ---- */
.toast { position: fixed; bottom: 18px; right: 18px; background: var(--ink-btn); border: 1px solid var(--ink-btn); color: var(--ink-btn-ink); padding: 11px 16px; border-radius: var(--r); font-size: 14px; font-weight: 500; box-shadow: var(--shadow-lg); opacity: 0; transform: translateY(6px); transition: 0.18s; max-width: 380px; pointer-events: none; z-index: 120; }
.toast.show { opacity: 1; transform: none; }
.toast.err { background: var(--bad-solid); border-color: var(--bad-solid); color: #fff7f2; }
/* An error stays up to be read; a click puts it away (util.js `toast`). */
.toast.show.err { pointer-events: auto; cursor: pointer; }

/* ---- Login gate ---- */
.login-sub { color: var(--muted); font-size: 14px; margin: 6px 0 20px; }
.login-error { color: var(--bad); font-size: 14px; min-height: 18px; margin: 2px 0 8px; }

/* ---- Top bar: theme flip, current user + logout ---- */
/* The right cluster may shrink — pinned at flex-shrink:0 it pushed the whole
   app wider than the window and the BODY scrolled sideways. Identity is the
   first thing to go: the avatar still says who you are. */
/* Standing top-bar links. Same active wash as the sidebar (#nav a.nav-sub.active)
   so "where am I" reads identically in both places.
   They used to be bare text pills, which read as prose sitting next to the
   search box rather than as the four standing DOORS they are. Now each one is
   a bordered control with a glyph, in the house micro-label case (11.5px/700
   uppercase, same as a thead th or a .kicker) — a set of tools, not a
   sentence. */
.topbar-jump { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.topbar-jump a {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 11.5px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--muted); text-decoration: none; white-space: nowrap;
  padding: 6px 12px; border-radius: var(--r-pill);
  border: 1px solid var(--border); background: var(--panel);
  transition: color .12s, background .12s, border-color .12s;
}
.topbar-jump a svg { width: 15px; height: 15px; flex: none; opacity: .85; }
.topbar-jump a:hover { color: var(--text); border-color: var(--border-strong); background: var(--panel2); }
.topbar-jump a:hover svg { opacity: 1; }
.topbar-jump a.active { color: var(--accent); border-color: var(--accent); background: var(--accent-wash); }
.topbar-jump a.active svg { opacity: 1; }
.topbar-jump a:focus-visible { outline: none; box-shadow: var(--ring); }
/* Narrow screens keep the floor itself and drop the extras before the search.
   Below 900 the labels go and the glyphs stay: four icons still fit where four
   words do not, and every one of them carries a `title`. */
@media (max-width: 1180px) { .topbar-jump a[data-jump="floor-app"] { display: none; } }
@media (max-width: 1000px) { .topbar-jump a[data-jump="floor-plan"] { display: none; } }
@media (max-width: 900px) {
  .topbar-jump a { padding: 6px 9px; }
  .topbar-jump a span { display: none; }
}
/* Under 900px the bar carries one door, and it is the floor app (owner,
   2026-09-27): big, red, labelled. Systems gives up its seat — the drawer
   and the search both still reach the register. */
.topbar-jump a.topbar-floorgo { display: none; }
@media (max-width: 900px) {
  .topbar-jump a[data-jump="systems"] { display: none; }
  .topbar-jump a.topbar-floorgo {
    display: inline-flex; gap: 8px; min-height: 42px; padding: 8px 17px 8px 13px;
    font-size: 13.5px; letter-spacing: 0.04em;
    color: var(--accent-ink); background: var(--accent-deep); border-color: var(--accent-deep);
    box-shadow: 0 1px 2px rgba(179,20,28,0.35), 0 3px 10px rgba(179,20,28,0.20);
  }
  .topbar-jump a.topbar-floorgo span { display: inline; }
  .topbar-jump a.topbar-floorgo svg { width: 19px; height: 19px; opacity: 1; }
  .topbar-jump a.topbar-floorgo:hover { color: var(--accent-ink); background: var(--accent-deep); border-color: var(--accent-deep); filter: brightness(1.09); }
}
@media (max-width: 820px) { .integrity { display: none; } }
/* ---- Sidebar foot: who you are, and how the app looks ----
   Both moved out of the top bar 2026-08-18 (owner). They are settings about the
   READER, not about the page, so they sit under the module tree — which #nav's
   flex:1 pins to the bottom of the rail — behind a hairline that reads as the
   mirror of the one under the wordmark. */
.sidebar-foot { flex: none; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 8px; }
.sidebar-foot .user-box:empty { display: none; }
.theme-flip { font: inherit; font-size: 12.5px; font-weight: 600; color: var(--muted); background: var(--panel2); border: 1px solid transparent; border-radius: var(--r-pill); padding: 7px 12px; cursor: pointer; white-space: nowrap; text-align: left; transition: color .12s, background .12s; }
.theme-flip:hover { color: var(--text); background: var(--border); }

/* ---- THE LANGUAGE FLIP (bottom left, 2026-09-21) --------------------------
   Same pill as the theme flip directly above it, because it is the same KIND
   of thing: a setting about you, not about the page. It names the language you
   would switch TO, so the button is the answer rather than the question, and
   it always says that name IN that language — "中文" is legible to somebody who
   cannot read the word "Chinese", which is the entire point of the button. */
.lang-flip { font: inherit; font-size: 12.5px; font-weight: 600; color: var(--muted); background: var(--panel2);
             border: 1px solid transparent; border-radius: var(--r-pill); padding: 7px 12px; cursor: pointer;
             white-space: nowrap; text-align: left; display: flex; align-items: center; gap: 7px;
             transition: color .12s, background .12s; }
.lang-flip:hover { color: var(--text); background: var(--border); }
.lang-flip svg { flex: none; width: 14px; height: 14px; opacity: .8; }
/* On the sign-in card, where it matters most: the person who needs it has
   not got through the door yet. */
.boot-lang { position: fixed; left: 18px; bottom: 18px; z-index: 2; }
.boot-lang .lang-flip { background: var(--panel); border-color: var(--border); box-shadow: var(--sh-1, none); }

/* Chinese sets tighter than Latin and carries no ascender rhythm, so the two
   places this design system leans on negative tracking — big numbers and page
   titles — need it taken back off, or the characters touch. */
[data-lang="zh-Hans"] h1, [data-lang="zh-Hans"] h2, [data-lang="zh-Hans"] h3,
[data-lang="zh-Hans"] .kpi-value, [data-lang="zh-Hans"] .stat-num, [data-lang="zh-Hans"] .kpi-total {
  letter-spacing: 0;
}
/* The masthead kicker is letter-spaced small caps in English; on Chinese that
   reads as broken text rather than as a label. */
[data-lang="zh-Hans"] .kicker { letter-spacing: .04em; text-transform: none; }
.user-box { display: flex; align-items: center; gap: 10px; min-width: 0; }
.user-box .btn { margin-left: auto; }
.user-chip { display: flex; align-items: center; gap: 9px; min-width: 0; }
.avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--accent-deep); color: var(--accent-ink); display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; box-shadow: var(--shadow-sm); }
.user-meta { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
.user-name { font-size: 13.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.role-badge { font-size: 11px; color: var(--muted); }

/* ---- The Escape palette ----
   Escape steps the whole app back: the page behind blurs and dims, one lit box
   comes forward. The blur is what makes it read as a layer rather than a card
   that happened to appear — and it is why the box needs no heavy chrome to be
   obviously in front.

   It sits ABOVE the modal layer (z 100) because Escape can be pressed anywhere,
   and a palette peeking out from under something is worse than not opening.
   In practice they never coexist: Escape stands down while a modal is open. */
.palette { position: fixed; inset: 0; z-index: 120; display: flex; justify-content: center; padding: 22px;
  /* NOT dead centre. The box grows DOWNWARDS as results arrive, and a centred
     box grows both ways — the line you are reading slides up the screen while
     you read it. Anchored high, the input never moves and the results fill the
     room below it, which is also where the eye already is after a keystroke. */
  align-items: flex-start; padding-top: 15vh;
  background: var(--overlay); -webkit-backdrop-filter: blur(7px) saturate(0.86); backdrop-filter: blur(7px) saturate(0.86);
  /* The entrance is an ANIMATION with NO fill-mode, not a transition out of an
     opacity:0 base. A transition needs the browser to paint the start state on
     one frame and the end state on the next, which is why the first cut needed
     a requestAnimationFrame and a second class — two more things to get out of
     step. Here the RESTING style is the visible one and the keyframe only plays
     the arrival, so toggling `hidden` is the entire trigger. */
  animation: palette-in .13s ease; }
.palette[hidden] { display: none; }

.palette-box { width: 100%; max-width: 620px; background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow-lg); overflow: hidden;
  animation: palette-box-in .16s cubic-bezier(.2,.8,.3,1); }
@keyframes palette-in { from { opacity: 0; } }
@keyframes palette-box-in { from { opacity: 0; transform: translateY(-8px) scale(.985); } }

/* The field is the whole head of the box — no border, no well. The box IS the
   input; a bordered input inside a bordered panel is two frames for one thing. */
.palette-field { display: flex; align-items: center; gap: 12px; padding: 4px 16px 4px 18px; }
.palette-ic { width: 18px; height: 18px; flex: none; color: var(--faint); }
.palette-field input { flex: 1; min-width: 0; background: none; border: 0; outline: none; color: var(--text);
  font: inherit; font-size: 17px; letter-spacing: -0.01em; padding: 17px 0; }
.palette-field input::placeholder { color: var(--faint); }
.palette-esc { flex: none; }

/* The dropdown styles are shared with the top bar (.sr-*) — only the framing
   changes: in here it is a panel section, not a floating menu. */
.palette .search-results { position: static; max-height: min(52vh, 460px); border: 0; border-top: 1px solid var(--border);
  border-radius: 0; box-shadow: none; padding: 6px; }

.palette-foot { display: flex; align-items: center; gap: 14px; padding: 9px 16px; border-top: 1px solid var(--border);
  background: var(--panel2); font-size: 11.5px; color: var(--muted); }
.palette-foot-end { margin-left: auto; color: var(--faint); }
.palette kbd { font-family: var(--mono); font-size: 10.5px; font-weight: 600; color: var(--muted);
  background: var(--panel); border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 5px;
  padding: 2px 5px; margin-right: 3px; line-height: 1.4; }

/* Small screens: the footer hints are for a keyboard, and the tail is chatter. */
@media (max-width: 640px) { .palette { padding-top: 9vh; } .palette-foot-end { display: none; } }
@media (max-width: 460px) { .palette-foot { display: none; } }
/* A blurred backdrop that animates is the one thing motion sickness actually
   punishes. Keep the layer, drop the travel. */
@media (prefers-reduced-motion: reduce) { .palette, .palette-box { animation: none; } }

/* ---- API key reveal ---- */
.key-reveal { background: var(--ok-bg); border: 1px solid var(--ok-bd); border-radius: var(--r); padding: 12px 14px; margin-bottom: 14px; }
.key-reveal .key-value { font-family: var(--mono); font-size: 14px; color: var(--ok); word-break: break-all; background: var(--panel); border: 1px solid var(--ok-bd); border-radius: var(--r); padding: 8px 10px; margin: 8px 0; }
.key-reveal .warn { color: var(--warn); font-size: 13px; }

/* ---- Modal dialogs (dialog.js + shop-floor prompts) ---- */
.modal-overlay { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; padding: 20px; background: var(--overlay); }
.modal { width: 100%; max-width: 470px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); padding: 22px 24px; box-shadow: var(--shadow-lg); animation: modal-in 0.12s ease-out; }
@keyframes modal-in { from { transform: translateY(6px); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .modal { animation: none; } .toast { transition: opacity 0.18s; transform: none; } }
.modal-title { font-size: 16.5px; font-weight: 700; margin: 0 0 8px; letter-spacing: -0.01em; }
.modal.danger { border-color: var(--bad); }
.modal.danger .modal-title { color: var(--bad); }
.modal-body { font-size: 14px; color: var(--muted); line-height: 1.55; }
.modal-body b, .modal-body .mono { color: var(--text); }
.modal-reason { margin-top: 12px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 6px; margin-top: 18px; }
/* An escape hatch is not a peer of the way out: it goes to the far left, in the
   muted ink of a back link, so the right-hand end of the bar keeps meaning
   "finish with this window". */
.modal-actions.spread { justify-content: space-between; align-items: center; }
/* A PICKER IN A WINDOW IS A LIST, and the window has to be sized by the list --
   not by the paragraph above it. Fixed at 580x600 it showed six of thirty-three
   models through a letterbox while 300px of screen sat empty underneath. Title,
   blurb and buttons are pinned; the rows are the only thing that scrolls; the
   height is whatever the screen can spare. */
.modal.modal-pick { max-width: 720px; max-height: min(88vh, 820px); display: flex; flex-direction: column; }
.modal.modal-pick .picker { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; margin: 12px 0 0; }
.modal.modal-pick .pick-list { flex: 1 1 auto; min-height: 140px; max-height: none; }
.modal.modal-pick .modal-actions { flex: none; margin-top: 14px; }
/* Ten rows deep with a number column at the far edge, the eye loses the line
   between "Riot" and its price across 350px of nothing. The dropdown pickers
   are five rows and need no rule; this one does. */
.modal-pick .pick-item { border-bottom: 1px solid var(--border); }
.modal-pick .pick-item:last-child { border-bottom: none; }
/* A FORM IN A WINDOW is the other half of the same lesson. `.form-sec` sets a
   248px label gutter beside its fields because it was drawn for a 1400px page,
   and it only gives that gutter up at `@media (max-width: 1000px)` -- a
   VIEWPORT query, which never fires for a 680px dialog sitting on a 1440px
   screen. So the New customer window was 248px of label beside 289px of
   fields, and the fields split that two-up: an email address in 135px, a
   suburb in 135px, and half the window a column with nothing in it.
   The window is sized for the form it holds now, the gutter is 200, and the
   title and the buttons are pinned while only the fields scroll -- so the
   button bar is a footer instead of a bar floating over whatever field you
   happened to scroll past. */
.modal.modal-form { max-width: 880px; max-height: min(88vh, 860px); display: flex; flex-direction: column; padding-bottom: 0; }
.modal.modal-form > .modal-scroll {
  flex: 1 1 auto; min-height: 0; overflow: auto; margin: 14px -24px 0;
  border-top: 1px solid var(--border);
  scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent;
}
.modal.modal-form > .modal-scroll::-webkit-scrollbar { width: 10px; }
.modal.modal-form > .modal-scroll::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 10px; border: 3px solid var(--panel); }
.modal.modal-form > .modal-scroll::-webkit-scrollbar-track { background: transparent; }
.modal.modal-form > .form-error { margin: 12px 0 0; }
.modal-form .form-sec { grid-template-columns: 200px 1fr; padding: 20px 24px; }
.modal.modal-form > .form-actions { flex: none; margin: 0 -24px; padding: 14px 24px; }
/* The page collapses the gutter at 1000px; a rule that beats the page rule has
   to collapse with it, or a phone gets 200px of label beside 100px of field. */
@media (max-width: 1000px) {
  .modal.modal-form { max-width: none; }
  .modal-form .form-sec { grid-template-columns: 1fr; gap: 12px; padding: 16px 20px; }
}
.btn.danger-solid { background: var(--bad-solid); border-color: var(--bad-solid); color: #fff7f2; font-weight: 600; }
.btn.danger-solid:hover { filter: brightness(1.1); }
.summary-table { width: auto; margin: 6px 0 2px; }
.summary-table td { padding: 3px 8px; border: none; font-size: 14px; vertical-align: top; }
.summary-table td:first-child { color: var(--muted); padding-left: 0; white-space: nowrap; }

/* ---- Form framework (forms.js) ---- */
.field-error { color: var(--bad); font-size: 13px; min-height: 15px; margin-top: 2px; }
.field .req { color: var(--bad); }
.field .hint { color: var(--muted); font-size: 13px; margin-top: 2px; font-weight: 400; }
input.invalid, select.invalid, textarea.invalid { border-color: var(--bad); }
input.invalid:focus, select.invalid:focus, textarea.invalid:focus { border-color: var(--bad); box-shadow: 0 0 0 1px var(--bad); }
label.check { flex-direction: row; display: flex; align-items: center; gap: 8px; color: var(--text); font-size: 14px; font-weight: 400; }
label.check input { width: auto; }
/* Several answers to one question — which locations a warehouse holds, and
   anything else that is a set rather than a yes/no. A grid, not a list, so a
   long set wraps into columns instead of running off the bottom of a dialog. */
.check-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 6px 16px; margin-top: 4px; }
.form-error { background: var(--bad-bg); border: 1px solid var(--bad-bd); color: var(--bad); border-radius: var(--r); padding: 9px 11px; font-size: 14px; margin-bottom: 12px; }
.btn.loading { position: relative; color: transparent !important; pointer-events: none; }
.btn.loading::after { content: ""; position: absolute; width: 13px; height: 13px; top: 50%; left: 50%; margin: -6.5px 0 0 -6.5px; border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%; color: var(--accent-ink); animation: spin 0.6s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
/* A short single-column form is a reading column, not a billboard — but 560px
   was tight enough that even two fields had to stack. 760px holds two tracks. */
.form-card { max-width: 760px; }
/* A form that carries the build grid can't live in the reading column — the
   parts table alone is 880px. Let the card run, and keep the plain fields at a
   sane measure so the text inputs don't stretch across the whole bench. */
.form-card.with-parts { max-width: none; }
.form-card.with-parts > .field, .form-card.with-parts > .form-grid { max-width: 774px; }
.form-sub { margin: -4px 0 14px; }
.line-total { text-align: right; font-weight: 700; font-size: 15px; padding: 10px 4px 0; font-variant-numeric: tabular-nums; }
.form-actions { display: flex; gap: 8px; align-items: center; margin-top: 18px; }

/* ---- Sectioned forms: a long form reads as a few labelled areas, laid out
   across the page, instead of one endless single-file column. Hierarchy comes
   from three distinct text levels (section title / label / hint) plus the rule
   and the section's tinted heading column — not from size alone. ---- */
/* Uncapped. 1180px parked half a wide monitor in grey; the section grid below
   is what keeps this readable now — it answers extra width with more field
   columns, and each one stops at a measure you can still scan. */
.form-card.form-wide { max-width: none; padding: 0; overflow: visible; }
.form-wide .form-sub, .form-wide > .form-error { margin: 16px 20px; }
.form-sec { container-type: inline-size; container-name: formsec; display: grid; grid-template-columns: 248px 1fr; gap: 30px; padding: 24px; border-bottom: 1px solid var(--border); }
.form-sec:last-of-type { border-bottom: 0; }
.form-sec-head h3 { font-size: 15px; font-weight: 800; letter-spacing: -0.015em; margin: 0; color: var(--text); }
.form-sec-head .hint { color: var(--muted); font-size: 13px; margin-top: 5px; line-height: 1.45; }
/* Was a hard 3 columns, which meant the fields squeezed to ~290px inside the old
   1180px cap and would balloon to ~640px now that the cap is gone. The count is
   a ladder rather than auto-fit so every column is the SAME width at every step
   — auto-fit with a track ceiling leaves a short last column. `.span-2` still
   spans two of whatever the step is; at one column it is capped back to one. */
/* Was a hard 3 columns, which meant the fields squeezed to ~290px inside the old
   1180px cap and would balloon to ~640px now that the cap is gone. The count is
   a LADDER, not auto-fit: every column is the same width at every step, where
   auto-fit with a track ceiling leaves a short, misaligned last column.

   The steps are @container, not @media, because what decides how many fields
   fit is the width of the SECTION — and a section loses 248px to its heading
   column, sits inside a card, and may share the page with a rail. The viewport
   only knows about the window. `.span-2` keeps spanning two of whatever the
   current step is, and is dropped once there is only one column left. */
.form-sec-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 20px; align-content: start; }
/* The numbers are the section's CONTENT box, so they already exclude its 24px
   padding; the fields get another 278px less again for the heading column and
   the gap. Each step is the width at which the next column still leaves a field
   ~300px or wider — below that a step is a downgrade, not an upgrade. */
@container formsec (min-width: 1100px) { .form-sec-fields { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@container formsec (min-width: 1480px) { .form-sec-fields { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@container formsec (min-width: 1880px) { .form-sec-fields { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
@container formsec (max-width: 540px)  { .form-sec-fields { grid-template-columns: minmax(0, 1fr); } .form-sec-fields .span-2 { grid-column: auto; } }
.form-sec-fields .field { margin-bottom: 0; min-width: 0; }
.form-sec-fields .span-2 { grid-column: span 2; }
.form-sec-fields .span-full { grid-column: 1 / -1; }
/* The label is the quiet one; the value the reader scans is the loud one. */
.form-wide .field > label { font-size: 12.5px; font-weight: 700; color: var(--muted); letter-spacing: 0.02em; }
.form-wide .field input, .form-wide .field select, .form-wide .field textarea { font-size: 14.5px; }
.form-wide .field-error { min-height: 0; }
.form-wide .field-error:not(:empty) { margin-top: 4px; }
/* A field that can never be edited should not look like one that can. */
.field-locked input { background: var(--panel2); color: var(--muted); letter-spacing: 0.01em; font-weight: 500; cursor: not-allowed; }

/* Switches read as rows with room for their explanation, not as bare ticks. */
.switch-field .check { align-items: flex-start; gap: 10px; padding: 9px 11px; border: 1px solid var(--border); border-radius: var(--r); background: var(--panel3); }
.switch-field .check:hover { border-color: var(--border-strong); }
.switch-field .check input { margin-top: 1px; }
.switch-label { display: block; font-weight: 600; font-size: 14px; color: var(--text); }
.switch-field .check .hint { display: block; margin-top: 2px; }
/* A switch that reaches outside this page wears its guard, so the confirm
   dialog is expected rather than a surprise. */
.switch-guarded .check { border-left: 1px solid var(--warn-bd); background: var(--panel2); }
.guard-tag { display: inline-block; vertical-align: 1px; margin-left: 6px; background: var(--warn-bg); border: 1px solid var(--warn-bd); color: var(--warn); border-radius: var(--r-pill); padding: 0 7px; font-size: 11px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase; }
.seg.sm .seg-btn { padding: 4px 11px; font-size: 13.5px; }

/* Units live on the control — "$" and "g" are not part of the field's name. */
.affix { position: relative; display: block; }
.affix-t { position: absolute; top: 50%; transform: translateY(-50%); color: var(--muted); font-size: 13.5px; pointer-events: none; }
.affix-pre .affix-t { left: 11px; }
.affix-pre input { padding-left: 24px; }
.affix-post .affix-t { right: 11px; }
.affix-post input { padding-right: 42px; }

.form-actions.sticky { position: sticky; bottom: 0; margin: 0; padding: 13px 20px; background: var(--panel); border-top: 1px solid var(--border); border-radius: 0 0 var(--r-lg) var(--r-lg); box-shadow: 0 -6px 18px -12px var(--shadow); z-index: 5; }
/* Below 1000px the heading stops being a column and becomes a caption above the
   fields. The COUNT is not set here — @container formsec above owns that, and
   this rule widens the section, so overriding it from the viewport would undo
   the extra column the stack just paid for. */
@media (max-width: 1000px) { .form-sec { grid-template-columns: 1fr; gap: 12px; } }

/* ---- Tabbed detail pages (supplier / customer) ---- */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 18px; flex-wrap: wrap; }
.tab { font: inherit; font-size: 14px; cursor: pointer; background: none; border: none; border-bottom: 2px solid transparent; color: var(--muted); padding: 9px 13px; margin-bottom: -1px; border-radius: var(--r) var(--r) 0 0; transition: color .12s, background .12s; }
.tab:hover { color: var(--text); background: var(--panel2); }
.tab.active { color: var(--accent); font-weight: 700; border-bottom-color: var(--accent); }
.tab .count { display: inline-block; background: var(--chip); border: 1px solid var(--border); border-radius: var(--r-pill); padding: 0 6px; font-size: 12px; margin-left: 5px; color: var(--muted); font-family: var(--sans); font-weight: 600; }
/* A tab that lists a DIFFERENT KIND OF THING from the ones before it (RMA:
   eight tabs of parts, then one of shipments). A rule rather than a gap,
   because a gap in a wrapping strip disappears the moment the strip wraps. */
.tab.rw-tabsep { margin-left: 10px; padding-left: 17px; border-left: 1px solid var(--border); }
.tab-panel[hidden] { display: none; }

/* ---- A tab's own toolbar: what it says on the left, what it does on the right.
   Used by the pages that are TABS of a shell (#/sales-settings, the adjustments
   screen) rather than pages in their own right: the shell drew the h1 and the
   rule under it, so a second .page-head here would draw a second rule and a
   second title. `.end` is the same row with nothing to say — controls only. */
.tab-bar { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin: -4px 0 14px; }
.tab-bar.end { justify-content: flex-end; }
.tab-bar > .muted { font-size: 12.5px; max-width: 92ch; flex: 1 1 380px; min-width: 0; }
.tab-bar > .row-gap { flex: 0 0 auto; }
.tab-panel .form-card { max-width: 620px; }

/* ---- Compatibility editor (compat.js) ---- */
.attr-row { border: 1px solid var(--border); border-radius: var(--r); padding: 10px 12px; margin-bottom: 8px; background: var(--panel); }
.attr-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 6px; }
.attr-label { font-weight: 600; font-size: 14px; }
.attr-row .hint, .rule-row .hint { color: var(--muted); font-size: 12.5px; margin-top: 6px; }
.rule-row { border: 1px solid var(--border); border-radius: var(--r); padding: 10px 12px; margin-bottom: 8px; background: var(--panel); }
.rule-sentence { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rule-attr { font-weight: 600; font-size: 14px; }
.rule-sentence select.rule-op { width: auto; min-width: 105px; }
.rule-value { flex: 1; min-width: 180px; }
.rule-preview { margin-top: 7px; font-size: 13.5px; color: var(--text); background: var(--chip); border: 1px dashed var(--border-strong); border-radius: var(--r); padding: 6px 9px; }
.pv-tag { font-size: 11px; font-weight: 700; color: var(--muted); margin-right: 6px; }

/* chips multi-select (enum values) */
.chips { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.chip-sel { display: inline-flex; align-items: center; gap: 4px; background: var(--chip); border: 1px solid var(--border-strong); border-radius: var(--r); padding: 2px 5px 2px 9px; font-size: 13.5px; }
.chip-x { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 15px; line-height: 1; padding: 0 3px; }
.chip-x:hover { color: var(--bad); }
.chips select.chip-add { width: auto; min-width: 140px; }

/* yes/no segmented toggle (boolean values) */
.seg { display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--r); overflow: hidden; }
.seg-btn { font: inherit; background: var(--panel); color: var(--muted); border: none; padding: 5px 14px; cursor: pointer; }
.seg-btn + .seg-btn { border-left: 1px solid var(--border-strong); }
.seg-btn.on { background: var(--ink-btn); color: var(--ink-btn-ink); font-weight: 600; }
/* A .field is a flex column, so an inline-flex child still gets stretched to
   the full track — a two-button toggle came out as a card-wide bar. A segmented
   control is sized by its options, always. */
.field > .seg { align-self: flex-start; }

.num-wrap { display: inline-flex; align-items: center; gap: 7px; }
.num-wrap input { width: 130px; }
.num-wrap .unit { color: var(--muted); font-size: 13.5px; }

/* attribute picker (search + grouped list) */
.picker { border: 1px solid var(--border-strong); border-radius: var(--r); margin: 4px 0 12px; background: var(--panel); overflow: hidden; }
.picker .pick-search { width: 100%; border: none; border-bottom: 1px solid var(--border); border-radius: 0; padding: 9px 11px; }
.picker .pick-search:focus { box-shadow: none; }
.pick-list { max-height: 300px; overflow: auto; overscroll-behavior: contain;
  scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
.pick-list::-webkit-scrollbar { width: 10px; }
.pick-list::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 10px; border: 3px solid var(--panel); }
.pick-list::-webkit-scrollbar-track { background: transparent; }
.pick-group { font-size: 11px; font-weight: 700; color: var(--muted); padding: 9px 11px 3px; letter-spacing: 0.04em; text-transform: uppercase; }
/* The heading stays put while its own rows scroll under it. A list long enough
   to scroll is a list where "am I still in the custom ranges?" is a live
   question, and the answer was the first thing to leave the screen. */
.pick-list > .pick-group { position: sticky; top: 0; z-index: 1; background: var(--panel); }
.pick-item { display: block; width: 100%; text-align: left; font: inherit; background: none; border: none; cursor: pointer; padding: 6px 11px; color: var(--text); }
.pick-item:hover { background: var(--panel2); }
/* THE ROW THAT STAYS CHOSEN. Everywhere else a pick closes its list, so there
   was never a selected state to draw; the batch parts door keeps the list on
   screen and asks what to do to the part, so the part has to stay visible. */
.pick-item.on { background: var(--accent-wash); box-shadow: inset 2px 0 0 var(--accent); }
.pick-label { display: block; font-size: 14px; font-weight: 600; }
.pick-desc { display: block; font-size: 12.5px; color: var(--muted); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pick-empty { color: var(--muted); padding: 14px; text-align: center; font-size: 14px; }

/* A DROPDOWN YOU CAN TYPE INTO (2026-09-17). A native select only searches by
   first letter, which in a four-hundred-row category tree is not searching. The
   select is still there, still the value — this is the box in front of it. It
   wears the same border and focus ring as every other control because it IS the
   control now; only the popup is ours. */
.combo { position: relative; }
.combo-list {
  position: absolute; z-index: 40; left: 0; right: 0; top: calc(100% + 3px);
  max-height: 280px; overflow: auto; overscroll-behavior: contain;
  background: var(--panel); border: 1px solid var(--border-strong); border-radius: var(--r);
  box-shadow: 0 10px 26px -14px var(--shadow); scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent;
}
.combo-list::-webkit-scrollbar { width: 10px; }
.combo-list::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 10px; border: 3px solid var(--panel); }
.combo-list::-webkit-scrollbar-track { background: transparent; }
.combo-opt { padding: 7px 11px; cursor: pointer; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.combo-opt:hover, .combo-opt.active { background: var(--panel2); }
/* What is CHOSEN and what the arrow keys are ON are two different facts, and on
   a filtered list they are usually different rows. */
.combo-opt.on { box-shadow: inset 2px 0 0 var(--accent); font-weight: 600; }
.combo-empty { color: var(--muted); padding: 12px; text-align: center; font-size: 13.5px; }
/* One fact on the grey line is not grey: a recipe with nothing in it is the one
   thing on that row you would want to know before you picked it. */
.pick-desc .flag-warn { color: var(--warn); font-weight: 600; }
/* A TICKABLE row (0140) — matching a bill to a delivery, or a delivery to
   bills. Same list furniture as .pick-item, but it is a checkbox: the point is
   that several can be true at once. */
.pick-row { display: flex; gap: 9px; align-items: flex-start; padding: 7px 11px; cursor: pointer; border-bottom: 1px solid var(--border); }
.pick-row:last-child { border-bottom: none; }
.pick-row:hover { background: var(--panel2); }
.pick-row input { margin-top: 3px; }
.pick-row span { min-width: 0; }
.pick-item.on { background: var(--panel2); box-shadow: inset 2px 0 0 var(--accent); }

/* The second list (0076). The strip is deliberately quiet — it is an offer, not
   a result — and it sits under everything so it never competes with a real hit.
   A row in the unlisted list carries its own call to action, because picking it
   does something extra: it brings the product back. */
.pick-more {
  display: block; width: 100%; text-align: left; font: inherit; font-size: 12.5px;
  background: none; border: none; border-top: 1px solid var(--border); cursor: pointer;
  padding: 8px 11px; color: var(--muted);
}
.pick-more:hover { background: var(--panel2); color: var(--text); }
/* THE TWO FACTS A PICKER EXISTS TO DELIVER (owner, 2026-09-14: "the 9-available
   ... is really hard to see"). Stock and cost used to be the fourth and fifth
   clause of a grey `·` sentence, in the same 12.5px muted grey as the product's
   name. They are a right-hand column now: number over caption, stock coloured
   by what it means — green when there is some, amber when there is none, red
   when it is already oversold — and cost deliberately NOT coloured, because a
   number that is neither good nor bad earns its contrast from weight. */
.pick-item.has-meta { display: flex; align-items: center; gap: 12px; }
.pick-main { flex: 1 1 auto; min-width: 0; }
.pick-meta { flex: none; display: flex; align-items: stretch; gap: 6px; }
/* 66px of box with 8px of padding each side left 50px for a number that is
   routinely 70px wide ($1,414.46), so every pill in the app was being pushed
   past its own minimum with its digits against the wall. Sized for the number
   it actually holds, and 5px of vertical padding so the caption underneath is
   not touching it. */
.pick-stock, .pick-cost {
  display: flex; flex-direction: column; align-items: flex-end; justify-content: center;
  gap: 2px; min-width: 74px; padding: 5px 9px; border-radius: var(--r-sm);
  border: 1px solid transparent;
}
.pick-stock b, .pick-cost b { font-family: var(--mono); font-size: 13px; font-weight: 700; line-height: 1.1; }
.pick-stock small, .pick-cost small {
  font-size: 9.5px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; line-height: 1;
}
.pick-stock.ok   { background: var(--ok-bg);  border-color: var(--ok-bd);  color: var(--ok); }
.pick-stock.none { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--warn); }
.pick-stock.over { background: var(--bad-bg); border-color: var(--bad-bd); color: var(--bad); }
.pick-cost { background: var(--panel2); border-color: var(--border); color: var(--text); }
.pick-cost small { color: var(--muted); }
.pick-cost.none b { color: var(--faint); }
.pick-item:hover .pick-cost { border-color: var(--border-strong); }

.pick-item[data-relist] { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pick-item[data-relist] .pick-main { flex: 1 1 auto; min-width: 0; }
.pick-item[disabled] { cursor: default; opacity: 0.55; }
.pick-cta { flex: none; font-size: 11px; font-weight: 700; letter-spacing: 0.03em; color: var(--accent); }
.pick-cta.muted { color: var(--muted); font-weight: 600; }

/* Search box + category narrowing, one bar. The category is a second way IN,
   not a filter applied afterwards — you go to Graphics Cards because you don't
   know the SKU — so it sits beside the box, not above the results. */
.pick-head { display: flex; align-items: stretch; border-bottom: 1px solid var(--border); }
.pick-head .pick-search { flex: 1; min-width: 0; border-bottom: none; }
.pick-head .pick-cat-host { flex: none; display: flex; }
.pick-cat {
  border: none; border-left: 1px solid var(--border); border-radius: 0;
  background: var(--panel2); color: var(--muted); font-size: 12.5px;
  max-width: 220px; padding: 0 9px;
}
/* THE CLOSED CONTROL IS NARROW; THE OPEN LIST MUST NOT BE (Vignesh, 2026-09-22).
   Chrome sizes a select's option popup to the select itself, so a 220px control
   served three hundred category names chopped mid-word — which is what his
   screenshot shows. `text-overflow` keeps the CLOSED state tidy at its own
   width, and the grouped labels (see mountCategoryFilter) are short enough to
   read inside it now that a row says its own name instead of its whole path. */
.pick-cat { text-overflow: ellipsis; }
.pick-cat optgroup { font-style: normal; font-weight: 600; color: var(--text); }
.pick-cat option { font-weight: 400; color: var(--text); }
.pick-cat:focus { box-shadow: none; color: var(--text); }

/* A timeline read as sentences rather than a table — one entry per rule change,
   each with its timestamp above it. */
.plain-list { list-style: none; margin: 0; padding: 0; font-size: 13.5px; }
.plain-list li { padding: 7px 0; border-top: 1px solid var(--border); line-height: 1.45; }
.plain-list li:first-child { border-top: none; padding-top: 0; }

/* What a mounted picker settled on, with the way back out. */
.pick-chosen { display: flex; align-items: center; gap: 8px; font-size: 13.5px; padding: 7px 0 0; flex-wrap: wrap; }
/* The code reads as a code and the name reads as a name, with a rule between
   them (Neel, 2026-09-22). The name takes the slack so the Change button stays
   on the right-hand end of the row wherever the picker is embedded. */
.pick-chosen-sku { color: var(--text); }
.pick-chosen-name { flex: 1 1 auto; min-width: 0; color: var(--muted);
  border-left: 1px solid var(--border); padding-left: 8px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Combobox: a select you can type at. The input IS the box, so the control
   reads as one field rather than a border inside a border, and the hit list
   floats over the page — opening it must not shove the recipe below down. */
.mp { position: relative; overflow: visible; }
.mp:focus-within { border-color: var(--accent); box-shadow: var(--ring); }
.mp .mp-in { border: none; border-radius: var(--r); }
.mp .mp-in:focus { box-shadow: none; }
.mp .mp-list { position: absolute; z-index: 30; left: -1px; right: -1px; top: calc(100% + 5px);
  max-height: 380px; background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: var(--r); box-shadow: var(--shadow-md); padding-bottom: 4px; }

/* ---- Compatibility violations panel (models / builds / swaps) ---- */
.compat-ok { color: var(--ok); font-size: 14px; padding: 2px 0; font-weight: 500; }
.compat-violations { display: flex; flex-direction: column; gap: 5px; }
.cv { display: flex; gap: 8px; font-size: 13.5px; border-radius: var(--r); padding: 6px 9px; border: 1px solid var(--border); }
.cv-error { background: var(--bad-bg); border-color: var(--bad-bd); color: var(--bad); }
.cv-warning { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--warn); }
.cv-info { background: var(--chip); color: var(--muted); }
.cv-ic { flex: none; font-weight: 700; }

/* QC checklist rows inside the stage prompt */
.qc-item { display: flex; align-items: center; gap: 10px; padding: 5px 0; }
.qc-label { flex: 1; font-size: 14px; }

/* ---- Help & guides (System module) ---- */
.help-wrap { display: flex; gap: 22px; align-items: flex-start; }
.help-nav { flex: none; width: 240px; position: sticky; top: 16px; display: flex; flex-direction: column; gap: 1px; }
.help-nav a { display: block; padding: 6px 10px; border-radius: var(--r); color: var(--muted); text-decoration: none; font-size: 14px; }
.help-nav a:hover { background: var(--panel2); color: var(--text); }
.help-nav a.active { background: var(--panel2); color: var(--text); font-weight: 600; }
.help-body { flex: 1; min-width: 0; max-width: 720px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r); padding: 8px 26px 26px; }
.help-body h2 { font-size: 21px; font-weight: 700; letter-spacing: -0.01em; margin: 22px 0 6px; }
.help-body h3 { font-size: 16px; font-weight: 600; margin: 20px 0 4px; }
.help-body h4 { font-size: 14px; font-weight: 600; color: var(--muted); margin: 16px 0 2px; }
.help-body p { font-size: 14.5px; line-height: 1.62; margin: 8px 0; color: var(--text); }
.help-body ul, .help-body ol { font-size: 14.5px; line-height: 1.6; padding-left: 22px; margin: 8px 0; }
.help-body li { margin: 3px 0; }
.help-body code { font-family: var(--mono); font-size: 13px; background: var(--chip); border: 1px solid var(--border); border-radius: var(--r); padding: 0 4px; }
.help-body strong { font-weight: 600; }
.help-body a { color: var(--accent); }
.help-table { margin: 12px 0; font-size: 14px; border: 1px solid var(--border); }
.help-table th { background: var(--panel2); text-align: left; }
@media (max-width: 820px) { .help-wrap { flex-direction: column; } .help-nav { width: 100%; position: static; flex-direction: row; flex-wrap: wrap; } }

/* ---- Build stage stepper (mfg.js) ---- */
.stepper { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.step { display: flex; align-items: center; gap: 6px; font-size: 13.5px; color: var(--muted); }
.step.past { color: var(--text); }
.step.now { color: var(--text); font-weight: 700; }

/* PC registry detail: the machine's life — main column of episodes + a rail. */
.pc-life { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 14px; align-items: start; }
@media (max-width: 1060px) { .pc-life { grid-template-columns: 1fr; } }

/* ==========================================================================
   THE SYSTEM CARD, REDESIGNED (owner directive, 2026-09-01)
   Proposal it was drawn against: docs/mockups/system-card.html
   --------------------------------------------------------------------------
   The page had become a stack of white boxes of equal visual weight, each one a
   table, none of them saying which mattered. Four moves, each with a reason:

     1. THE SERIAL IS THE NAME OF THE THING. It was 25px, the same size as every
        other page title in the app, sitting above six cards of the same colour.
        It is the only object on this page.
     2. A FACT STRIP instead of two rail cards. Model, customer, order, branch,
        promise and warranty are what you check first, and they were in two
        stacked boxes off to the right of everything.
     3. A NOTE IS PROSE, so it gets the width. It was in a four-column grid
        where a sentence competed with three short cells.
     4. TESTING INFORMATION IS ATTRIBUTES, not rows to compare — a definition
        grid reads them; a three-column table spends half its width on gutters.

   Everything below is prefixed `sc-` and used nowhere else, so none of it can
   reach another screen.
   ========================================================================== */

/* ---- 1. the head ---- */
.sc-serial {
  font-size: 34px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.05;
  margin: 4px 0 0; font-variant-numeric: tabular-nums;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
@media (max-width: 620px) { .sc-serial { font-size: 27px; } }
.badge.big { font-size: 13px; padding: 4px 13px; }

/* The serial is a copy button wearing the heading's clothes: same size, same
   weight, no chrome, and a glyph that turns up when you reach for it. It is the
   string most often retyped off this page by eye. */
.sc-serial-copy {
  font: inherit; color: inherit; background: none; border: 0; padding: 0; margin: 0;
  cursor: pointer; display: inline-flex; align-items: center; gap: 10px;
  text-align: left; max-width: 100%; overflow-wrap: anywhere;
}
.sc-serial-copy .ic {
  font-size: 15px; font-weight: 500; color: var(--faint);
  opacity: 0; transition: opacity 0.12s;
}
.sc-serial-copy:hover { color: var(--accent); }
.sc-serial-copy:hover .ic, .sc-serial-copy:focus-visible .ic { opacity: 1; }
@media (hover: none) { .sc-serial-copy .ic { opacity: 1; } }

/* ---- 1b. the identity row: SKU and name, under the serial ---- */
/* Three strings get taken off this page all day — the serial, the SKU and the
   name — so they read as one row of labelled values rather than a heading, a
   sentence and a sub-line in a fact cell. The SKU keeps a separate button to
   the catalogue: the value copies, the button leaves.

   The gap BETWEEN values is far wider than the gap inside one, or the SKU's
   button reads as though it belongs to the label after it. */
.sc-ident { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 30px; margin-top: 7px; }
.sc-id { display: inline-flex; align-items: center; gap: 8px; min-width: 0; max-width: 100%; }
.sc-id-k {
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--faint); flex: none;
}
/* OPEN THE PRODUCT — a real button, not a character in a sentence. The owner
   asked on 2026-09-16 for it to be about twice the size and to wear a better
   mark than an arrow. So: roughly twice the table arrow's target, the panel
   fill and border that say "press me", and an icon sized to the row's value
   rather than to the 12px sub-line the SKU used to live in. */
.sc-open {
  flex: none; display: inline-grid; place-items: center;
  width: 32px; height: 32px; border-radius: var(--r-sm);
  border: 1px solid var(--border); background: var(--panel2);
  color: var(--muted); text-decoration: none;
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.sc-open svg { width: 19px; height: 19px; }
.sc-open:hover, .sc-open:focus-visible {
  color: var(--accent); border-color: var(--border-strong); background: var(--panel3);
}

/* ---- 2. the fact strip ---- */
.sc-facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
  margin: 4px 0 14px; overflow: hidden;
}
.sc-fact { padding: 12px 16px; border-right: 1px solid var(--border); min-width: 0; }
.sc-fact:last-child { border-right: none; }
.sc-k {
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--faint);
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
}
.sc-fact-v { font-size: 14.5px; font-weight: 600; margin-top: 3px; overflow-wrap: anywhere; }
.sc-fact-v .sub { display: block; font-size: 12px; font-weight: 500; color: var(--muted); margin-top: 1px; }
@media (max-width: 900px) { .sc-fact { border-right: none; border-bottom: 1px solid var(--border); } }

/* ---- 3. the notes timeline ---- */
/* DENSE (owner, 2026-09-01: "the notes are WAY WAY WAY too big").
   The first cut gave a note 15px prose, 1.5 line-height, 13px of padding and a
   96px chip gutter — which is right for one note and absurd for eight, because
   six of them are one line long and the slice ate the screen before the machine
   itself got a look in. Same shape, a third of the height: the chip shrinks to
   what the word needs, the meta goes on the SAME line as the text where it
   fits, and the row breathes 7px instead of 13.

   What did NOT shrink is the pinned treatment. That one is a person saying
   "read this before you touch it", and he asked for it by name. */
/* IN THE RAIL NOW, so no chip gutter and no grid: the source is a tiny inline
   word ahead of the text, and 320px of column is plenty for a sentence. */
.sc-note { padding: 6px 14px; border-bottom: 1px solid var(--border); }
.sc-note:last-of-type { border-bottom: none; }
.sc-note:hover { background: var(--panel3); }
/* A pinned note is the one row allowed to shout. `inset` rather than a
   border-left: a border would shift the chip gutter and break the column
   alignment down the list. */
.sc-note.pinned, .sc-note.pinned:hover { background: var(--warn-bg); box-shadow: inset 3px 0 0 var(--warn); }
.sc-src {
  display: inline; margin-right: 6px;
  font-size: 10px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  border-radius: 3px; padding: 1px 5px;
  background: var(--chip); border: 1px solid var(--border); color: var(--muted);
  white-space: nowrap;
}
/* The source is a WORD, not a link (owner asked for exactly that): you are
   reading what happened to a machine, not navigating to a task. Colour tells
   the four kinds apart without spending a column on it. */
.sc-src.build  { background: var(--info-bg); border-color: var(--info-bd); color: var(--info); }
.sc-src.test   { background: var(--accent-wash); border-color: var(--accent-wash-bd); color: var(--accent); }
.sc-src.pick   { background: var(--ok-bg); border-color: var(--ok-bd); color: var(--ok); }
.sc-src.record { background: var(--chip); border-color: var(--border-strong); color: var(--muted); }
/* No `.sc-note-step`: the step a note was written at is detail for the task
   screen, not for a 320px rail. */
.sc-note-body { font-size: 13px; line-height: 1.4; overflow-wrap: anywhere; }
/* The byline reads UNDER the note here rather than floating beside it: at this
   width a floated byline steals half the first line and the prose wraps against
   it after four words. */
.sc-note-meta { font-size: 11px; color: var(--faint); margin-top: 1px; }
.sc-note-meta b { color: var(--muted); font-weight: 600; }
.sc-note-add { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; padding: 8px 14px; border-top: 1px solid var(--border); background: var(--panel3); }
.sc-note-add input[type="text"] { flex: 1 1 140px; min-width: 0; padding: 4px 9px; font-size: 13px; }
.sc-note-add .check { font-size: 12px; color: var(--muted); }
.sc-note-empty { padding: 10px 14px; font-size: 12.5px; }

/* The rail is a narrow column of small cards; a scrollbar on each one is noise.
   A long note wraps, it does not scroll sideways. */
.card.np.sc-notes { overflow-x: visible; }

/* Two stamps, day over clock — see stampCell. */
.sc-stamp { white-space: nowrap; font-variant-numeric: tabular-nums; }
.sc-stamp .muted { font-size: 12px; }

/* Shipping, when there is nothing to consign. */
.sc-ship-word { font-size: 14px; font-weight: 600; padding: 2px 0 6px; }

/* ---- 4. the assembly track ---- */
/* It was seven words in a row with dots, which at a glance is seven words. A
   filled track reads as progress before you read anything. */
.sc-track { display: flex; gap: 3px; padding: 14px 16px 0; }
.sc-seg { flex: 1; min-width: 0; }
.sc-seg-bar { height: 5px; border-radius: var(--r-pill); background: var(--panel2); }
.sc-seg.past .sc-seg-bar { background: var(--ok); }
.sc-seg.now .sc-seg-bar { background: var(--accent-deep); }
.sc-seg-k {
  display: block; margin-top: 6px; font-size: 11px; font-weight: 600;
  letter-spacing: 0.03em; color: var(--faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sc-seg.past .sc-seg-k { color: var(--muted); }
.sc-seg.now .sc-seg-k { color: var(--text); font-weight: 800; }
.sc-track-note { padding: 10px 16px 0; font-size: 12.5px; color: var(--muted); display: flex; gap: 14px; flex-wrap: wrap; }

/* ---- 5. the assembly table ---- */
/* ---- the assembly table, one line per row ----
   Every cell was two lines: name over badge, day over clock, number over bar.
   Five columns of that is a table twice the height it needs, on a card you read
   to see where a machine is up to. */
/* SPECIFICITY, DELIBERATELY. `.card.np td:nth-child(2|3|4)` above sets
   `white-space: normal` for the wide-table pages, and a bare `.sc-asm td` loses
   to it — which put the clock on a second line in Started and Completed and made
   the row 56px again after all this. Qualified with `.card.np table` so it wins
   on element count. */
.card.np table.sc-asm th, .card.np table.sc-asm td { padding: 6px 16px; white-space: nowrap; }
.card.np table.sc-asm td { font-size: 13.5px; }
.sc-task-n { font-weight: 700; }
/* A column where every cell is a link should not be a column of red. */
.sc-task-n a { color: inherit; }
.sc-task-n a:hover { color: var(--accent); }
/* The normal case is small grey words, not a pill. A pill that means "nothing is
   wrong" trains you to stop reading pills. */
.sc-task-state { font-size: 12px; color: var(--muted); }
.sc-run { font-size: 12px; font-weight: 600; color: var(--info); }
.card.np tr.sc-pending td { color: var(--faint); }
.card.np tr.sc-pending .sc-task-n { font-weight: 600; color: var(--muted); }
/* No `.sc-reentry` rule: the Assembly table is one row per task, full stop. A
   task worked twice sums its time into that one row — it does not grow a second
   one. (The FINISHED-build record, buildEpisodeHtml, does show a row per visit,
   and always has: that card is the device history, where each visit is the
   point.) */
/* No `.sc-who` / `.sc-pip`: the avatar circle beside every operator name set the
   row height on its own and held the two letters the name already starts with. */

/* The time column is the one number anybody compares down the page, so it keeps
   its bar — but as a 2px rule UNDER the number, inside the same line, rather
   than a block that adds a second one. Width is set inline: it is data. */
.sc-dur { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; position: relative; }
.sc-durbar {
  position: absolute; right: 16px; bottom: 3px; height: 2px;
  border-radius: var(--r-pill); background: var(--border-strong);
}

/* ---- 5b. copy a value out of the identity row ---- */
/* A SKU is meant to be TAKEN: it gets pasted into Shopify, a supplier email and
   a spreadsheet all day, and so is the name beside it. So each is a button that
   looks like the value, with the copy glyph appearing on hover — visible enough
   to invite the click, quiet enough not to read as a control in a row of plain
   facts. It reads at full value weight because it sits under the title now, not
   as a sub-line in a fact cell (2026-09-16). */
.sc-copy {
  font: inherit; font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums;
  color: var(--text); background: none; border: 0; padding: 0; margin: 0;
  cursor: pointer; display: inline-flex; align-items: center; gap: 5px; max-width: 100%;
  text-align: left; overflow-wrap: anywhere;
}
.sc-copy .ic { color: var(--faint); font-size: 11px; opacity: 0; transition: opacity 0.12s; }
.sc-copy:hover { color: var(--accent); }
.sc-copy:hover .ic, .sc-copy:focus-visible .ic { opacity: 1; }
@media (hover: none) { .sc-copy .ic { opacity: 1; } }

/* No `.sc-cs-warn`: the yellow panel that used to head the calls slice said, in
   a sentence, what the badge on the row under it says in a word. Removed
   2026-09-01. */

/* ---- 6. records: four lines, no chrome ---- */
.sc-rec { display: flex; justify-content: space-between; gap: 12px; align-items: baseline; padding: 4px 0; font-size: 13px; }
.sc-rec + .sc-rec { border-top: 1px solid var(--border); }
/* A button that reads as a link, because opening a document is navigation. */
.sc-rec-open {
  font: inherit; font-size: 13px; font-weight: 600; padding: 0; border: 0; background: none;
  color: var(--accent); cursor: pointer; text-align: left;
}
.sc-rec-open:hover { text-decoration: underline; }
.sc-rec-v { flex: 0 0 auto; text-align: right; }

/* ---- 7. testing information ---- */
/* A TABLE. The definition grid that was here carried a `.sc-def-n` explanation
   under every value — help text, who typed it, when — which is four extra lines
   of grey on a card you open to read four values. The help belongs where the
   value is ENTERED, not where it is read. */
.sc-answer { font-weight: 600; }
.sc-answer .muted { font-weight: 500; }
.sc-answer .sc-file { margin-right: 4px; }
.sc-file {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  font-size: 13px; font-weight: 600; text-decoration: none; color: var(--accent);
  background: var(--panel3); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 4px 9px;
}
.sc-file:hover { border-color: var(--border-strong); }
.sc-file .sz { color: var(--faint); font-weight: 500; font-variant-numeric: tabular-nums; }
/* Dashed, because it is a value that is being WITHHELD rather than displayed. */
.sc-secret {
  font-family: var(--mono); letter-spacing: 0.14em; font-size: 15px;
  background: var(--panel2); border: 1px dashed var(--border-strong);
  border-radius: var(--r-sm); padding: 3px 10px;
}

/* ---------------------------------------------------------------- service
   The ticket is a process, so the page reads top-to-bottom as one: a stage
   strip, then a card per phase. Deliberately NOT a three-column workspace —
   every other page here is table-first, and a 650px work area between two
   permanent rails would be the odd one out.                                */

.svc-stagebar { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 14px; }
.svc-stagebtn {
  display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px;
  border: 1px solid var(--border); border-radius: var(--r-pill); background: var(--panel);
  color: var(--muted); font-size: 13px; font-weight: 600; cursor: pointer;
}
.svc-stagebtn:hover { border-color: var(--border-strong); color: var(--text); }
.svc-stagebtn.active { background: var(--accent-wash); border-color: var(--accent); color: var(--accent); }
.svc-count { font-variant-numeric: tabular-nums; opacity: .8; }
tr.svc-late td:first-child { box-shadow: inset 2px 0 0 var(--bad); }
.svc-flags { display: inline-flex; gap: 4px; flex-wrap: wrap; }

/* The stage strip: where the machine is, and where it can go next. */
.svc-strip { display: flex; flex-wrap: wrap; gap: 4px; margin: 0 0 14px; }
.svc-step {
  display: inline-flex; align-items: center; gap: 7px; padding: 7px 13px 7px 9px;
  border: 1px solid var(--border); border-radius: var(--r); background: var(--panel);
  color: var(--muted); font-size: 13px; font-weight: 600; cursor: pointer;
}
.svc-step i {
  display: grid; place-items: center; width: 19px; height: 19px; flex: 0 0 auto;
  border-radius: 50%; border: 1px solid var(--border-strong);
  font-size: 10.5px; font-style: normal; font-variant-numeric: tabular-nums;
}
.svc-step:hover:not(:disabled) { border-color: var(--border-strong); color: var(--text); }
.svc-step.done { color: var(--ok); }
.svc-step.done i { background: var(--ok); border-color: var(--ok); color: #fff; }
.svc-step.now { background: var(--accent-wash); border-color: var(--accent); color: var(--accent); }
.svc-step.now i { background: var(--accent); border-color: var(--accent); color: #fff; }
.svc-step:disabled { cursor: default; opacity: .75; }

/* Card internals. */
.svc-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.svc-head h2 { margin: 0 0 2px; }
.svc-sub { margin: 0 0 7px; font-size: 12.5px; font-weight: 700; color: var(--muted); }
.svc-empty { margin-top: 10px; padding: 13px 14px; border: 1px dashed var(--border-strong); border-radius: var(--r); color: var(--muted); font-size: 13px; }

/* A movement that was taken back (0137). It stays on the table — the reason
   anybody reads this page later is to find the wrong part somebody fitted —
   but it must not read as work, so it recedes rather than shouting. Only the
   SKU is struck through: the columns beside it carry WHY it was taken back,
   and a line through the explanation helps nobody. */
.svc-struck > td { opacity: .6; }
.svc-struck > td:nth-child(2) { text-decoration: line-through; text-decoration-color: var(--muted); }

.svc-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 1px; margin-top: 12px; background: var(--border); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; }
.svc-facts > div { background: var(--panel3); padding: 10px 12px; }
.svc-facts span { display: block; font-size: 11.5px; color: var(--muted); margin-bottom: 4px; }
.svc-facts strong { font-size: 13px; font-weight: 600; line-height: 1.4; }

.svc-kv, .svc-perm { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; padding: 7px 0; border-bottom: 1px solid var(--border); font-size: 13px; }
.svc-kv > span, .svc-perm > span:first-child { color: var(--muted); }
.svc-kv > strong { font-weight: 600; text-align: right; min-width: 0; overflow-wrap: anywhere; }
.svc-perms { margin-bottom: 4px; }

.svc-note { margin-top: 12px; padding: 11px 13px; background: var(--panel3); border: 1px solid var(--border); border-radius: var(--r); font-size: 13px; line-height: 1.55; white-space: pre-wrap; }
.svc-quote { margin-top: 14px; padding: 12px 14px; background: var(--panel2); border-radius: var(--r); font-size: 13.5px; line-height: 1.6; white-space: pre-wrap; }
.svc-history { margin-top: 12px; font-size: 13px; }
.svc-history summary { cursor: pointer; color: var(--muted); }
.svc-mini { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 12.5px; }
.svc-mini th { text-align: left; color: var(--muted); font-weight: 600; padding: 5px 8px; background: var(--panel3); }
.svc-mini td { padding: 6px 8px; border-top: 1px solid var(--border); }

/* The release gate. */
/* One line of a parts basket. Boxed so four of them read as four decisions
   rather than one very long form. */
.op-line { border: 1px solid var(--border); border-radius: var(--r); padding: 12px 13px; margin-bottom: 10px; background: var(--panel3); }
.op-line-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; font-size: 12.5px; }
.op-lines .op-line:only-child .op-drop { display: none; }
/* Has the customer paid? One row, stated once, beside the release button that
   depends on it — never a banner repeating a fact the row already carries. */
.svc-pay { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap;
  margin-top: 12px; padding: 11px 13px; border-radius: var(--r); border: 1px solid var(--border); background: var(--panel3); font-size: 13px; }
.svc-pay.ok { background: var(--ok-bg); border-color: var(--ok-bd); }
.svc-pay.bad { background: var(--bad-bg); border-color: var(--bad-bd); }
.svc-qc { border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; }
.svc-check { display: flex; align-items: flex-start; gap: 10px; padding: 11px 13px; border-bottom: 1px solid var(--border); cursor: pointer; }
.svc-check:last-child { border-bottom: 0; }
.svc-check:hover { background: var(--panel3); }
.svc-check.on { background: var(--ok-bg); }
.svc-check input { width: 16px; height: 16px; margin-top: 2px; flex: 0 0 auto; accent-color: var(--ok); }
.svc-check span { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.svc-check b { font-size: 13px; font-weight: 600; }
.svc-check small { font-size: 11.5px; color: var(--muted); }
.svc-evi { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }

/* Evidence. */
.svc-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 10px; margin-top: 13px; }
.svc-evi-item { margin: 0; border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; background: var(--panel); }
.svc-evi-item.missing { border-style: dashed; opacity: .7; }
.svc-thumb { display: grid; place-items: center; height: 96px; background: var(--panel2); overflow: hidden; }
.svc-thumb img { width: 100%; height: 100%; object-fit: cover; }
.svc-kindmark, .svc-missing { font-size: 12px; color: var(--muted); font-weight: 600; letter-spacing: .04em; }
.svc-missing { color: var(--bad); }
.svc-evi-item figcaption { display: flex; flex-direction: column; gap: 2px; padding: 7px 9px; font-size: 11.5px; }
.svc-evi-item figcaption b { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.svc-drop { margin-top: 13px; padding: 15px; border: 1px dashed var(--border-strong); border-radius: var(--r); text-align: center; color: var(--muted); font-size: 12.5px; }
.svc-drop.over { border-color: var(--accent); background: var(--accent-wash); color: var(--accent); }

/* Activity, straight off the ledger. */
.svc-timeline { list-style: none; margin: 12px 0 0; padding: 0 0 0 14px; border-left: 1px solid var(--border); }
.svc-timeline li { position: relative; padding: 5px 0 5px 12px; font-size: 13px; }
.svc-timeline li::before { content: ""; position: absolute; left: -19px; top: 12px; width: 7px; height: 7px; border-radius: 50%; background: var(--faint); box-shadow: 0 0 0 3px var(--panel); }
.svc-timeline li.ok::before { background: var(--ok); }
.svc-timeline li.bad::before { background: var(--bad); }
.svc-when { display: inline-block; min-width: 140px; color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
.svc-who { float: right; font-size: 11.5px; }

/* A text button that lives inside a table cell or a checklist row. */
.btn-link { border: 0; background: none; padding: 0; color: var(--accent); font: inherit; font-size: 11.5px; font-weight: 600; cursor: pointer; }
.btn-link:hover { text-decoration: underline; }
.btn-link.danger { color: var(--bad); }

@media (max-width: 760px) {
  .svc-when { display: block; min-width: 0; }
  .svc-who { float: none; display: block; }
}

/* ==========================================================================
   PRODUCTION FLOOR (migration 0051) â€” the plan of the room, the task boards,
   the working screen for one task, and the numbers.

   The floor plan is the only place in the app that draws absolute positions:
   stations carry x/y/w/h in the database because a manager arranges them to
   match the real room. Everything else here is ordinary card/table furniture
   in the house style â€” the floor should not look like a different product.
   ========================================================================== */

.pf-pill { display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px; border-radius: var(--r-pill);
  font-size: 11.5px; font-weight: 700; background: var(--chip); border: 1px solid var(--border); color: var(--muted); white-space: nowrap; }
.pf-pill.ok   { background: var(--ok-bg);   border-color: var(--ok-bd);   color: var(--ok); }
.pf-pill.warn { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--warn); }
.pf-pill.bad  { background: var(--bad-bg);  border-color: var(--bad-bd);  color: var(--bad); }
.pf-pill.ghost { background: transparent; }
/* ============================================================== THE QUEUE
   Five tabs, one row per machine, grouped by promise date (owner, 2026-09-01).
   Everything here is the house kit — .card, .pf-filters, .pf-views, .btn — plus
   four things the kit did not have: a date-group band, an editable promise
   date, and the full-width parts row under a machine with its yes/no grid. */
.q-head { display: flex; align-items: center; gap: 14px; padding: 11px 16px;
  font-size: 12.5px; border-bottom: 1px solid var(--border); }
.q-stats { margin-left: auto; display: flex; gap: 16px; color: var(--muted); }
.q-stats b { color: var(--text); }
.pf-view .q-late { width: 6px; height: 6px; border-radius: 50%; background: var(--bad);
  display: inline-block; margin-left: 6px; vertical-align: 1px; }
table.q-list { table-layout: fixed; width: 100%; border-collapse: collapse; }
table.q-list th { font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); text-align: left; padding: 8px 14px; background: var(--panel3);
  border-bottom: 1px solid var(--border); }
table.q-list td { padding: 9px 14px; border-bottom: 1px solid var(--border); vertical-align: top; }
table.q-list td .sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* THE DATE BAND. A group heading is not a row of data, so it does not read like
   one: no hover, no cursor, and the overdue band carries the only red on the
   page that is not a word. */
tr.q-grp td { background: var(--panel3); padding: 5px 14px; font-size: 11.5px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
tr.q-grp.late td { background: var(--bad-bg); color: var(--bad); }
tr.q-mrow > td { border-bottom: 0; }
.q-when { font-weight: 700; }
.q-serial { font-weight: 700; }
.q-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 7px; vertical-align: 1px; }
.q-dot.ok { background: var(--ok); } .q-dot.warn { background: var(--warn); }
.q-dot.bad { background: var(--bad); } .q-dot.grey { background: var(--faint); }

/* The promise date, edited in place. The pencil rests invisible-ish and comes
   up on the row, so a column of them does not read as a column of buttons. */
.q-pen { font: inherit; font-size: 12px; line-height: 1; background: none; border: 0;
  padding: 1px 4px; margin-left: 2px; color: var(--faint); cursor: pointer; border-radius: var(--r-sm); }
tr:hover .q-pen { color: var(--accent); background: var(--accent-wash); }
/* Outside a table there is no row to hover, so the one on the floor task's
   detail card stays legible on its own — it is a single control beside a single
   date, not a column of them. */
.pf-kv .q-pen { color: var(--muted); }
.pf-kv .q-pen:hover { color: var(--accent); background: var(--accent-wash); }
.q-pen:focus-visible { outline: none; box-shadow: var(--ring); }

/* THE PARTS, as a full-width row under the machine they belong to. */
tr.q-parts > td { background: var(--panel3); padding: 0 14px 12px; }
.q-expand { font: inherit; font-size: 12.5px; font-weight: 700; color: var(--accent); background: none;
  border: 0; padding: 8px 2px; cursor: pointer; display: flex; align-items: center; gap: 7px; text-align: left; }
.q-expand .caret { font-size: 10px; transition: transform .12s; }
.q-expand.on .caret { transform: rotate(90deg); }
.q-expand .which { font-weight: 500; color: var(--muted); }
.q-grid { border: 1px solid var(--border); border-radius: var(--r); overflow: hidden;
  background: var(--panel); margin-bottom: 4px; }
.q-grid table { width: 100%; border-collapse: collapse; }
.q-grid th { background: var(--panel); font-size: 10.5px; padding: 6px 12px; }
.q-grid td { padding: 6px 12px; font-size: 12.5px; border-bottom: 1px solid var(--border); }
.q-grid tbody tr:last-child td { border-bottom: 0; }
.q-sku { font-weight: 700; }
/* An allocated part is the quiet case: the eye should land on the No. */
.q-no { color: var(--bad); font-weight: 700; }

.bad-text { color: var(--bad); }
.warn-text { color: var(--warn); }
.pf-who { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; background: var(--panel2);
  border: 1px solid var(--border); border-radius: var(--r-pill); padding: 3px 5px 3px 12px; }
.pf-check { display: flex; align-items: flex-start; gap: 9px; font-size: 13.5px; padding: 6px 0; cursor: pointer; }
.pf-check input { width: auto; margin-top: 2px; }
.pf-radios, .pf-checks { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; font-size: 13.5px; }
.pf-radios label, .pf-checks label { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.pf-radios input, .pf-checks input { width: auto; }
.banner.tight { padding: 8px 11px; font-size: 13px; margin-bottom: 8px; display: block; }
.banner.tight .sub { font-size: 12px; font-weight: 500; opacity: .85; margin-top: 2px; }

/* ---- the short-pick line on a system record ----
   Deliberately NOT a banner. That a machine is short is already said by the chip
   beside its serial and by its own Pick task row; a third red bar listing the
   same SKUs is noise. This is the detail behind them — one muted line, opened
   only by somebody who wants the numbers. (owner, 2026-08-26)                */
.pick-short { margin: 8px 14px 12px; font-size: 12.5px; }
.pick-short > summary { cursor: pointer; color: var(--muted); width: fit-content; }
.pick-short > summary:hover { color: var(--text); }
.pick-short-body { margin: 6px 0 0 15px; display: grid; gap: 3px; color: var(--muted); line-height: 1.5; }
.pick-short-body b { color: var(--text); font-weight: 600; }

/* ---- label / value row for prose (mfg.js kvl) ----
   `minmax(0,1fr)` lets the value column shrink; `overflow-wrap:anywhere` is what
   makes an email address or a Shopify order number WRAP inside it instead of
   running out through the side of a 300px card.                              */
.kvl { display: grid; grid-template-columns: 90px minmax(0, 1fr); gap: 10px; padding: 3px 0; font-size: 13px; }
.kvl > span:first-child { padding-top: 1px; white-space: nowrap; }
.kvl > span:last-child { min-width: 0; overflow-wrap: anywhere; line-height: 1.45; }
.kvl .mono { overflow-wrap: anywhere; }
.pf-acts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }

/* ---- the search that answers "where the hell is it" ---- */
.pf-find { padding: 14px 16px; margin-bottom: 14px; }
.pf-find-l { display: block; font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); margin-bottom: 5px; }
.pf-find input { font-size: 15px; }
.pf-find .sub { margin-top: 5px; }
.pf-hit { display: grid; grid-template-columns: minmax(0,1.6fr) minmax(0,1fr) minmax(0,1.2fr); gap: 10px; align-items: baseline;
  padding: 9px 10px; border: 1px solid var(--border); border-radius: var(--r); margin-top: 8px; background: var(--panel3); cursor: pointer; }
.pf-hit:hover { border-color: var(--accent); background: var(--accent-wash); }
.pf-hit .a { display: flex; gap: 7px; align-items: baseline; flex-wrap: wrap; }
.pf-hit .c { font-size: 12.5px; color: var(--muted); display: flex; gap: 7px; align-items: center; flex-wrap: wrap; }
.pf-hit.none { display: block; cursor: default; color: var(--muted); }
.pf-hit.none:hover { border-color: var(--border); background: var(--panel3); }
/* ---- one hall at a time (0142) ---- */
/* The building has more than one room and the plan draws one of them. The tabs
   sit above the finder because the finder answers for the WHOLE building —
   putting them under it would read as though it searched the hall on screen. */
.pf-halls { margin-bottom: 14px; }

.pf-kpis { margin-bottom: 4px; }
.pf-slotline { margin: 0 2px 14px; }

/* ---- the plan itself ---- */
.pf-canvas-wrap { overflow: auto; padding: 0; }
.pf-canvas { position: relative; background: var(--panel3); }
.pf-canvas.edit { background-image: linear-gradient(var(--border) 1px, transparent 1px), linear-gradient(90deg, var(--border) 1px, transparent 1px);
  background-size: 20px 20px; background-position: -1px -1px; background-color: var(--panel); }
.pf-st { position: absolute; display: flex; flex-direction: column; overflow: hidden;
  background: var(--panel); border: 1px solid var(--border-strong); border-radius: 10px; box-shadow: var(--shadow-sm); }
.pf-st .pf-head { display: flex; justify-content: space-between; align-items: center; gap: 6px; padding: 4px 8px;
  background: var(--panel2); border-bottom: 1px solid var(--border); font-size: 11px; font-weight: 800;
  text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.pf-st .pf-head .n { font-variant-numeric: tabular-nums; font-weight: 700; }
.pf-st .pf-body { flex: 1; padding: 6px 7px; overflow: hidden; display: flex; flex-direction: column; gap: 4px; }
.pf-st.busy { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-wash), var(--shadow-sm); }
.pf-st.wait { border-color: var(--warn-bd); }
.pf-st.bad  { border-color: var(--bad-solid); box-shadow: 0 0 0 2px var(--bad-bg), var(--shadow-sm); }
.pf-st.off  { opacity: .45; }
/* The head's colour is the KIND's, picked from a closed set on its row (0156).
   These used to be keyed off the kind itself (.rack, .shelf, .air, .qc), which
   meant a kind the owner invented drew in no colour at all. */
.pf-st.t-ok     .pf-head { background: var(--ok-bg);      color: var(--ok);           border-color: var(--ok-bd); }
.pf-st.t-info   .pf-head { background: var(--info-bg);    color: var(--info);         border-color: var(--info-bd); }
.pf-st.t-warn   .pf-head { background: var(--warn-bg);    color: var(--warn);         border-color: var(--warn-bd); }
.pf-st.t-bad    .pf-head { background: var(--bad-bg);     color: var(--bad);          border-color: var(--bad-bd); }
.pf-st.t-accent .pf-head { background: var(--accent-wash); color: var(--accent-hover); border-color: var(--accent-wash-bd); }
/* A MARKER is a name drawn on the plan, not a place: no fill, no shadow, a
   dashed outline. Was `.label` until the kind became a flag. */
.pf-st.marker { background: transparent; border-style: dashed; box-shadow: none; }
.pf-st.marker .pf-body { align-items: center; justify-content: center; color: var(--faint); font-weight: 700;
  text-transform: uppercase; letter-spacing: .09em; font-size: 11px; }
.pf-free { color: var(--faint); font-size: 11.5px; font-style: italic; }
.pf-occ { border-radius: 6px; padding: 4px 7px; background: var(--accent-wash); border: 1px solid var(--accent-wash-bd);
  line-height: 1.32; cursor: pointer; }
.pf-occ:hover { border-color: var(--accent); }
.pf-occ.bad { background: var(--bad-bg); border-color: var(--bad-bd); }
.pf-occ b { display: block; font-size: 11.5px; font-family: var(--mono); }
.pf-occ span { display: block; font-size: 10.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pf-occ .st { text-transform: uppercase; letter-spacing: .03em; font-weight: 700; font-size: 9.5px; }
.pf-slots { display: grid; grid-template-columns: repeat(var(--cols, 4), 1fr); gap: 3px; }
.pf-slot { border: 1px solid var(--border); border-radius: 5px; background: var(--panel3); padding: 2px 4px;
  min-height: 28px; font-size: 9.5px; line-height: 1.25; color: var(--faint); }
.pf-slot .n { display: block; font-variant-numeric: tabular-nums; }
.pf-slot b { font-family: var(--mono); font-size: 10px; color: var(--text); }
.pf-slot.busy { background: var(--accent-wash); border-color: var(--accent); cursor: pointer; }
.pf-slot.wait { background: var(--info-bg); border-color: var(--info-bd); cursor: pointer; }
.pf-slot.bad  { background: var(--bad-bg); border-color: var(--bad-bd); cursor: pointer; }
.pf-rsz { display: none; position: absolute; right: -1px; bottom: -1px; width: 14px; height: 14px;
  background: var(--accent); border-radius: 3px 0 9px 0; cursor: nwse-resize; }
.pf-canvas.edit .pf-st { cursor: move; }
.pf-canvas.edit .pf-st .pf-rsz { display: block; }
.pf-canvas.edit .pf-st.sel { outline: 2px solid var(--accent); outline-offset: 1px; }
.pf-toolbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; padding: 10px 14px; margin-bottom: 12px; }
.pf-toolbar select { width: auto; min-width: 150px; }
.pf-toolbar .spacer { flex: 1; }
/* The room's measurements. Full-width so the toolbar wraps it onto its own
   line — the floor is one edit, the benches on it are another. */
.pf-room { flex-basis: 100%; display: flex; align-items: center; gap: 7px; font-size: 12.5px;
  padding-top: 9px; margin-top: 3px; border-top: 1px dashed var(--border); }
.pf-room label { margin: 0; font-weight: 700; color: var(--faint); text-transform: uppercase; letter-spacing: .04em; }
.pf-room input { width: 84px; }
.pf-room .x { color: var(--faint); }
/* The far corner of the floor itself. Sits outside the last station so it can
   never be mistaken for one, and only exists while the layout is unlocked. */
.pf-room-grip { position: absolute; right: 0; bottom: 0; width: 26px; height: 26px; cursor: nwse-resize;
  background: linear-gradient(135deg, transparent 45%, var(--accent) 45%); border-bottom-right-radius: 3px;
  box-shadow: -1px -1px 0 0 var(--accent) inset; touch-action: none; z-index: 3; }
.pf-room-grip:hover { filter: brightness(1.25); }
.pf-legend { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12.5px; color: var(--muted); margin-top: 10px; align-items: center; }
.pf-legend .sw { width: 11px; height: 11px; border-radius: 3px; display: inline-block; margin-right: 5px; vertical-align: -1px; border: 1px solid var(--border-strong); }
.pf-legend .sw.free { background: var(--panel2); }
.pf-legend .sw.busy { background: var(--accent-wash); border-color: var(--accent); }
.pf-legend .sw.wait { background: var(--info-bg); border-color: var(--info-bd); }
.pf-legend .sw.bad  { background: var(--bad-bg); border-color: var(--bad-solid); }

/* ---- boards and lists ---- */
.pf-filters { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 14px; }
.pf-filters input[type=search] { max-width: 280px; }
.pf-filters select { width: auto; min-width: 150px; }
.pf-filters .spacer { flex: 1; }
.pf-lanes { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 10px; align-items: flex-start; }
.pf-lane { flex: 0 0 250px; background: var(--panel3); border: 1px solid var(--border); border-radius: var(--r-lg); }
.pf-lane.trouble { background: var(--bad-bg); border-color: var(--bad-bd); }
.pf-lane-h { display: flex; justify-content: space-between; align-items: center; padding: 9px 12px; border-bottom: 1px solid var(--border); }
.pf-lane-h b { font-size: 11.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.pf-lane-h .n { font-size: 12px; font-variant-numeric: tabular-nums; color: var(--muted); background: var(--chip);
  border-radius: var(--r-pill); padding: 0 7px; }
.pf-lane-body { padding: 9px; display: flex; flex-direction: column; gap: 8px; max-height: 62vh; overflow-y: auto; }
.pf-lane-empty { color: var(--faint); font-size: 12px; padding: 2px; }
/* A BOARD CARD IS TEXT, NOT A LINK (owner, 2026-09-01: "red, red, red, red, red").
   CS builds its cards as <a> (production and embellish use <div>), so they took
   `a { color: var(--accent) }` — and this app's accent IS the Aftershock red.
   Every card on that board therefore rendered its whole body in alarm colour,
   underlined, whether or not anything was wrong, which is why the board looked
   like a fire and the list of the same rows looked like nothing. Card text is
   card text; the alarms are the left edge and the due word. */
.pf-card { background: var(--panel); border: 1px solid var(--border); border-left: 4px solid var(--border-strong);
  border-radius: var(--r); padding: 9px 10px; cursor: pointer; box-shadow: var(--shadow-sm);
  color: var(--text); text-decoration: none; display: block; }
/* Lateness, said once, in words. Text — not a wash, not a pill, not a border. */
.late { color: var(--bad); font-weight: 600; }
.pf-card:hover { border-color: var(--accent); box-shadow: var(--shadow-md); }
.pf-card.ff-D { border-left-color: var(--info); }
.pf-card.ff-O { border-left-color: var(--warn); }
.pf-card.ff-L { border-left-color: var(--ok); }
.pf-card.bad  { border-left-color: var(--bad-solid); background: var(--bad-bg); }
/* The floor boards say "something is wrong here" with the wash above, because
   nothing else on those cards is red. A CS card already says it in words ("3
   days late"), so it marks the edge and stops. */
.pf-card.overdue { border-left-color: var(--bad-solid); }
.pf-card .r1 { display: flex; gap: 6px; align-items: baseline; justify-content: space-between; }
.pf-card .r2 { font-size: 13px; margin-top: 2px; }
.pf-card .r3 { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 7px; }
/* THE BOARD'S COLUMNS (owner directive, 2026-09-01: the columns were laying
   out badly).
   `table-layout: fixed` makes the <colgroup> widths binding instead of a
   suggestion, which is the whole fix: content stops deciding the layout.
   `.clip` ellipses rather than wraps (it needs the fixed layout to work);
   `.nowrap` protects the short, known-width cells. */
table.pf-cols { table-layout: fixed; }
table.pf-cols td.nowrap, table.pf-cols th.nowrap { white-space: nowrap; }
table.pf-cols td.clip { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A second line inside a clipped cell needs its own ellipsis: the cell's
   overflow would cut a long customer name mid-letter with no sign it did. */
table.pf-cols td.clip .sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
table.pf-list tbody tr { cursor: pointer; }
table.pf-list tbody tr:hover { background: var(--panel2); }
table.pf-list tbody tr.trouble { background: var(--bad-bg); }
/* The parts drawer: which SKUs a machine is waiting on, under its own row. The
   open row is tinted so a long table cannot lose track of whose drawer it is. */
table.pf-list tbody tr.open > td { background: var(--panel2); }
table.pf-list tbody tr.open.trouble > td { background: var(--bad-bg); }
table.pf-list tbody tr.pf-open { cursor: default; }
table.pf-list tbody tr.pf-open:hover { background: none; }
table.pf-list tbody tr.pf-open > td { background: var(--panel3); padding: 10px 14px 12px; }
.pf-cov { padding: 0; border: 0; background: none; cursor: pointer; font: inherit; }

/* ---- one task ---- */
.pf-steps { display: flex; gap: 5px; flex-wrap: wrap; margin-bottom: 16px; }
.pf-step { font-size: 11.5px; font-weight: 700; padding: 4px 11px; border-radius: var(--r-pill);
  background: var(--chip); border: 1px solid var(--border); color: var(--faint); }
.pf-step.done { background: var(--ok-bg); border-color: var(--ok-bd); color: var(--ok); }
.pf-step.cur  { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.pf-step.cur.bad { background: var(--bad-solid); border-color: var(--bad-solid); }
.pf-detail { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 16px; align-items: start; }
@media (max-width: 1100px) { .pf-detail { grid-template-columns: 1fr; } }
.pf-side .card { margin-bottom: 14px; }
.pf-side h3 { font-size: 13px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin-bottom: 9px; }
.pf-kv { display: grid; grid-template-columns: 108px minmax(0,1fr); gap: 5px 10px; font-size: 13.5px; }
/* The spec card (UL3): its second list, the embellishments, sits under a gap. */
.pf-spec .pf-kv + h3 { margin-top: 14px; }
.pf-kv > div:nth-child(odd) { color: var(--muted); font-size: 12.5px; }
.pf-move { display: flex; gap: 6px; margin-top: 12px; }
.pf-move select { flex: 1; min-width: 0; }
.pf-move input { width: 72px; }
.pf-tasklist { list-style: none; margin: 0; padding: 0; }
.pf-tasklist li { display: flex; justify-content: space-between; gap: 8px; align-items: center; padding: 5px 0;
  border-bottom: 1px dashed var(--border); font-size: 13.5px; }
.pf-tasklist li:last-child { border-bottom: 0; }
.pf-tasklist li.cur { font-weight: 700; }
/* EVERY NOTE ABOUT THE MACHINE, AT THE BENCH (2026-09-22). The system record's
   own note rows, reused, so a note reads the same at the bench as it does on the
   record. The negative side inset cancels `.sc-note`'s 14px gutter — that gutter
   exists for a `.card.np` with no padding of its own, and this list sits inside
   a padded card. */
.pf-notes { margin: 13px -21px 0; border-top: 1px solid var(--border); max-height: 360px; overflow-y: auto; }
.pf-notes .sc-note { padding: 7px 21px; }
.pf-log { list-style: none; margin: 0; padding: 0; max-height: 340px; overflow-y: auto; }
.pf-log li { display: flex; gap: 9px; padding: 6px 0; border-bottom: 1px dashed var(--border); font-size: 12.5px; }
.pf-log li:last-child { border-bottom: 0; }
.pf-log time { flex: 0 0 96px; color: var(--faint); font-size: 11.5px; }
.pf-scanbar { display: flex; gap: 10px; align-items: center; margin: 12px 0; }
.pf-scanbar input { max-width: 320px; font-size: 16px; font-family: var(--mono); }
table.pf-parts { margin-top: 6px; }
table.pf-parts tr.ok  td { background: var(--ok-bg); }
table.pf-parts tr.bad td { background: var(--bad-bg); }
/* WHAT KIND OF PART — "Cooling › Fans" beside the SKU. Deliberately NOT mono:
   it sits on the same line as the SKU, and the difference in face is what lets
   an eye scanning the SKU column skip straight past it. */
.pf-type { color: var(--muted); font-family: inherit; }
.pf-type::before { content: "·"; margin: 0 6px; opacity: 0.6; }
/* …unless it starts a line of its own, where a leading dot is just litter. */
.pf-type.bare::before { content: none; }
td.right, th.right { text-align: right; }
/* auto-FILL, and a ceiling on each tile: auto-fit stretched a lone packing
   photo to the card's full width at 4:3, which filled the screen (owner,
   2026-09-27). */
.pf-photos { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 240px)); gap: 12px; margin: 14px 0; }
.pf-photo { border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; background: var(--panel3); }
.pf-photo.has { border-color: var(--ok-bd); }
.pf-photo .ph { aspect-ratio: 4 / 3; display: grid; place-items: center; background: var(--panel2); color: var(--faint); font-size: 12.5px; }
.pf-photo .ph img { width: 100%; height: 100%; object-fit: cover; }
.pf-photo .ft { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 7px 9px; font-size: 12.5px; }
/* 0277 — a shot can hold several photos: each one is a thumbnail with its own remove. */
.pf-thumbs { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 9px 9px; }
.pf-thumb { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.pf-thumb img { width: 56px; height: 42px; object-fit: cover; border-radius: 4px; display: block; }
.pf-repicks { margin-top: 12px; }

/* ---- the multi-field modal (dialog.js only ships confirm and prompt) ---- */
.pf-modal { position: fixed; inset: 0; z-index: 90; background: var(--overlay); display: grid; place-items: center; padding: 20px; }
.pf-modal-box { background: var(--panel); border-radius: var(--r-lg); box-shadow: var(--shadow-lg);
  width: min(540px, 96vw); max-height: 88vh; overflow: auto; padding: 20px 22px; }
/* WIDER, for the ones with a SEARCH RESULT LIST in them. A picker row carries a
   SKU, a description, a stock pill and a cost pill, and at 540px the two pills
   squeezed the description to nothing — so the row said what was in stock and
   what it cost without saying what it was (owner, 2026-09-22). Still sized to
   its content, not to the page (R-UI-1): the plain modal stays 540. */
.pf-modal-box.wide { width: min(680px, 96vw); }
.pf-modal-box h2 { font-size: 18px; margin-bottom: 12px; }
.pf-modal-foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
.pf-modal-box .f { display: block; font-size: 11.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted); margin: 12px 0 4px; }

/* ---- dashboard and settings ---- */
.pf-grid2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(430px, 1fr)); gap: 14px; margin-bottom: 14px; align-items: start; }
@media (max-width: 900px) { .pf-grid2 { grid-template-columns: 1fr; } }
.pf-qual { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 10px; padding: 0 16px 14px; }
.pf-qual > div { background: var(--panel3); border: 1px solid var(--border); border-radius: var(--r); padding: 9px 11px; }
.pf-qual b { display: block; font-size: 21px; font-weight: 800; letter-spacing: -.02em; }
.pf-qual span { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.pf-ffblock { margin-top: 14px; }
.pf-ffblock h3 { font-size: 13.5px; margin-bottom: 6px; }
.pf-addrow { display: flex; gap: 6px; margin-top: 7px; }
.pf-addrow input { max-width: 260px; }
.pf-addrow select { max-width: 190px; }
/* A serial slot with no category (0086): the floor asks for it, the builder scans
   it, and the serial reaches no part. Amber, not red — the slot still works as a
   label, it just cannot be matched, and the fix is to re-add it with a category. */
.chip-sel.warn { border-color: var(--warn-bd); background: var(--warn-bg); }

/* ---- Production settings (0084): the route editor.
   One row per step: its number, its engine kind, its title, and the three
   controls that move or remove it. The number is deliberately a plain mono
   figure rather than a drag handle — a route is four to six steps long and
   arrows are unambiguous on a touchscreen at a bench. ---- */
/* A seventh column for the GAP (0122): the route's spacing moved here from the
   Promise dates page, so every step now carries "how many working days from its
   neighbour" beside its title. The build step shows the anchor glyph instead of
   a box, in the same column, so the numbers stay in one vertical line. */
.ps-step { display: grid; grid-template-columns: 24px minmax(148px, 172px) 1fr 4.2em auto auto auto; gap: 6px; align-items: center; margin-bottom: 6px; }
.ps-step-n { color: var(--faint); font-size: 12px; text-align: right; }
.ps-step-gap { text-align: center; }

/* The rank a jump buys sits INSIDE the Jump the queue card (0122, R-UI-2:
   controls live next to the thing they control). One row, the sentence taking
   the width and the select staying the width of the word in it. */
.pd-rank { display: flex; align-items: center; gap: 12px; margin: 0 12px 12px; padding: 9px 11px;
  border: 1px solid var(--line); border-radius: 6px; font-size: 12.5px; max-width: 80ch; }
.pd-rank span { flex: 1 1 auto; color: var(--muted); }
.pd-rank select { width: auto; min-width: 8em; margin: 0; padding: 5px 9px; font-size: 13px; }
.ps-step-anchor { color: var(--accent); text-align: center; font-size: 13px; }
.ps-anchor { display: flex; align-items: center; gap: 10px; margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line); font-size: 13px; }
.ps-anchor span { flex: 1 1 auto; }
.ps-anchor input { width: 5em; margin: 0; padding: 5px 9px; font-size: 13px; text-align: center; }
/* Shorter by PADDING, never by `height`. The base control is padding:10px 12px
   at 14px, which is ~40px tall intrinsically; forcing height:32px on top of that
   left every step's kind and title clipped through the middle of the text. */
.ps-step select, .ps-step input { margin: 0; padding: 5px 9px; font-size: 13px; line-height: 1.35; }
.ps-step .btn.sm { padding: 4px 8px; min-width: 30px; }
/* The vocabulary modals ask for four or five things at once, so they need more
   width than the app's 470px default — a one-field-per-modal settings screen is
   a settings screen nobody finishes using. */
.modal:has(.ps-form) { max-width: 620px; }
.ps-form { max-height: 62vh; overflow-y: auto; }
.ps-form .field { margin-bottom: 12px; }
table tbody tr.trouble td { background: var(--bad-bg); }


/* ---- per-day throughput. Three hues, one per task type; the bars stack so a
   day reads as one column and the mix is visible without a legend hunt. ---- */
.pf-days { display: flex; gap: 6px; align-items: flex-end; padding: 6px 16px 16px; overflow-x: auto; }
.pf-day { display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 26px; flex: 1 1 26px; }
.pf-day .bars { display: flex; flex-direction: column-reverse; justify-content: flex-start;
  width: 100%; height: 110px; border-radius: 4px 4px 0 0; overflow: hidden; background: var(--panel2); }
.pf-day .bars i { display: block; width: 100%; min-height: 0; }
.pf-day .bars i.build { background: var(--accent); }
.pf-day .bars i.test  { background: var(--info); }
.pf-day .bars i.pack  { background: var(--ok); }
.pf-day .n { font-size: 11.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.pf-day .d { font-size: 10px; color: var(--faint); white-space: nowrap; }
.pf-key { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin: 0 3px 0 8px; vertical-align: -1px; }
.pf-key.build { background: var(--accent); }
.pf-key.test  { background: var(--info); }
.pf-key.pack  { background: var(--ok); }

/* The repick history's weekly bars reuse .pf-days/.pf-day above; only the three
   problem colours are new. Wrong is the ordinary case (warn), faulty and
   missing are the ones that cost somebody a part or a walk. */
.pf-day .bars i.wrong   { background: var(--warn); }
.pf-day .bars i.faulty  { background: var(--bad); }
.pf-day .bars i.missing { background: var(--accent); }
.pf-key.wrong   { background: var(--warn); }
.pf-key.faulty  { background: var(--bad); }
.pf-key.missing { background: var(--accent); }


/* ---- saved views, active-filter chips, sortable list headers ---- */
.pf-views { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-bottom: 10px; }
.pf-view { font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; padding: 4px 12px;
  border: 1px solid var(--border-strong); background: var(--panel); color: var(--muted); border-radius: var(--r-pill); }
.pf-view:hover { background: var(--panel2); color: var(--text); }
.pf-view.on { background: var(--accent-wash); border-color: var(--accent); color: var(--accent-hover); }
.pf-view.saved { padding-right: 6px; display: inline-flex; align-items: center; gap: 4px; }
.pf-view .x { color: var(--faint); font-weight: 700; padding: 0 4px; border-radius: var(--r-sm); }
.pf-view .x:hover { color: var(--bad); background: var(--bad-bg); }
.pf-view.add { border-style: dashed; color: var(--faint); }
.pf-view.add:hover { color: var(--accent); border-color: var(--accent); background: var(--panel); }
.pf-view-sep { width: 1px; align-self: stretch; background: var(--border); margin: 0 2px; }
/* The count a pill carries. Search's type row uses these; the assembly tabs
   inline the same treatment. A zero-count pill is shown, not hidden — "no
   invoices matched" and "invoices weren't searched" are different sentences. */
.pf-view .n { margin-left: 6px; opacity: .7; font-weight: 700; }
.pf-view.muted { cursor: default; opacity: .45; }
.pf-view.muted:hover { background: transparent; color: var(--muted); }

.pf-chips { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin: 0 0 12px; min-height: 26px; }
.pf-chips .sub b { color: var(--text); }

.pf-sort { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.pf-sort select { width: auto; min-width: 180px; }

th.pf-th { cursor: pointer; user-select: none; white-space: nowrap; }
th.pf-th:hover { color: var(--text); background: var(--panel2); }
th.pf-th.on { color: var(--accent); }


/* ---- Assembly overview (#/assembly) ---- */
/* Five tiles across; the strip collapses long before the phones do. */
.kpi-row.ao-kpis { grid-template-columns: repeat(5, 1fr); }
@media (max-width: 1200px) { .kpi-row.ao-kpis { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .kpi-row.ao-kpis { grid-template-columns: 1fr; } }
/* The RMA pipeline's five stages (0113). Same 5-up shape as the assembly
   overview, named for its own page so neither can be re-tuned by accident. */
.ao-kpis .kpi-value { font-size: 28px; }

/* The line: nodes flow left to right and wrap into a column on a phone. */
.ao-flow { display: flex; align-items: stretch; gap: 0; margin: 8px 0 0; overflow-x: auto; padding-bottom: 4px; }
.ao-node { flex: 1 1 0; min-width: 128px; display: block; background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: 11px 13px; box-shadow: var(--shadow-sm); color: inherit; text-decoration: none; }
a.ao-node:hover { border-color: var(--accent); }
.ao-node.ghost { background: var(--panel3); border-style: dashed; }
.ao-node.done { background: var(--ok-bg); border-color: var(--ok-bd); }
.ao-node.done .ao-n { color: var(--ok); }
.ao-t { font-size: 11.5px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
.ao-n { font-family: var(--sans); font-size: 26px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.15; margin: 1px 0 2px; }
.ao-s { font-size: 11.5px; color: var(--muted); line-height: 1.45; }
.ao-old { font-size: 10.5px; color: var(--faint); margin-top: 5px; }
.ao-arrow { display: grid; place-items: center; padding: 0 5px; color: var(--faint); flex: none; }
@media (max-width: 900px) {
  .ao-flow { flex-direction: column; }
  .ao-arrow { transform: rotate(90deg); padding: 2px 0; }
}

/* Embellish runs beside the line, not in it. */
.ao-rail { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 8px 0 0; padding: 9px 13px;
  background: var(--panel3); border: 1px dashed var(--border-strong); border-radius: var(--r-lg);
  font-size: 12.5px; color: var(--muted); text-decoration: none; }
.ao-rail:hover { border-color: var(--accent); }
.ao-rail b { color: var(--text); }
.ao-rail-n { font-size: 16px; }

.ao-2col { display: grid; grid-template-columns: 1.25fr 1fr; gap: 14px; margin-top: 14px; align-items: start; }
.ao-3col { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; margin: 14px 0; align-items: start; }
@media (max-width: 1100px) { .ao-2col, .ao-3col { grid-template-columns: 1fr; } }

.ao-row { cursor: pointer; }
.ao-flags { display: flex; flex-direction: column; }
.ao-flag { display: flex; gap: 10px; align-items: flex-start; padding: 9px 16px; border-top: 1px solid var(--border);
  font-size: 13px; color: inherit; text-decoration: none; }
.ao-flag:hover { background: var(--panel3); }
.ao-flag .pf-pill { flex: none; margin-top: 1px; }
.ao-flag-what { display: flex; flex-direction: column; gap: 1px; }

.ao-pills { display: flex; gap: 6px; flex-wrap: wrap; }
.ao-bars { display: grid; gap: 7px; margin-top: 10px; }
.ao-bar { display: grid; grid-template-columns: 76px 1fr 34px; gap: 8px; align-items: center; font-size: 12px; color: var(--muted); }
.ao-track { height: 8px; background: var(--panel2); border-radius: 4px; overflow: hidden; }
.ao-fill { height: 100%; background: var(--accent); border-radius: 4px; }
.ao-bar b { text-align: right; color: var(--text); }
.ao-floor { font-size: 14px; margin-bottom: 4px; }

/* ---- "Go to the part" arrow (leftmost column of a parts table) ---- */
/* The SKU used to be the link, which read as if clicking it acted on this
   machine's part. It doesn't — it leaves for the catalogue. So the departure
   gets its own narrow column, quiet until you reach for it. */
th.go-h { width: 26px; padding-right: 0; }
td.go-c { width: 26px; padding-right: 0; text-align: center; }
a.go {
  display: inline-grid; place-items: center;
  width: 20px; height: 20px;
  border-radius: var(--r-sm);
  color: var(--faint); font-size: 13px; line-height: 1;
  text-decoration: none;
}
a.go:hover, a.go:focus-visible { color: var(--accent); background: var(--panel2); }
tr:hover a.go { color: var(--muted); }
tr:hover a.go:hover { color: var(--accent); }

.q-row { cursor: pointer; }
.q-row .twist { color: var(--faint); font-size: 11px; }
.q-row.open { background: var(--panel2); }
table.q-sub { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; font-size: 13px; }
table.q-sub th { background: var(--panel2); font-size: 10.5px; padding: 6px 12px; }
table.q-sub td { padding: 7px 12px; }

/* The at-risk board: quiet when nothing is late, loud when something is. */

/* Twelve controls is a lot of bar. They get a well of their own and a tighter
   minimum width, so the row wraps into a block instead of a straggling line. */

/* Draft cards: a coverage bar, the tubs it holds, and one verb. The card is
   wide enough for the tub table because the table is the point — a card that
   only says "8 machines" cannot be released from, and the supervisor was
   opening every list to find out what was in it. */
.q-lists { display: grid; grid-template-columns: repeat(auto-fill, minmax(520px, 1fr)); gap: 12px; }
.q-list-body { max-height: 320px; overflow: auto; border-top: 1px solid var(--border); }
table.q-sub.tubs { border: none; border-radius: 0; width: 100%; }
table.q-sub.tubs th { position: sticky; top: 0; z-index: 1; }
table.q-sub.tubs tr.short td { background: var(--warn-bg); }
table.q-sub.tubs .xs { font-size: 11px; }
table.q-sub.tubs .bad { color: var(--bad); }
table.q-sub.tubs .nowrap { white-space: nowrap; }
@media (max-width: 620px) { .q-lists { grid-template-columns: 1fr; } }
.q-list { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--shadow-sm); overflow: hidden; }
.q-list.full { border-color: var(--ok-bd); }
.q-list-head { display: flex; justify-content: space-between; gap: 10px; align-items: flex-start; padding: 12px 15px 10px; }
.q-list-head h3 { font-size: 15.5px; font-weight: 800; margin: 0; }
.q-cov { height: 6px; background: var(--panel2); }
.q-cov i { display: block; height: 100%; background: var(--ok); }
.q-list-foot { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; padding: 11px 15px; border-top: 1px solid var(--border); background: var(--panel3); }


/* Centred table cell — the type-flag checkboxes on Categories → Types (0073).
   Everything else the embellishment screens need already existed: the queue is
   pf-lanes/pf-card like every floor board, the run page is the q-list card the
   draft pick lists use, and the artwork panel is card/card-head/pf-kv. The first
   cut of this feature shipped ~75 lines of parallel emb-* rules instead, which is
   why it looked like a different product. */
td.c, th.c { text-align: center; }

/* The builder lives INSIDE the product form, revealed by its switch — so a rule
   is a bordered block on the form, not a card on a page of its own. */
.custom-field[hidden] { display: none; }
.tt-trigger { border: 1px solid var(--border-strong); border-radius: var(--r-lg); padding: 15px 16px; margin-top: 11px; background: var(--panel3); }
.tt-empty { border: 1px dashed var(--border-strong); border-radius: var(--r); padding: 14px 16px; color: var(--muted); font-size: 13px; }
.tt-head { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; }
.tt-head b { font-size: 14.5px; }
.tt-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; }
.tt-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 13px; padding: 9px 12px;
  background: var(--panel3); border: 1px solid var(--border); border-radius: var(--r); }
.tt-line-step { font-size: 12.6px; font-weight: 600; color: var(--muted); background: var(--panel);
  border: 1px solid var(--border); border-radius: var(--r-sm); padding: 3px 9px; }
.tt-line-step.new { background: var(--accent); border-color: var(--accent); color: #fff; }
.tt-line-arrow { color: var(--muted); font-size: 11px; }
.tt-check { display: flex; align-items: center; gap: 8px; padding: 7px 9px; margin-top: 6px;
  background: var(--panel3); border: 1px solid var(--border); border-radius: var(--r); }
.tt-check .tt-check-label { flex: 1; min-width: 140px; }
.tt-flag { display: inline-flex; align-items: center; gap: 5px; font-size: 12.3px; color: var(--muted); white-space: nowrap; }
.tt-move, .tt-x { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 14px; line-height: 1; padding: 2px 4px; }
.tt-move:hover, .tt-x:hover { color: var(--text); }
.tt-x:hover { color: var(--bad); }
@media (max-width: 620px) { .tt-check { flex-wrap: wrap; } }

/* The checks as they read on a task page: one row per line, answered or not. */
.tt-checkrow { display: flex; align-items: center; gap: 10px; padding: 9px 11px; margin-top: 6px;
  background: var(--panel); border: 1px solid var(--border-strong); border-radius: var(--r); }
.tt-checkrow.done { background: var(--ok-bg); border-color: var(--ok-bd); }
.tt-checkrow.na { background: var(--panel2); }
.tt-checkrow .tt-tick { width: 22px; height: 22px; border-radius: var(--r-sm); border: 1.5px solid var(--border-strong);
  display: grid; place-items: center; font-size: 12px; flex: none; background: var(--panel); }
.tt-checkrow.done .tt-tick { background: var(--ok); border-color: var(--ok); color: #fff; }
.tt-checkrow .sub { font-size: 11.8px; color: var(--muted); }

/* The read-only view on the product's page: the same lines, no controls. */
.tt-read { border-top: 1px solid var(--border); padding-top: 11px; margin-top: 11px; }
.tt-read:first-child { border-top: none; padding-top: 0; margin-top: 0; }
.tt-read-h { display: flex; align-items: center; gap: 8px; font-size: 13.6px; margin-bottom: 7px; }
.tt-read-checks { margin: 8px 0 0; padding-left: 20px; font-size: 12.8px; color: var(--muted); }
.tt-read-checks li { margin-bottom: 3px; }

/* ================================================ PRODUCT PAGE (rebuilt 0085)
   Owner brief 2026-08-18: the page was scattered and wordy, its headings were
   getting lost and nothing lined up. It was to be tightened and split into two
   even halves, so that long or short lists leave no void gaps.

   THE THREE RULES THAT MAKE A VOID IMPOSSIBLE, and they are worth stating
   because every previous attempt at this page was a hand-tuned guess:

     1. `.pd-split` is TWO EQUAL HALVES with `align-items: stretch`, so both
        halves are always exactly the same height. Not "about the same" —
        identical, by grid.
     2. Each half holds exactly ONE greedy card (`.pd-grow`, `flex: 1`): cost
        layers on the left, the ledger on the right. Whatever slack the other
        half's content creates lands in that card's LIST. Spare height becomes
        rows you can read instead of black panel.
     3. Each of those lists scrolls inside a ceiling (`.pd-scroll`, 68vh), so the
        opposite failure — 400 cost layers dragging the page to 6000px — cannot
        happen either.

   The halves are balanced on their FIXED content too (record ≈ specs, the
   replenishment/compat pair ≈ stock by location), so in the extreme where BOTH
   lists sit at the ceiling the two columns still finish within a row of each
   other.

   Headings: same 13px/700/uppercase the rest of the app uses, but in INK rather
   than --muted and above a hairline. Eight muted headings on one page is what
   made the old version read as headless. */
.pd-split { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: stretch; }
.pd-col { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.pd-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 1080px) { .pd-split, .pd-pair { grid-template-columns: 1fr; } }

/* Every card on this page is a flex column so its footer sinks to the bottom
   edge — a gap ABOVE a footer is the same defect as a gap below a card. */
.pd-card { display: flex; flex-direction: column; padding: 0; margin: 0; overflow: hidden; }
.pd-grow { flex: 1; min-height: 0; }
.pd-scroll { flex: 1; min-height: 0; overflow-y: auto; max-height: 68vh;
  scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
.pd-scroll::-webkit-scrollbar { width: 10px; }
.pd-scroll::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 10px; border: 3px solid var(--panel); }
.pd-scroll::-webkit-scrollbar-track { background: transparent; }
/* A day header and a table head both stay put while their list moves. */
.pd-scroll .pd-day, .pd-scroll thead th { position: sticky; top: 0; z-index: 1; }

.pd-head { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 11px 15px; border-bottom: 1px solid var(--border); min-height: 43px; }
/* Was 13px UPPERCASE — the local copy of the eyebrow the base `.card h2` rule
   stopped drawing. These heads are only 43px tall, so the title runs one
   notch under the app default rather than the full 15. */
.pd-head h2 { margin: 0; font-size: 14px; font-weight: 700; color: var(--text); letter-spacing: -0.004em; text-transform: none; }
.pd-meta { display: flex; align-items: center; gap: 9px; font-size: 12px; color: var(--muted);
  font-variant-numeric: tabular-nums; }
.pd-meta b { color: var(--text); font-weight: 600; }
.pd-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: auto; padding: 9px 15px; border-top: 1px solid var(--border);
  background: var(--panel3); font-size: 12px; color: var(--muted); }
.pd-warn { padding: 8px 15px; border-bottom: 1px solid var(--warn-bd); background: var(--warn-bg);
  color: var(--text); font-size: 12.5px; }
.pd-blank { padding: 13px 15px; font-size: 13px; color: var(--muted); line-height: 1.5; }
.pd-blank p { margin: 0 0 10px; }
.pd-blank p:last-child { margin-bottom: 0; }
.pd-blank .btn { margin-top: 2px; }
.pd-stats { margin-bottom: 14px; }
.pd-stats .stat-card { padding: 12px 15px 11px; }
.pd-stats .stat-num { font-size: 26px; }
.pd-stats .stat-num.ok { color: var(--ok); }
.pd-stats .stat-num.neg { color: var(--bad); }

/* COMPATIBILITY, AS TWO TABLES, under Specs on the product page (2026-09-26).
   Cin7's own words: ATTR, then REQ · REC · HIDE · AGGR. Every cell is a
   searchable dropdown (forms.js searchableSelect) whose list FLOATS, so this
   card must not clip — which is why it is not a `.card.np` (that scrolls
   sideways and cuts a floating list in half) and overrides `.pd-card`'s
   overflow. The tables are fixed-layout and fit the column instead. */
.cpt { overflow: visible; }
.cpt-sec { border-bottom: 1px solid var(--border); }
.cpt-sub { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 15px 7px; }
.cpt-sub h3 { margin: 0; font-size: 12.5px; font-weight: 700; color: var(--text); letter-spacing: 0.02em; }
.cpt-sub h3 span { color: var(--muted); font-weight: 500; margin-left: 6px; letter-spacing: 0; }
.cpt-t { width: 100%; table-layout: fixed; border-collapse: collapse; }
.cpt-t thead th { padding: 7px 6px; font-size: 11px; }
.cpt-t td { padding: 4px 6px; border-bottom: 0; border-top: 1px solid var(--border); vertical-align: middle; font-size: 13.5px; }
.cpt-t th:first-child, .cpt-t td:first-child { padding-left: 15px; }
.cpt-t th:last-child, .cpt-t td:last-child { padding-right: 10px; }
.cpt-attrs col.c-key { width: 52%; }
.cpt-rules col.c-type { width: 78px; }
.cpt-rules col.c-key { width: 36%; }
.cpt-rules col.c-op { width: 70px; }
.cpt-t col.c-x { width: 36px; }
.cpt-t input, .cpt-t select, .cpt-t .combo-input { width: 100%; min-width: 0; padding: 5px 8px; font-size: 13.5px; }
.cpt .combo-list { min-width: 240px; }
.cpt-t tr.chg td { background: var(--warn-bg); }
.cpt-t tr.bad td { background: var(--bad-bg); }
.cpt-none td { color: var(--muted); font-size: 13px; padding: 9px 15px; }
.cpt-x { border: 0; background: none; color: var(--faint); cursor: pointer; font-size: 17px; line-height: 1;
  padding: 3px 6px; border-radius: var(--r-sm); width: auto; }
.cpt-x:hover { color: var(--bad); background: var(--bad-bg); }
.cpt-num { display: flex; align-items: center; gap: 6px; }
.cpt-num input { flex: 1; }
.cpt-num span { color: var(--muted); font-size: 12.5px; }
.cpt-multi { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.cpt-multi .combo { flex: 1 1 90px; min-width: 90px; }
.cpt-pick { display: inline-flex; align-items: center; gap: 2px; padding: 1px 3px 1px 8px; font-size: 12.5px;
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-pill); }
.cpt-pick button { border: 0; background: none; color: var(--faint); cursor: pointer; font-size: 14px; line-height: 1;
  padding: 1px 4px; width: auto; border-radius: var(--r-pill); }
.cpt-pick button:hover { color: var(--bad); }
.cpt-err { color: var(--bad); font-size: 12.5px; padding: 6px 15px 9px; }
.cpt-footwrap { margin-top: auto; }
.cpt-footwrap > * { border-radius: 0 0 var(--r-lg) var(--r-lg); }
.cpt-tags summary { cursor: pointer; padding: 9px 15px; font-size: 12.5px; color: var(--muted); }
.cpt-tags summary b { color: var(--text); font-weight: 600; margin-left: 4px; }
.cpt-tags summary span { margin-left: 9px; }
.cpt-chips { display: flex; flex-wrap: wrap; gap: 5px; padding: 0 15px 12px; }
.cpt-chip { font-family: var(--mono); font-size: 11.5px; background: var(--panel2); border: 1px solid var(--border);
  border-radius: var(--r-pill); padding: 1px 8px; }
.cpt-chip.new { background: var(--ok-bg); border-color: var(--ok-bd); color: var(--ok); }
/* Categories → Compatibility keys (0309). */
.ckey-bar { max-width: 440px; margin-bottom: 10px; }
.ckey-bar .search { width: 100%; }
.ckey-desc { font-size: 12.5px; margin-top: 2px; }
.ckey-vals { font-size: 13px; max-width: 46ch; }
.ckey-modal { max-width: 520px; }
.ckey-fixed { padding: 8px 0 2px; font-size: 14px; }
.ckey-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 7px; }
.ckey-chips .chip-sel .muted { font-size: 11.5px; margin-left: 3px; }
.ckey-chips .ckey-new { border-color: var(--ok-bd); background: var(--ok-bg); }
.ckey-x { border: 0; background: none; color: var(--faint); cursor: pointer; font-size: 14px; line-height: 1; padding: 1px 4px; width: auto; }
.ckey-x:hover { color: var(--bad); }

/* ---- Two columns of label → value. This is the squish: sixteen facts in eight
   rows instead of sixteen, and the empty half of them behind one line. ---- */
.pd-fields { display: grid; grid-template-columns: 1fr 1fr; }
.pd-fields.pd-one { grid-template-columns: 1fr; }
@media (max-width: 620px) { .pd-fields { grid-template-columns: 1fr; } }
.pd-f { display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  margin: 0; padding: 4px 15px; font-size: 13px; border-bottom: 1px solid var(--border); }
/* The vertical rule is drawn by the LEFT cell of each row. Safe here because
   nothing spans this grid — add a full-width header and this parity breaks. */
.pd-fields > .pd-f:nth-child(odd) { border-right: 1px solid var(--border); }
.pd-fields.pd-one > .pd-f:nth-child(odd) { border-right: none; }
.pd-f:hover { background: var(--panel3); }
.pd-f dt { color: var(--muted); margin: 0; }
.pd-f dd { margin: 0; text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }
.pd-q { color: var(--faint); font-weight: 500; }
.pd-f.unset dd { color: var(--faint); font-weight: 500; }
/* "9 empty — barcode, weight, supplier… Show" is one row where nine "—" were. */
.pd-more { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%;
  font: inherit; font-size: 12px; text-align: left; cursor: pointer; color: var(--muted);
  background: var(--panel3); border: none; border-top: 1px solid var(--border); padding: 9px 15px; }
.pd-more:hover { color: var(--text); }
.pd-more b { color: var(--accent); font-weight: 600; }

/* ---- The two lists ---- */
.pd-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.pd-table thead th { color: var(--muted); font-weight: 700; font-size: 10.5px; letter-spacing: 0.05em;
  text-transform: uppercase; text-align: left; background: var(--panel3);
  border-bottom: 1px solid var(--border); padding: 6px 15px; }
.pd-table td { padding: 5px 15px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.pd-table tbody tr:last-child td { border-bottom: none; }
.pd-table tbody tr:hover { background: var(--panel3); }
.pd-table td.r, .pd-table th.r { text-align: right; }
.pd-table td.nowrap { white-space: nowrap; }
.pd-rank { font-size: 10.5px; font-weight: 700; color: var(--faint); font-variant-numeric: tabular-nums; }
.pd-wh { font-size: 10.5px; font-weight: 700; letter-spacing: 0.05em; color: var(--muted);
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 0 5px; }
/* The layer the next issue draws from. FIFO means something on this page. */
.pd-table tr.pd-next, .pd-table tr.pd-next:hover { background: var(--accent-wash); }
.pd-table tr.pd-next .pd-rank { color: var(--accent); }
.pd-pill { font-size: 9.5px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--accent); border: 1px solid var(--accent-wash-bd); border-radius: var(--r-sm);
  padding: 0 5px; margin-left: 7px; }

.pd-day { font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted); padding: 5px 15px; background: var(--panel3);
  border-bottom: 1px solid var(--border); }
.pd-mv { display: grid; grid-template-columns: 52px 1fr 62px; gap: 12px; align-items: baseline;
  padding: 6px 15px; border-bottom: 1px solid var(--border); font-size: 13px; }
.pd-mv:hover { background: var(--panel3); }
.pd-mv .t { color: var(--faint); font-size: 12px; font-variant-numeric: tabular-nums; }
.pd-mv .what { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; min-width: 0; }
.pd-mv .what b { font-weight: 600; }
.pd-mv .what span { color: var(--muted); font-size: 12.5px; }
.pd-mv .qty { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
.pd-mv[data-dir="in"] .qty, .pd-mv[data-dir="in"] .pd-chev { color: var(--ok); }
.pd-mv[data-dir="out"] .qty, .pd-mv[data-dir="out"] .pd-chev { color: var(--accent); }
.pd-chev { font-style: normal; font-weight: 800; font-size: 11px; flex: none; }
.pd-seg .seg-btn { padding: 2px 9px; font-size: 11.5px; font-weight: 700; }

/* ---- Stock by location: three cells, no bars (owner directive: the bars were
   not wanted). ---- */
.pd-locs { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.pd-lc { padding: 8px 15px; border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.pd-lc .nm { font-size: 12.5px; font-weight: 600; display: flex; align-items: baseline; gap: 6px; }
.pd-lc .nm em { font-style: normal; font-size: 10px; font-weight: 700; letter-spacing: 0.05em; color: var(--faint); }
.pd-lc .row { display: flex; gap: 12px; flex-wrap: wrap; font-size: 12px; color: var(--muted);
  font-variant-numeric: tabular-nums; margin-top: 1px; }
.pd-lc .row b { color: var(--text); font-weight: 700; font-size: 14.5px; }
.pd-lc .row .z b { color: var(--warn); }
.pd-lc .row .neg b { color: var(--bad); }

/* ================================================ SPECIFICATIONS (0085)
   The questions a category asks, and a product's answers. Two surfaces: the
   spec-field editor on the Categories page, and the fill-in form. */
.sf-req { color: var(--accent); font-size: 9.5px; vertical-align: super; margin-left: 2px; }
.sf-inh { color: var(--faint); font-size: 11px; margin-left: 4px; font-weight: 600; }
.sf-flag { font-style: normal; font-size: 9.5px; font-weight: 800; letter-spacing: 0.03em;
  padding: 1px 5px; border-radius: 4px; border: 1px solid var(--border);
  background: var(--panel2); color: var(--faint); }
.sf-flag.on { background: var(--accent-wash); border-color: var(--accent-wash-bd); color: var(--accent); }

.sf-row { display: grid; grid-template-columns: minmax(130px, 1.25fr) 96px minmax(0, 1.5fr) 96px 54px auto;
  gap: 11px; align-items: center; padding: 7px 15px; border-bottom: 1px solid var(--border); font-size: 13px; }
.sf-row:hover { background: var(--panel3); }
.sf-row.hd { background: var(--panel3); font-size: 10.5px; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--muted); }
.sf-row.off { opacity: 0.62; }
.sf-name { font-weight: 600; display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; min-width: 0; }
/* The permanent key, said quietly. It is the thing that makes renaming safe, so
   it is visible — but it is not what the row is ABOUT. */
.sf-name em { font-style: normal; font-size: 11px; font-weight: 500; color: var(--faint);
  font-variant-numeric: tabular-nums; letter-spacing: 0.01em; }
.sf-type { font-size: 10.5px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase;
  color: var(--info); background: var(--info-bg); border: 1px solid var(--info-bd);
  border-radius: var(--r-sm); padding: 1px 6px; width: max-content; }
.sf-type.num { color: var(--ok); background: var(--ok-bg); border-color: var(--ok-bd); }
.sf-type.bool { color: var(--warn); background: var(--warn-bg); border-color: var(--warn-bd); }
.sf-accepts { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sf-accepts b { color: var(--text); font-weight: 600; }
.sf-flags { display: flex; gap: 4px; justify-content: flex-end; }
.sf-fill { text-align: right; font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.sf-acts { display: flex; gap: 5px; justify-content: flex-end; }
.sf-acts .btn { padding: 3px 9px; font-size: 12px; }
/* ---- Search results: every matching question, across every category. The
   grid deliberately echoes .sf-row so a hit reads as the same object it will be
   when you land on it, with the owning category taking the first column. ---- */
.sf-hit-head { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 15px; border-bottom: 1px solid var(--border); background: var(--panel3);
  font-size: 13px; color: var(--muted); }
.sf-hit-head b { color: var(--text); }
.sf-hit { display: grid; grid-template-columns: minmax(120px, 1fr) minmax(120px, 1.15fr) 96px minmax(0, 1.3fr) 54px;
  gap: 11px; align-items: center; padding: 7px 15px; border-bottom: 1px solid var(--border);
  font-size: 13px; cursor: pointer; }
.sf-hit:hover, .sf-hit:focus-visible { background: var(--panel3); outline: none; }
.sf-hit.off { opacity: 0.62; }
.sf-hit-where { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sf-hit-where b { color: var(--text); font-weight: 600; }
/* The question you searched for, once you are looking at its category. */
.sf-row.hit { background: var(--accent-wash); box-shadow: inset 3px 0 0 var(--accent); }
.sf-row.hit:hover { background: var(--accent-wash); }
/* ---- The sheet is nested: one section per category in the chain, outermost
   first, each indented a step further. The indent means what it means in the
   tree beside it — distance from the root — so a question's owner is read off
   its position instead of a parenthesis at the end of a flat list. ---- */
.sf-grp { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
  padding: 9px 15px 8px; border-bottom: 1px solid var(--border); background: var(--panel3);
  font-size: 12.5px; color: var(--muted); cursor: pointer; }
.sf-grp.own { background: var(--accent-wash); cursor: default; }
.sf-grp:not(.own):hover, .sf-grp:not(.own):focus-visible { background: var(--panel2); outline: none; }
.sf-grp-name { font-size: 13.5px; font-weight: 700; color: var(--text); }
.sf-grp.own .sf-grp-name { color: var(--accent); }
.sf-grp-tag { font-size: 10px; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--faint); border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; }
.sf-grp.own .sf-grp-tag { color: var(--accent); border-color: var(--accent-wash-bd); }
.sf-grp-n { font-variant-numeric: tabular-nums; }
/* Only offered on hover: it is the way out of this sheet, not an invitation. */
.sf-grp-go { margin-left: auto; font-size: 12px; color: var(--accent); opacity: 0; }
.sf-grp:hover .sf-grp-go, .sf-grp:focus-visible .sf-grp-go { opacity: 1; }
/* An inherited row is legible but plainly not yours to edit here. */
.sf-row.inh .sf-name { font-weight: 500; }
.sf-foot { display: flex; gap: 18px; flex-wrap: wrap; padding: 9px 15px; border-top: 1px solid var(--border);
  background: var(--panel3); font-size: 11.5px; color: var(--muted); }
.sf-foot span { display: inline-flex; align-items: baseline; gap: 6px; }
.sf-dialog { max-height: 62vh; overflow-y: auto; margin: 4px -4px 0; padding: 0 4px; }
.sf-dialog .field:last-child { margin-bottom: 4px; }

/* ---- The fill-in form. One row per question, the note under the input. ---- */
.sp-form { padding: 4px 0; }
.sp-row { display: grid; grid-template-columns: minmax(160px, 1fr) minmax(220px, 1.2fr);
  gap: 10px 18px; align-items: baseline; padding: 9px 19px; border-bottom: 1px solid var(--border); }
.sp-row:last-child { border-bottom: none; }
.sp-row > label { font-size: 13.5px; font-weight: 600; color: var(--text); }
.sp-hint { grid-column: 2; font-size: 12px; color: var(--muted); margin-top: -4px; }
.sp-hint:empty { display: none; }
.sp-in input, .sp-in select { width: 100%; }
.sp-unit { display: flex; align-items: center; gap: 8px; }
.sp-u { font-size: 13px; color: var(--muted); white-space: nowrap; }
.sp-multi { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.sp-chk { display: inline-flex; align-items: center; gap: 6px; font-size: 13.5px; }
.sp-chk input { width: auto; }
@media (max-width: 720px) { .sp-row { grid-template-columns: 1fr; } .sp-hint { grid-column: 1; } }


/* ======================================================== FLOOR DASHBOARD
   The wall screen: who did what, what the queues look like, what is overdue.
   One chart language for the whole page — each task KIND owns one hue
   everywhere (pick/build/test/pack/embellish), queue STATES wear the bench's
   own status tokens, and single-series bars wear their section's hue. The
   five kind hues are a CVD-validated categorical set (adjacent-pair ΔE ≥ 8
   under protan/deutan simulation on both surfaces; the dark column is the
   same hues re-stepped for the dark ground, not a second palette). Light
   mode leaves three hues under 3:1 against white — the relief is that every
   mark carries a direct label, which is also just how a wall is read. */
.fd {
  --fd-pick: #2a78d6;    /* slot 1 · blue    — pick / drawn */
  --fd-build: #eb6834;   /* slot 2 · orange  — build */
  --fd-test: #1baf7a;    /* slot 3 · aqua    — testing */
  --fd-pack: #eda100;    /* slot 4 · yellow  — packing */
  --fd-emb: #e87ba4;     /* slot 5 · magenta — embellish */
  --fd-neutral: var(--faint);
  font-variant-numeric: tabular-nums;
}
:root[data-theme="dark"] .fd {
  --fd-pick: #3987e5;
  --fd-build: #d95926;
  --fd-test: #199e70;
  --fd-pack: #c98500;
  --fd-emb: #d55181;
}

.fd-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 6px; }
.fd-head h1 { margin: 0 0 2px; }

.fd-sec { display: flex; align-items: baseline; gap: 12px; margin: 22px 2px 10px; }
.fd-sec h2 { margin: 0; font-size: 15px; letter-spacing: 0.02em; }
.fd-sec a { font-size: 12.5px; color: var(--muted); text-decoration: none; }
.fd-sec a:hover { color: var(--accent); }

.fd-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 12px; align-items: stretch; }
.fd-grid > div { min-width: 0; display: grid; }
/* Grid items refuse to shrink below their content unless told twice: once on
   the span wrapper, once on the card inside it. */
.fd-grid .card { min-width: 0; }
.fd-grid .span2 { grid-column: span 2; }
.fd-grid .span3 { grid-column: span 3; }
.fd-grid .span4 { grid-column: span 4; }
@media (max-width: 1240px) {
  .fd-grid .span2 { grid-column: span 3; }
  .fd-grid .span3 { grid-column: span 4; }
  .fd-grid .span4 { grid-column: span 6; }
}
@media (max-width: 760px) {
  .fd-grid .span2 { grid-column: span 6; }
  .fd-grid .span3 { grid-column: span 6; }
  .fd-grid .span4 { grid-column: span 12; }
}

/* ---- stat tiles ---- */
.fd-tile { padding: 14px 16px; display: flex; flex-direction: column; gap: 2px; justify-content: flex-start; }
.fd-tile-label { font-size: 12px; font-weight: 600; color: var(--muted); }
.fd-tile-value { font-size: 33px; font-weight: 800; letter-spacing: -0.01em; line-height: 1.15; }
.fd-tile-sub { font-size: 11.5px; color: var(--faint); }
.fd-tile.warn .fd-tile-value { color: var(--warn); }
.fd-tile.bad .fd-tile-value { color: var(--bad); }

/* ---- charts ---- */
.fd-chart { padding: 13px 15px 11px; display: flex; flex-direction: column; }
.fd-chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.fd-chart-head h3 { margin: 0; font-size: 13px; font-weight: 700; }
.fd-note { font-size: 11.5px; color: var(--faint); }
.fd-empty { color: var(--faint); font-size: 12.5px; padding: 26px 0 30px; text-align: center; flex: 1; display: grid; place-items: center; }

/* Bars: thin marks, rounded data end, direct label above, name below.
   The dashed line is the mean of the bars shown. */
.fd-plot { position: relative; display: flex; align-items: flex-end; gap: 7px; height: 168px; padding-top: 16px; flex: 1; }
.fd-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; gap: 3px; }
.fd-col > i { display: block; width: 100%; max-width: 34px; border-radius: 4px 4px 0 0; }
.fd-val { font-size: 11px; font-weight: 700; line-height: 1; }
.fd-name {
  font-size: 10.5px; color: var(--muted); line-height: 1.05;
  height: 30px; width: 100%; overflow: hidden;
  transform: rotate(-33deg) translateY(9px); transform-origin: top center;
  text-align: center; white-space: nowrap; text-overflow: ellipsis;
}
.fd-name.flat { transform: none; height: 13px; text-align: center; color: var(--faint); font-size: 10px; overflow: visible; }
.fd-avgline { position: absolute; left: 0; right: 0; border-top: 2px dashed color-mix(in srgb, var(--bad-solid) 68%, transparent); pointer-events: none; z-index: 1; }
.fd-avgline span {
  position: absolute; right: 0; top: -17px;
  font-size: 10px; font-weight: 700; color: var(--muted);
  background: color-mix(in srgb, var(--panel) 82%, transparent); padding: 1px 4px; border-radius: 4px;
}

/* Histograms: slimmer columns, stacked segments split by a 2px surface gap. */
.fd-plot.histo { gap: 3px; height: 150px; }
.fd-plot.histo .fd-col { gap: 2px; }
.fd-plot.histo .fd-col > * { max-width: none; }
.fd-stack { display: flex; flex-direction: column; justify-content: flex-end; width: 100%; height: 100%; gap: 2px; }
.fd-stack i { display: block; width: 100%; border-radius: 2px; min-height: 2px; }
.fd-stack i:first-child { border-radius: 4px 4px 2px 2px; }
.fd-chips { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 8px; }
.fd-chips span { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--muted); }
.fd-chips i { width: 9px; height: 9px; border-radius: 3px; }

/* Donuts: the total lives in the hole; the legend carries names AND counts,
   so identity is never asked of color alone. */
.fd-donut { display: grid; grid-template-columns: 118px 1fr; gap: 6px 14px; align-items: center; position: relative; flex: 1; }
.fd-donut svg { width: 118px; height: 118px; grid-row: 1; }
.fd-donut svg circle { cursor: default; }
.fd-hole { position: absolute; inset: 0 auto 0 0; width: 118px; text-align: center; display: flex; flex-direction: column; justify-content: center; pointer-events: none; }
.fd-hole b { font-size: 25px; font-weight: 800; line-height: 1.05; }
.fd-hole span { font-size: 10px; color: var(--faint); }
.fd-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.fd-legend li { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--muted); min-width: 0; }
.fd-legend li > i { width: 9px; height: 9px; border-radius: 3px; flex: none; }
.fd-legend li > b { margin-left: auto; color: var(--text); font-weight: 700; }

/* ---- the shared tooltip: an ink slip that follows the pointer ---- */
.fd-tip {
  position: fixed; z-index: 90; pointer-events: none;
  background: var(--ink-btn); color: var(--ink-btn-ink);
  font-size: 12px; font-weight: 600; line-height: 1.3;
  padding: 5px 9px; border-radius: 7px; box-shadow: var(--shadow-md);
  max-width: 300px;
}

/* ---- a figure opens the machines behind it (A15-A20) ----
   Every tile, bar, legend line and day column on the wall and on Performance
   is a link to the list it counted. They keep their own look; the pointer and
   a quiet hover are what say they open. */
a.fd-tile, a.fd-col, .fd-legend a, .fd-chart-head h3 a, a.fd-note,
a.kpi, a.pf-day, .pf-qual > a, a.pf-plain { color: inherit; text-decoration: none; }
a.fd-tile:hover, a.kpi:hover, .pf-qual > a:hover { border-color: var(--accent); }
a.fd-col:hover .fd-name, a.fd-col:hover .fd-val, .fd-legend a:hover, .fd-chart-head h3 a:hover,
a.fd-note:hover, a.pf-day:hover .n, a.pf-plain:hover { color: var(--accent); }
.fd-legend a { display: flex; align-items: center; gap: 7px; min-width: 0; width: 100%; }
.fd-legend a > i { width: 9px; height: 9px; border-radius: 3px; flex: none; }
.fd-legend a > b { margin-left: auto; color: var(--text); font-weight: 700; }
.fd-donut svg a circle { cursor: pointer; }
.pf-qual > a { display: block; background: var(--panel3); border: 1px solid var(--border); border-radius: var(--r); padding: 9px 11px; }
/* Performance's daily bars stack every kind now, picks and prints included. */
.pf-day .bars i.pick, .pf-key.pick { background: var(--warn); }
.pf-day .bars i.embellish, .pf-key.embellish { background: #e87ba4; }
:root[data-theme="dark"] .pf-day .bars i.embellish, :root[data-theme="dark"] .pf-key.embellish { background: #d55181; }

/* ---- order reviews (H13 0287; tasks under Customer service since 0307) ----
   Built from the app's own parts (owner, 2026-09-26: match the rest of the
   app): the masthead, a .card.np table, .cols cards, the notes list. A status
   is a .badge in its vocabulary's st-* tone; these few lines only make the
   clickable one a button and lay the review's two columns out. */
button.badge.rv-st { cursor: pointer; font-family: inherit; gap: 4px; }
.card.np td .badge { white-space: nowrap; }
.rv-line + .rv-line { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
button.badge.rv-st:hover { filter: brightness(0.95); }
button.badge.rv-st svg { width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-width: 2.4;
                         stroke-linecap: round; stroke-linejoin: round; opacity: .7; }
.page-head h1 .badge { vertical-align: middle; }
.card.np td.rv-why { min-width: 24ch; max-width: 46ch; }
.rv-main { flex: 2 1 460px; min-width: 0; max-width: 760px; }
.cols > .card.rv-side { flex: 0 1 300px; min-width: 260px; }
.rv-side .kvl { align-items: center; }
.rv-side select, .rv-side input[type="date"] { width: 100%; }
.note-list .note.sys .note-body-read { color: var(--muted); }
.rv-statuses { max-width: 760px; }
.rv-channels { display: flex; flex-direction: column; gap: 6px; margin: 4px 0; }
.fu-grp { display: flex; align-items: baseline; gap: 10px; padding: 10px 16px 6px; font-size: 12.5px; font-weight: 700;
  color: var(--warn); border-top: 1px solid var(--border); text-transform: uppercase; letter-spacing: .04em; }
.fu-grp .n { color: var(--muted); font-weight: 600; }
.fu-grp a { margin-left: auto; font-weight: 600; text-transform: none; letter-spacing: 0; }
tr.fu-grp-row > td { padding: 0; }

/* ---- a machine stands at a station (A12-A14, 0289) ----
   Numbered slots only where a station turns them on; a machine there with no
   slot is listed under the grid, and a station past its size warns in amber. */
.pf-loose { margin-top: 6px; font-size: 11px; color: var(--muted); }
.pf-st.over .pf-head .n { color: var(--warn); font-weight: 800; }
.wiz-exbtn.full small { color: var(--warn); }

/* ---- Spec filters on the products browser (0085) ----
   A second filter row that only exists once a category is chosen, because a spec
   question belongs to a shelf. Tinted so it reads as "this shelf's questions"
   rather than as more of the same generic filters above it. */
.pf-specs { border-top: 1px solid var(--border); background: var(--panel2); }
.pf-specs .browse-f span { display: flex; align-items: baseline; gap: 5px; }
.pf-unit { font-style: normal; font-size: 11px; color: var(--faint); font-weight: 600; }

/* ── Promise dates (0091/0092) ─────────────────────────────────────────────
   Owner directive 2026-08-20: "the ui is taking up all available space, make
   smaller cards. 3 wide."

   Every rule on that page is a handful of numbers with a worked example beside
   them, and a handful of numbers does not need the full width of a monitor. So:
   a fixed 3-across grid of dense cards, each one readable at a glance and
   comparable with its neighbours — which is the actual job, because "is stock
   really slower than custom?" is a question you answer by looking sideways.

   Fixed 3, not auto-fit: auto-fit would give 4 or 5 on a wide screen and the
   route rows inside would stop being legible. Two below 1200px, one below 760. */
.pd-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; align-items: start; }
@media (max-width: 1200px) { .pd-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 760px)  { .pd-grid { grid-template-columns: 1fr; } }

/* Tighter than .card on every axis: this is a settings tile, not a document. */
.pd-card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 11px 12px 10px; display: flex; flex-direction: column; gap: 9px; min-width: 0; }
.pd-card .pd-title { display: flex; justify-content: space-between; align-items: baseline; gap: 6px; }
.pd-card .pd-title b { font-size: 13.5px; }
.pd-card .pd-title .mono { font-size: 10.5px; color: var(--muted); }
.pd-card .pd-sub { font-size: 11.5px; color: var(--muted); line-height: 1.45; margin-top: -4px; }

/* The two lead-time fields, side by side. Labels wrap above tiny inputs rather
   than sitting inline, because at a third of the width inline labels squeeze the
   input down to two characters. */
.pd-num { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.pd-num > span { font-size: 10.5px; color: var(--muted); line-height: 1.3; text-transform: uppercase;
  letter-spacing: .03em; font-weight: 600; }
.pd-num > input, .pd-num > select { width: 100%; min-width: 0; font-family: var(--mono); font-size: 12.5px;
  padding: 4px 6px; }

/* The route, as rows rather than a table: name · gap · the date it produces.
   A <table> at this width forces a horizontal scrollbar inside the card. */
.pd-route { display: flex; flex-direction: column; gap: 1px; background: var(--border);
  border-radius: var(--r-sm); overflow: hidden; }
.pd-step { display: grid; grid-template-columns: 1fr 3.6em 5.4em; gap: 7px; align-items: center;
  background: var(--panel2); padding: 4px 7px; font-size: 12px; }
.pd-step .pd-step-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The route is READ-ONLY on this page since 0122 — its spacing is edited in
   Production settings, next to the route itself — so the gap renders as text in
   the same column the input used to hold. The input rule stays for the shape. */
.pd-step .pd-dash { font-family: var(--mono); }
.pd-step input { width: 100%; min-width: 0; text-align: center; font-family: var(--mono); font-size: 12px;
  padding: 2px 3px; }
.pd-step .pd-step-date { font-family: var(--mono); font-size: 11px; color: var(--muted); text-align: right; }
.pd-step.is-anchor { background: var(--panel3); }
.pd-step.is-anchor .pd-step-name { font-weight: 600; }
.pd-step .pd-dash { text-align: center; color: var(--muted); font-size: 11px; }

/* The one-line verdict at the foot of a rule card. */
.pd-foot { font-size: 11.5px; line-height: 1.45; color: var(--muted); }
.pd-foot.bad { color: var(--bad); }


/* ============================================================== INSIGHTS
   The lakehouse's front door. One chart language, and it is not the floor's:
   there, a task KIND owns a hue; here, a kind of MONEY does. Revenue is blue,
   cost is orange, margin is green, stock value is violet — on every page, in
   both themes, so a colour learned on the dashboard still means the same thing
   three pages later. Declared on `.ins` rather than :root so this palette and
   .fd's cannot drift into each other.

   The four hues are a CVD-checked categorical set (adjacent-pair ΔE ≥ 8 under
   protan/deutan on both grounds). Identity is never asked of colour alone:
   every chart carries a legend, every mark a tooltip, every figure a label. */
.ins {
  --ins-revenue: #2a78d6;   /* money coming in */
  --ins-cost: #eb6834;      /* money the parts cost */
  --ins-margin: #0f9d6b;    /* what we kept */
  --ins-stock: #7a5bd6;     /* money asleep on a shelf */
  --ins-any: var(--accent-deep);
  font-variant-numeric: tabular-nums;
}
:root[data-theme="dark"] .ins {
  --ins-revenue: #3987e5;
  --ins-cost: #d95926;
  --ins-margin: #17b87e;
  --ins-stock: #8a6ee0;
}

/* Doubt goes first, always — the digest's own rule, borrowed for the screen. */
.ins-doubt { margin: 0 0 12px; }
.ins-doubt a { margin-left: 8px; font-weight: 600; }

/* ---- the window picker: presets AND free-form dates, never one alone ---- */
.ins-window { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 0 0 12px; }
.ins-seg { display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--r-sm); overflow: hidden; }
.ins-seg button {
  border: 0; border-left: 1px solid var(--border); background: var(--panel);
  color: var(--muted); font-size: 12.5px; font-weight: 600; padding: 6px 11px; cursor: pointer;
}
.ins-seg button:first-child { border-left: 0; }
.ins-seg button:hover { background: var(--panel2); color: var(--text); }
.ins-seg button.on { background: var(--accent-wash); color: var(--accent); }
.ins-dates { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--muted); }
.ins-dates input { font-size: 12.5px; }

/* ---- controls + the sentence under them ---- */
.ins-controls-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 14px; }
.ins-controls-row label { display: flex; flex-direction: column; gap: 3px; font-size: 11.5px; font-weight: 600; color: var(--muted); }
.ins-controls .ins-window { margin: 0; }
/* A SENTENCE, never a bare number: "214 models with margin in 22 May – 20 Aug". */
.ins-sentence { font-size: 12px; color: var(--faint); margin-top: 8px; }

/* ---- tiles ---- */
.ins-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; margin: 0 0 14px; }
.ins-tile { padding: 13px 15px; display: flex; flex-direction: column; gap: 3px; }
.ins-tile-label { font-size: 12px; font-weight: 600; color: var(--muted); }
.ins-tile-value { font-size: 27px; font-weight: 800; letter-spacing: -0.01em; line-height: 1.15; }
.ins-tile-sub { font-size: 11px; color: var(--faint); line-height: 1.35; }
.ins-tile.warn .ins-tile-value { color: var(--warn); }
.ins-tile.bad .ins-tile-value { color: var(--bad); }

/* ---- the 12-column grid, same breakpoints as the floor's ---- */
.ins-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 12px; align-items: stretch; }
.ins-grid > div { min-width: 0; display: grid; }
.ins-grid .card { min-width: 0; }
.ins-grid .span3 { grid-column: span 3; }
.ins-grid .span4 { grid-column: span 4; }
.ins-grid .span6 { grid-column: span 6; }
.ins-grid .span8 { grid-column: span 8; }
.ins-grid .span9 { grid-column: span 9; }
@media (max-width: 1240px) {
  .ins-grid .span3 { grid-column: span 4; }
  .ins-grid .span4 { grid-column: span 6; }
  .ins-grid .span8, .ins-grid .span9 { grid-column: span 12; }
}
@media (max-width: 760px) {
  .ins-grid .span3, .ins-grid .span4, .ins-grid .span6 { grid-column: span 12; }
}

/* ---- charts ---- */
.ins-chart { padding: 13px 15px 11px; display: flex; flex-direction: column; }
.ins-chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 6px; }
.ins-chart-head h3 { margin: 0; font-size: 13px; font-weight: 700; }
.ins-note { font-size: 11px; color: var(--faint); text-align: right; max-width: 58%; }
.ins-empty { color: var(--faint); font-size: 12.5px; padding: 34px 0; text-align: center; flex: 1; display: grid; place-items: center; }
.ins-svg { width: 100%; height: auto; display: block; overflow: visible; }
.ins-grid-l { stroke: var(--border); stroke-width: 1; }
.ins-axis { fill: var(--faint); font-size: 10.5px; font-variant-numeric: tabular-nums; }
/* An unmeasured stretch is DRAWN, because "we did not look" and "it was zero"
   are different facts and a chart that blurs them is worse than no chart. */
.ins-gap { fill: var(--faint); opacity: 0.09; }
/* A figure the engine does not believe wears a ring, and the ring carries the
   engine's own sentence on hover. */
.ins-suspect { fill: var(--panel); stroke: var(--warn); stroke-width: 2.5; }
.ins-legend { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 7px; }
.ins-legend span { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--muted); }
.ins-legend i { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.ins-legend i.dash { height: 0; border-top: 2px dashed currentColor; border-radius: 0; opacity: 0.7; }
.ins-legend i.hatch { background: var(--faint); opacity: 0.28; }

/* ---- horizontal bars: a league table that happens to be drawn ---- */
.ins-bars { display: flex; flex-direction: column; gap: 5px; padding-top: 2px; }
.ins-bar-row { display: grid; grid-template-columns: minmax(90px, 190px) 1fr auto; align-items: center; gap: 10px; }
.ins-bar-row[role="button"] { cursor: pointer; }
.ins-bar-row[role="button"]:hover .ins-bar-label { color: var(--accent); }
.ins-bar-label { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ins-bar-track { background: var(--panel2); border-radius: 4px; height: 15px; overflow: hidden; }
.ins-bar-track i { display: block; height: 100%; border-radius: 4px; }
.ins-bar-val { font-size: 12px; font-weight: 700; min-width: 76px; text-align: right; }

/* ---- the definition panel: the owner's "super clear variables" ask ---- */
.ins-def { padding: 14px 16px; }
.ins-def h3 { margin: 0 0 7px; font-size: 13px; font-weight: 700; }
.ins-def p { margin: 0 0 10px; font-size: 12.5px; line-height: 1.55; color: var(--text); }
.ins-def dl { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; margin: 0; font-size: 12px; }
.ins-def dt { color: var(--muted); font-weight: 600; }
.ins-def dd { margin: 0; color: var(--text); min-width: 0; overflow-wrap: anywhere; }

/* Plan against actual, side by side — the model lens's whole argument. */
.ins-cmp { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
.ins-cmp > div { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; font-size: 12.5px; }
.ins-cmp span { color: var(--muted); }
.ins-cmp b { font-size: 16px; font-weight: 800; }
.ins-creep-bad { margin: 0; font-size: 12.5px; color: var(--warn); line-height: 1.5; }
.ins-creep-ok { margin: 0; font-size: 12.5px; color: var(--ok); line-height: 1.5; }

/* ---- the catalogue: one expandable row per variable ---- */
.ins-cat { padding: 0; margin-bottom: 7px; }
/* Freight-service sections on Fulfilment → Settings (0180): ONE minimisable
   <details> per registered engine, carrying its own carrier list and its own
   credentials card. Native collapse — the same shape as ins-cat above. */
.fsvc { padding: 0; margin-bottom: 7px; }
.fsvc > summary {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 13px 16px; cursor: pointer; list-style: none;
}
.fsvc > summary::-webkit-details-marker { display: none; }
.fsvc > summary:hover { background: var(--panel2); border-radius: var(--r); }
.fsvc-h { font-size: 15px; font-weight: 800; }
.fsvc-count { flex: 1 1 auto; color: var(--muted); }
.fsvc-body { padding: 0 16px 14px; border-top: 1px solid var(--border); }
.ins-cat > summary {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 11px 14px; cursor: pointer; list-style: none;
}
.ins-cat > summary::-webkit-details-marker { display: none; }
.ins-cat > summary:hover { background: var(--panel2); }
.ins-cat-title { font-size: 13px; font-weight: 700; }
.ins-cat-basis { font-size: 11.5px; color: var(--faint); flex: 1 1 260px; min-width: 0; }
.ins-cat-body { padding: 0 14px 13px; border-top: 1px solid var(--border); }
.ins-cat-body p { margin: 10px 0; font-size: 12.5px; line-height: 1.55; }
.ins-cat-body dl { display: grid; grid-template-columns: auto 1fr; gap: 3px 12px; margin: 0 0 10px; font-size: 12px; }
.ins-cat-body dt { color: var(--muted); font-weight: 600; }
.ins-cat-body dd { margin: 0; overflow-wrap: anywhere; }
.ins-cat-where { font-size: 12.5px; }

/* ---- digest figures ---- */
.ins-figs { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 10px; }
.ins-fig { display: flex; flex-direction: column; gap: 2px; padding: 10px 12px; background: var(--panel2); border-radius: var(--r-sm); }
.ins-fig-title { font-size: 11.5px; font-weight: 600; color: var(--muted); }
.ins-fig b { font-size: 19px; font-weight: 800; }
.ins-fig-delta { font-size: 11px; color: var(--faint); }
.ins-fig-basis { font-size: 10.5px; color: var(--faint); line-height: 1.35; }
.ins-missing { margin: 8px 0 0; padding-left: 18px; font-size: 12.5px; line-height: 1.6; color: var(--muted); }
.ins-missing b { color: var(--text); }

.ins-foot { font-size: 11.5px; color: var(--faint); margin: 14px 2px 0; line-height: 1.5; }
.ins tr.on td { background: var(--accent-wash); }
.ins tr.clickable { cursor: pointer; }
.ins tr.clickable:hover td { background: var(--panel2); }
.ins tr.warn-row td { background: color-mix(in srgb, var(--warn-bg) 55%, transparent); }

/* The shared tooltip — same ink slip the floor uses, scoped to this module. */
.ins-tip {
  position: fixed; z-index: 90; pointer-events: none;
  background: var(--ink-btn); color: var(--ink-btn-ink);
  font-size: 12px; font-weight: 600; line-height: 1.3;
  padding: 5px 9px; border-radius: 7px; box-shadow: var(--shadow-md); max-width: 320px;
}

/* ============================================================================
   ACCOUNTS PAYABLE (0094) — supplier bills, order currency, landed cost.

   Layout rule for everything below (CLAUDE.md R-UI-1): a card is the width of
   its CONTENT. The money settings, the charge sheet and the two ledgers all get
   a max-width and sit in `.cols` pairs; only the freight table and the payment
   run — genuinely wide, many-column data — are allowed to span the page.
   ========================================================================== */

/* ---- the order's money settings: three controls, so three controls wide ---- */
.po-money { max-width: 720px; }
/* The read-mode money facts, riding in the summary row instead of a card of
   their own with a screen of nothing beside it. */
.po-money-inline .po-money-grid { gap: 8px 20px; margin-top: 6px; }
.po-money-inline .pm-val { font-size: 14px; }
.po-money-inline .mono { margin-top: 8px; font-size: 11.5px; }
/* Deliver to, as a slice of the summary: the address at the tile's own size,
   and the instructions held to three lines so one long sentence cannot stretch
   every tile in the row. The whole sentence is on the hover. */
.po-deliver-slice .addr { font-size: 13px; line-height: 1.5; margin: 6px 0 4px; color: var(--text); }
.po-deliver-slice .po-deliv-note {
  margin-top: 6px; font-size: 12.5px; line-height: 1.45; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.po-money-grid { display: flex; flex-wrap: wrap; gap: 14px 26px; align-items: flex-start; }
.po-money-grid > * { min-width: 0; }
.po-money-grid .field { margin: 0; }
.po-money-grid .field select { min-width: 190px; }
.po-money-grid .pm-val { font-size: 15px; font-weight: 700; margin-top: 3px; }
.po-money .card-head { padding: 0 0 12px; }
.po-money .hint { max-width: 34ch; }

/* A rate is read out loud as "1 AUD = 0.71 USD", so it is typed that way. */
.fx-wrap { display: inline-flex; align-items: center; gap: 6px; }
.fx-wrap input { width: 124px; font-family: var(--mono); }
.ccy-tag {
  background: var(--panel2); border: 1px solid var(--border-strong); border-left: none;
  border-radius: 0 var(--r-sm) var(--r-sm) 0; padding: 6px 9px; font-size: 12.5px;
  color: var(--muted); font-weight: 600; margin-left: -7px;
}

/* ---- the totals block at the foot of the order ---- */
.po-totals { margin: 8px 16px 14px auto; max-width: 340px; font-size: 14px; }
.po-totals .pt-row { display: flex; justify-content: space-between; gap: 18px; padding: 3px 0; align-items: baseline; }
.po-totals .pt-row .num { text-align: right; font-variant-numeric: tabular-nums; }
.po-totals .pt-sub { display: block; font-size: 11.5px; font-weight: 400; }
.po-totals .pt-row.total {
  border-top: 1px solid var(--border); margin-top: 5px; padding-top: 8px;
  font-weight: 800; font-size: 16px;
}
.po-totals .pt-row.base { font-size: 12.5px; padding-top: 1px; }
.po-totals .pt-row.landed {
  margin-top: 7px; padding-top: 8px; border-top: 1px dashed var(--border-strong); font-weight: 700;
}
.po-totals .pt-note { margin-top: 7px; font-size: 12px; }
.fx-eq { font-weight: 400; }
th .th-sub { font-size: 10.5px; font-weight: 500; color: var(--faint); text-transform: none; letter-spacing: 0; }

/* ---- the purchase-order document ----

   ONE MEASURE. Every card used to pick its own width, so the right edge of the
   page went 32px, 995px, 315px, 32px on the way down and read as holes. The
   document gets a measure and every card fills it; what is left over is page
   margin, which is what margin is supposed to look like.

   AND ONE COLUMN RULE, for every table in it. `table { width: 100% }` spreads
   surplus width EVENLY across columns, so a two-digit UNITS column was handed
   238px and the eye had to cross a canyon to compare two numbers. The FIRST
   column absorbs the slack — it holds the name, it can use it — and everything
   numeric packs together where it reads as a column of figures. */
.po-page { max-width: 1520px; }
.po-page .card.np td:first-child, .po-page .card.np th:first-child { width: 100%; white-space: normal; }
.po-page .card.np th:not(:first-child), .po-page .card.np td:not(:first-child) { width: 1%; white-space: nowrap; }
/* The generic `.card.np td:nth-child(2|3)` widener is for list pages; inside this
   document those are data columns, and padding them out is the canyon. */
.po-page .card.np td:nth-child(2), .po-page .card.np td:nth-child(3) { min-width: 0; }
.card.np.po-doc td:first-child { min-width: 26ch; }

/* The foot of the order: a line/unit summary on the left, the money on the
   right. The totals used to sit alone under a full-width card, which left a bar
   of dead space the width of the screen beside them. */
.po-foot { display: flex; align-items: flex-start; justify-content: space-between; gap: 32px; padding: 4px 16px 0; flex-wrap: wrap; }
.po-foot-sum { font-size: 13px; color: var(--muted); padding-top: 10px; }
.po-foot .po-totals { margin: 8px 0 12px; }

/* ---- freight + landing: a content-width charge sheet BESIDE the wide split ----
   The charge sheet is a three-field form, so it gets the width of three fields
   and no more. It used to share a flex row with a nine-column table, which
   squeezed it below its own content width: an internal scrollbar, and an amount
   box the size of a slot. Stacked now — R-UI-1 in both directions, sizing the
   small thing to its content AND letting the genuinely wide table span. */
/* Side by side only when both fit at their natural size (640 + 900 + gap). Below
   that they wrap and stack — which is what the owner asked for when a 1200px
   window squeezed the charge sheet into a slot. No breakpoint to maintain: the
   flex bases decide it. */
.po-landing { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-start; }
.po-landing > .po-landed { flex: 1 1 620px; max-width: 820px; }
.po-landing > .po-freight { flex: 2 1 820px; }
/* A foreign-currency order with customs AND insurance renders ten columns and
   needs ~1180px. Sharing a row it only got 984 and grew a scrollbar, so it asks
   for its own row instead — set by the table itself (`wants-room`), because the
   stylesheet cannot count columns. */
.po-landing > .po-freight.wants-room { flex: 1 1 100%; }
.po-landed { max-width: 640px; }
.po-landed table { min-width: 0; }
.po-landed input.lc-amt { width: 116px; text-align: right; font-variant-numeric: tabular-nums; }
.po-landed input.lc-cmt { width: 100%; min-width: 140px; }
.po-landed select.lc-acct { width: auto; min-width: 116px; }
/* The reference column takes the slack, so the account and amount controls keep
   a comfortable fixed size instead of collapsing as the card narrows. */
.po-landed th:nth-child(3), .po-landed td:nth-child(3) { width: 100%; }
.po-landed th:nth-child(4), .po-landed td:nth-child(4) { white-space: nowrap; }
.po-landed tr.lc-none td { white-space: normal; }
.po-freight input.fl-weight { width: 76px; text-align: right; }
/* Cubic metres per unit. Four decimals, because a small part is 0.0017 m³ and
   rounding it to two would freight it for nothing. */
.po-freight input.fl-vol { width: 92px; text-align: right; font-variant-numeric: tabular-nums; }
.po-freight td.fl-vol-cell { white-space: nowrap; }
/* A quiet marker that THIS ORDER set the figure, rather than the product. */
.fl-ovr { font-size: 10px; font-weight: 700; color: var(--accent); margin-left: 4px; letter-spacing: 0.02em; }
/* The item column wraps and takes the slack. Capping it at 40ch was what forced
   the NUMERIC columns to absorb the surplus instead — Qty and Share came out at
   113px each to hold two digits. */
.card.np.po-freight td.fl-item { white-space: normal; min-width: 20ch; }
.po-freight tfoot td { font-weight: 700; background: var(--panel3); border-top: 1px solid var(--border-strong); }
.po-freight select { width: auto; min-width: 168px; }

/* ---- the two ledgers, side by side ---- */
.po-ledgers { align-items: stretch; }
.po-ledgers .po-ledger { flex: 1 1 520px; min-width: 460px; }
.po-ledger .table-foot { display: flex; justify-content: space-between; gap: 12px; }
.btn.sm.icon { padding: 3px 8px; font-size: 13px; line-height: 1.1; }
/* The books' figure stacks under the supplier's rather than stretching the
   column: "US$3,300.00 = $2,062.50" on one line was the entire 3px overflow. */
.po-page .po-ledger.inv .fx-eq { display: block; }
.po-ledger tr.voided td { opacity: 0.5; text-decoration: line-through; }
.po-ledger tr.voided td .badge { text-decoration: none; }
/* The red flag: stock on our shelf that no bill has claimed. */
.bad-note {
  background: var(--bad-bg); border-top: 1px solid var(--bad-bd); border-bottom: 1px solid var(--bad-bd);
  padding: 9px 16px; font-size: 12.5px; line-height: 1.5; color: var(--text);
}
.ok-t { color: var(--ok); font-weight: 600; }

/* ---- entering a bill ---- */
.modal.wide { max-width: 940px; width: 94vw; }
.inv-head { display: flex; flex-wrap: wrap; gap: 12px 22px; align-items: flex-start; margin-bottom: 6px; }
.inv-head .field { margin: 0; }
.inv-head .field input { width: 190px; }
.inv-head .field .hint { max-width: 30ch; }
table.inv-lines input.il-qty { width: 84px; text-align: right; font-variant-numeric: tabular-nums; }
/* The bill's lines fit the dialog (owner, 2026-09-27). Everything on `nowrap`
   let one long product name push the table past the edge and grow a scrollbar.
   The item takes the slack and wraps under its SKU; the figures stay on one
   line, tight, and take only what they need. Written at `.card.np` weight so
   the page-wide positional min-widths do not widen the number columns. A SKU
   that is really a 70-character quote reference may break mid-token — the
   20ch floor on the column is what stops that becoming a ribbon of letters. */
.card.np table.inv-lines th, .card.np table.inv-lines td { padding: 9px 10px; white-space: nowrap; min-width: 0; }
.card.np table.inv-lines td:first-child { white-space: normal; width: 100%; min-width: 20ch; }
.card.np table.inv-lines td:first-child .mono { overflow-wrap: anywhere; }
.card.np table.inv-lines tr > :first-child { padding-left: 14px; }
.card.np table.inv-lines tr > :last-child { padding-right: 14px; }
.inv-foot { display: flex; justify-content: space-between; align-items: center; gap: 14px; margin-top: 10px; flex-wrap: wrap; }
.inv-tot { font-size: 14px; }

/* The armed half of the two-click "no invoice" on the receiving bench. Loud on
   purpose — it only appears after the first click, and it is the one place the
   cost of skipping the bill is spelled out. */
.rip-warn {
  background: var(--warn-bg); border: 1px solid var(--warn-bd); border-radius: var(--r);
  padding: 10px 12px; margin-top: 12px; font-size: 13px; line-height: 1.5; color: var(--text);
}
.rip-warn b { display: block; margin-bottom: 3px; }

/* ---- the payables module ---- */
.stat-card.bad { border-color: var(--bad-bd); background: color-mix(in srgb, var(--bad-bg) 55%, var(--panel)); }
.stat-card.bad .stat-num { color: var(--bad); }
.stat-card.warn { border-color: var(--warn-bd); background: color-mix(in srgb, var(--warn-bg) 55%, var(--panel)); }
.ap-cols { align-items: stretch; }
.ap-cols .ap-ageing { flex: 0 1 500px; }
.ap-cols .ap-suppliers { flex: 2 1 620px; }
.ap-ageing .bar-cell { width: 110px; padding-right: 16px; }
.ap-bar { display: block; height: 7px; border-radius: 4px; background: var(--accent); min-width: 2px; opacity: 0.75; }
.ap-bar.bad { background: var(--bad); opacity: 1; }
.ap-bar.warn { background: var(--warn); opacity: 1; }
.ap-payfirst tr.late td, .ap-suppliers tr.late td { background: color-mix(in srgb, var(--bad-bg) 40%, transparent); }
.card.np tr.late td { background: color-mix(in srgb, var(--bad-bg) 40%, transparent); }
/* The priority score. It is a number people will argue with, so it looks like
   a number and the reasons sit next to it. */
.pri {
  display: inline-block; min-width: 34px; text-align: center; font-family: var(--mono);
  font-size: 12.5px; font-weight: 700; padding: 3px 7px; border-radius: var(--r-sm);
  background: var(--chip); border: 1px solid var(--border-strong); color: var(--muted);
}
.pri-pay_now { background: var(--bad-bg); border-color: var(--bad-bd); color: var(--bad); }
.pri-this_week { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--warn); }
.pri-scheduled { background: var(--ok-bg); border-color: var(--ok-bd); color: var(--ok); }
.card.np td.why { white-space: normal; min-width: 22ch; max-width: 46ch; line-height: 2; }
.card.np td.why .chip { font-size: 11.5px; }

/* ---- the pay run, rolled up by supplier ---- */
/* A wide table that genuinely needs the page: eleven columns of money against
   one row per supplier is the whole point of the screen (R-UI-1 decided, not
   defaulted). */
.ap-bysupplier .fold { width: 30px; padding-right: 0; }
.ap-bysupplier .fold-btn {
  background: none; border: none; cursor: pointer; color: var(--muted);
  font-size: 12px; line-height: 1; padding: 4px 2px; transition: transform 0.12s ease, color 0.12s ease;
}
.ap-bysupplier .fold-btn:disabled { cursor: default; opacity: 0.4; }
.ap-bysupplier tr.sup-row { cursor: pointer; }
.ap-bysupplier tr.sup-row:hover td { background: var(--panel2); }
.ap-bysupplier tr.sup-row.open .fold-btn { transform: rotate(90deg); color: var(--accent); }
.ap-bysupplier tr.sup-row.open td { border-bottom-color: transparent; }
.ap-bysupplier td.sup-name { white-space: normal; min-width: 20ch; max-width: 34ch; }
.ap-bysupplier td.sup-name a { font-weight: 650; }
.ap-bysupplier td.sup-name .sub { margin-top: 4px; display: flex; flex-wrap: wrap; gap: 4px; }
/* Must pay is the column the owner came for, so it is the only one that shouts. */
.ap-bysupplier td.mustpay { font-size: 14.5px; }
.ap-bysupplier th:nth-child(3), .ap-bysupplier td.mustpay { background: color-mix(in srgb, var(--panel2) 70%, transparent); }
.ap-bysupplier tr.late td.mustpay { background: color-mix(in srgb, var(--bad-bg) 70%, transparent); }
.ap-bysupplier tfoot td { font-weight: 650; background: var(--panel3); border-top: 1px solid var(--border-strong); }
.ap-bysupplier .sub .badge { font-size: 10.5px; padding: 1px 7px; }
/* A chip that is a warning rather than a label: no terms, no bank details. */
.chip.warn-c { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--warn); font-weight: 600; }

/* The bills behind one supplier, folded open inside their row. */
.ap-bysupplier tr.sup-bills > td { padding: 0 0 0 30px; background: var(--panel2); }
.sub-table { width: 100%; border-collapse: collapse; }
.sub-table th {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted);
  font-weight: 700; text-align: left; padding: 7px 12px; background: var(--panel3);
  border-bottom: 1px solid var(--border);
}
.sub-table td { padding: 8px 12px; font-size: 13px; border-bottom: 1px solid var(--border); white-space: nowrap; }
.sub-table tr:last-child td { border-bottom: none; }
.sub-table td.r, .sub-table th.r { text-align: right; }
.sub-table td.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.sub-table tr.late td { background: color-mix(in srgb, var(--bad-bg) 35%, transparent); }
.sub-table .sub { font-size: 11.5px; margin-top: 2px; }

/* The invoice fields on the receiving bench — three controls, not a page-wide
   bar. Sized to what is in it (R-UI-1). */
.rcv-invoice { max-width: 720px; }
.rcv-invoice .card-head { padding: 0 0 12px; }
.rcv-inv-grid { display: flex; flex-wrap: wrap; gap: 12px 22px; align-items: flex-start; }
.rcv-inv-grid .field { margin: 0; }
.rcv-inv-grid .field input { width: 210px; }
.rcv-inv-grid .hint { flex: 1 1 260px; max-width: 46ch; margin: 0; align-self: center; }

/* ============================================================================
   NOTES ON A DOCUMENT (0098/0099)

   A ledger, not a field. Newest first, every entry signed and dated, the box to
   write one at the top where the cursor already is. Sized to prose — a note is
   read as a sentence, so the card is a column of text, not a page-wide bar.
   ========================================================================== */
.notes-card { max-width: 760px; }
.notes-card .card-head { padding: 0 0 10px; }

.note-new { border: 1px solid var(--border-strong); border-radius: var(--r); padding: 9px 10px 8px; background: var(--panel2); }
.note-new textarea, .note-edit-box textarea {
  width: 100%; resize: vertical; min-height: 44px; font: inherit; font-size: 13.5px;
  background: var(--panel); color: var(--text); border: 1px solid var(--border-strong);
  border-radius: var(--r-sm); padding: 7px 9px;
}
.note-new textarea:focus, .note-edit-box textarea:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.note-new-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 7px; }
.note-new-foot .btn { margin-left: auto; }
.note-signed { font-size: 12px; }

.note-list { list-style: none; margin: 14px 0 0; padding: 0; }
.note-list .note {
  padding: 10px 0 11px; border-top: 1px solid var(--border);
}
.note-list .note:first-child { border-top: none; padding-top: 4px; }
.note-head { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; margin-bottom: 4px; }
.note-head b { font-size: 13.5px; }
.note-when { font-size: 12px; }
/* "edited", and by whom when it was not the author — the one case where the
   history has to name the hand that moved somebody else's sentence. */
.note-edited { font-size: 11.5px; font-style: italic; }
.note-asmgr {
  font-size: 10px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase;
  color: var(--warn); background: var(--warn-bg); border: 1px solid var(--warn-bd);
  border-radius: var(--r-pill); padding: 1px 7px;
}
/* The controls only appear on hover for your own notes — a thread of ten notes
   with twenty buttons in it is a toolbar, not a conversation. */
.note-acts { margin-left: auto; display: inline-flex; gap: 5px; opacity: 0; transition: opacity .12s; }
.note:hover .note-acts, .note:focus-within .note-acts { opacity: 1; }
.note-body-read { font-size: 13.5px; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; }
.note-edit-box { margin-top: 7px; }
.note-none { padding: 6px 0 2px; font-size: 13px; }

/* ---------------------------------------------------------------- model tree
   Parent model, then its submodels NESTED INSIDE it (0104).

   The first cut put both levels in one table and indented the child rows by
   26px, which is not nesting — it is a flat list with a limp. A submodel has to
   read as belonging to its parent at a glance, so a family is now a CARD and its
   submodels sit in a railed well inside that card. Depth is carried by
   containment and a rail, not by padding nobody can measure by eye. */
.mt-fam { border: 1px solid var(--border); border-radius: var(--r); background: var(--panel);
          margin: 0 0 14px; overflow: hidden; }
.mt-fam.off { opacity: 0.62; }

.mt-fam-head { display: flex; align-items: flex-start; gap: 12px; padding: 12px 14px;
               background: var(--panel3); border-bottom: 1px solid var(--border); }
/* The code IS the identity — it is the first thing on every serial beneath it,
   so it gets the weight rather than sharing a column with everything else. */
.mt-code { font-family: var(--mono); font-size: 17px; font-weight: 800; letter-spacing: 0.06em;
           background: var(--accent-wash); color: var(--accent-hover); border: 1px solid var(--accent-wash-bd);
           border-radius: var(--r-sm); padding: 3px 9px; line-height: 1.25; flex: none; }
.mt-fam-id { min-width: 0; flex: 1; }
.mt-fam-name { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; }
.mt-meta { color: var(--muted); font-size: 12.5px; margin-top: 2px; }
.mt-acts { display: flex; gap: 6px; flex: none; align-items: center; }

/* The well the submodels live in. The rail is the "these belong to that". */
.mt-kids { padding: 8px 14px 12px 0; margin-left: 26px; border-left: 2px solid var(--border-strong); }
.mt-kids-none { color: var(--muted); font-size: 12.5px; padding: 10px 0 4px 16px; }

.mt-tier { display: flex; align-items: center; gap: 10px; padding: 8px 0 8px 16px; position: relative; }
.mt-tier + .mt-tier { border-top: 1px solid var(--border); }
.mt-tier.off { opacity: 0.6; }
/* A short elbow from the rail into each row — the connector that says "child". */
.mt-tier::before { content: ""; position: absolute; left: 0; top: 50%; width: 11px; height: 2px;
                   background: var(--border-strong); }
.mt-char { font-family: var(--mono); font-size: 15px; font-weight: 800; width: 26px; text-align: center;
           border-radius: var(--r-sm); padding: 2px 0; flex: none;
           background: var(--chip); color: var(--text); border: 1px solid var(--border-strong); }
.mt-tier-id { min-width: 0; flex: 1; }
.mt-tier-name { font-size: 13.5px; font-weight: 600; }
.mt-serial { font-family: var(--mono); font-size: 12.5px; color: var(--muted); flex: none; }
.mt-serial b { color: var(--text); font-weight: 600; }
.mt-nums { font-family: var(--mono); font-size: 12px; color: var(--muted); flex: none;
           font-variant-numeric: tabular-nums; text-align: right; min-width: 84px; }

@media (max-width: 720px) {
  .mt-fam-head, .mt-tier { flex-wrap: wrap; }
  .mt-serial, .mt-nums { width: 100%; text-align: left; min-width: 0; }
}

/* Model tree: the search row, the disclosure twisty, and the closed-state count.
   Families open CLOSED — several families of four submodels is a page you scroll
   past rather than read — so a shut family still has to say how much is inside
   it, or collapsing just hides the answer. */
.mt-searchrow { display: flex; align-items: center; gap: 10px; padding: 12px 16px 14px; flex-wrap: wrap; }
.mt-search { flex: 1; min-width: 220px; font: inherit; font-size: 13.5px; padding: 7px 10px;
             border: 1px solid var(--border-strong); border-radius: var(--r-sm);
             background: var(--panel); color: var(--text); }
.mt-search:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.mt-searchnote { color: var(--muted); font-size: 12.5px; flex: none; }

.mt-fam-head { cursor: pointer; }
.mt-fam-head:hover { background: var(--panel2); }
/* The row is the disclosure target, so the buttons inside it must not inherit
   the pointer or every action looks like it opens the family. */
.mt-fam-head .btn { cursor: pointer; }
.mt-tw { flex: none; width: 22px; height: 22px; margin-top: 2px; padding: 0; line-height: 1;
         font-size: 11px; color: var(--muted); background: none; border: 1px solid transparent;
         border-radius: var(--r-sm); cursor: pointer; }
.mt-tw:hover { background: var(--panel); border-color: var(--border-strong); color: var(--text); }
.mt-tw:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.mt-kidcount { color: var(--muted); font-size: 12px; flex: none; white-space: nowrap;
               font-variant-numeric: tabular-nums; }

@media (max-width: 720px) { .mt-kidcount { width: 100%; } }

/* ---- the lens strip: eight questions over one dataset ---- */
.fu-tabs { display: flex; gap: 4px; flex-wrap: wrap; align-items: center;
  border-bottom: 1px solid var(--border); margin: 0 0 14px; padding: 0 2px; }
/* Written for an <a>, and it stays one — but the reset is here so a future
   <button class="fu-tab"> cannot come out wearing the browser's own chrome
   (in dark mode: a light grey pill in the system font). */
.fu-tab { display: inline-flex; align-items: center; gap: 7px; padding: 9px 13px 10px;
  font: inherit; font-size: 13.5px; font-weight: 600; color: var(--muted); text-decoration: none;
  background: transparent; border: 0; cursor: pointer;
  border-bottom: 2px solid transparent; margin-bottom: -1px; border-radius: var(--r) var(--r) 0 0; }
.fu-tab:hover { background: var(--panel2); color: var(--text); text-decoration: none; }
.fu-tab.on { color: var(--text); border-bottom-color: var(--accent); background: var(--panel); }
.fu-tab .n { min-width: 19px; text-align: center; padding: 0 5px; border-radius: var(--r-pill);
  background: var(--chip); border: 1px solid var(--border); color: var(--muted);
  font-size: 11.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.fu-tab.on .n { background: var(--accent-wash); border-color: var(--accent-wash-bd); color: var(--accent-deep); }
.fu-tab .n.hot { background: var(--bad-bg); border-color: var(--bad-bd); color: var(--bad); }
.fu-ord { border-bottom: 1px solid var(--border); }
.fu-ord:last-child { border-bottom: none; }
/* EVERY ROW IS ITS OWN GRID, so a content-sized track resolves to a different
   width on every row and the columns walk. `minmax(x, auto)` and a trailing
   `auto` did exactly that: an order with a long model name pushed its due date
   right, and the row under it did not. The last three tracks are fixed now, so
   the six columns line up down the whole list. */
.fu-ord-h { display: grid; gap: 10px; align-items: center; padding: 10px 14px; cursor: pointer;
  /* The last track is the actions column: freight dropdowns + Print sit on
     one line (0182), so it needs room for all three without wrapping. */
  grid-template-columns: 22px minmax(180px, 1.1fr) minmax(160px, 1.35fr) 122px 92px 400px; }
.fu-ord-h:hover { background: var(--panel3); }
.fu-tw { width: 22px; height: 22px; display: grid; place-items: center; color: var(--faint);
  font-size: 11px; transition: transform 0.12s; }
.fu-ord.open .fu-tw { transform: rotate(90deg); color: var(--muted); }
.fu-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fu-who .l1 { font-weight: 700; font-size: 14px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.fu-who .l1.model { font-weight: 600; }
.fu-who .l2 { font-size: 12.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fu-chips { display: flex; gap: 5px; flex-wrap: wrap; align-items: center; }
.fu-acts { display: flex; gap: 6px; justify-content: flex-end; align-items: center; flex-wrap: wrap; }

/* Due date: the one number a dispatcher scans a list for. */
.fu-due { display: flex; flex-direction: column; gap: 1px; font-variant-numeric: tabular-nums; }
.fu-due .d { font-weight: 700; font-size: 13.5px; }
.fu-due .t { font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
.fu-due.late .d, .fu-due.late .t { color: var(--bad); }
.fu-due.soon .d, .fu-due.soon .t { color: var(--warn); }

/* Nested lines are OPEN by default — the twisty collapses, it never reveals. */
.fu-subs { display: none; padding: 0 14px 12px 48px; }
.fu-ord.open .fu-subs { display: block; }
/* Same fix, same reason: fixed trailing tracks so the header and every line
   under it agree about where a column is. */
.fu-subhead, .fu-sub { display: grid; gap: 10px; align-items: center;
  grid-template-columns: 18px minmax(140px, 0.9fr) minmax(190px, 1.6fr) 46px 92px 210px 1fr; }
.fu-subhead { padding: 4px 10px 5px; font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--faint); }
.fu-sub { padding: 6px 10px; margin: 0; border: 1px solid transparent; border-radius: var(--r); font-size: 13.5px; }
.fu-sub:hover { background: var(--panel3); }
.fu-sub.mach { background: var(--panel3); border-color: var(--border); }
.fu-sub.mach .nm { font-weight: 600; }
.fu-sub.picked { border-color: var(--ok-bd); background: var(--ok-bg); }
/* SKUs, short names, sizes and connote chips are set in the app's own face with
   even-width digits — the .mono treatment every other page uses — never the
   typewriter face, which made Sort & print read like a different app. */
.fu-sub .sku { font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: 0.01em; }
.fu-sub .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fu-sub .qty, .fu-subhead .qty { text-align: right; font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums; }

/* What kind of thing a line is, at a glance. One hue per kind. */
.fu-pip { display: inline-block; width: 7px; height: 7px; border-radius: 2px; margin-right: 6px;
  vertical-align: 1px; flex: none; background: var(--muted); }
.fu-pip.pc { background: var(--accent); }
.fu-pip.kit { background: #7c3aed; }
.fu-pip.monitor { background: var(--info); }
.fu-pip.accessory { background: var(--warn); }

/* "Can't print yet" — every blocker at once, because somebody is at a printer. */
.fu-blk { display: flex; gap: 5px; flex-wrap: wrap; align-items: center; margin: 0 14px 9px 48px;
  padding: 7px 10px; border-radius: var(--r); background: var(--warn-bg); border: 1px solid var(--warn-bd); }
.fu-blk .lab { font-size: 10.5px; font-weight: 800; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--warn); }

/* ---- the flat item lists: one row per thing to pick ---- */
tr.fu-irow:hover td { background: var(--panel3); }
tr.fu-irow.selected td { background: var(--accent-wash); }
.fu-itm, .fu-for { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.fu-itm .isku, .fu-for .f1 { font-weight: 700; font-size: 13px; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.fu-itm .isku { font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: 0.01em; }
.fu-itm .inm, .fu-for .f2 { font-size: 12px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 40ch; }

/* ---- the selection bar: ticked items become ONE carton ---- */
.fu-selbar { position: fixed; left: 50%; bottom: 20px; z-index: 40;
  transform: translateX(-50%) translateY(150%); transition: transform 0.16s ease;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  max-width: calc(100vw - 32px); padding: 9px 12px 9px 18px;
  background: var(--panel); border: 1px solid var(--border-strong); border-radius: var(--r-pill);
  box-shadow: var(--shadow-md); }
.fu-selbar.show { transform: translateX(-50%) translateY(0); }
.fu-selbar .cnt { font-size: 13.5px; font-weight: 700; white-space: nowrap; }
.fu-selbar .warn { font-size: 12.5px; font-weight: 600; color: var(--warn); }

/* ---- pallets ---- */

/* A printed sticker, drawn. Paper-white in both themes, because paper is. */

/* Narrow: the order header collapses to two rows rather than scrolling sideways,
   and the nested grid drops to name + actions. Same page, one code path. */
@media (max-width: 1080px) {
  .fu-ord-h { grid-template-columns: 22px 1fr auto; }
  .fu-ord-h .fu-duecol, .fu-ord-h .fu-chips { grid-column: 2 / -1; }
  .fu-subs { padding-left: 22px; }
  .fu-subhead { display: none; }
  .fu-sub { grid-template-columns: 18px 1fr auto; }
  .fu-sub .qty, .fu-sub .stk { display: none; }
}

/* the order rows: the ROW is the control, so no button to aim at.
   `.pf-list` HERE MEANT A DIV, and that markup is gone — but the selector
   stayed, and the only elements left carrying the class are the floor's three
   <table class="pf-list"> boards. A table set to `display:flex` stops being a
   table: table-layout, colgroup widths and column alignment are all discarded,
   every cell becomes a flex item, and the columns line up by accident. That is
   why the build board's columns were cooked (owner, 2026-09-01). Scoped to the
   div it was written for; if nothing is a div.pf-list any more, it does
   nothing, which is what a dead rule should do. */
div.pf-list { display: flex; flex-direction: column; }

/* ============================================================ working a stack */

/* What the hero IS, and the identifiers it is not. The screen tells you to scan
   a SKU, so it has to print the SKU — and the serial it is not asking for yet
   still has to be readable, because a packer holding the wrong box needs to see
   which machine they were sent for. */

/* the box catalogue: every field editable in place, and a blank last row.
   R-UI-1 — six short columns and three rows is not a page-wide table, and a
   Delete button a mouse-drag away from the row it deletes is the reason. */
.card.bx-cat { width: max-content; max-width: 100%; }
.bx-table input[type=text], .bx-table input[type=number] { padding: 4px 7px; font-size: 13px; }
.bx-num { width: 78px; text-align: right; font-family: var(--mono); }
.bx-new { background: var(--panel3); }
.bx-new input::placeholder { color: var(--faint); }
/* search to ADD: the list starts empty and this is how it fills */
.bx-add { padding: 12px 14px; border-bottom: 1px solid var(--border); background: var(--panel3); }
.bx-add input[type=search] { width: 100%; max-width: 520px; padding: 8px 11px; }
.bx-results { margin-top: 8px; display: flex; flex-direction: column; gap: 4px; max-height: 300px; overflow: auto; }
.bx-hit { display: grid; grid-template-columns: minmax(110px, auto) 1fr minmax(110px, auto) auto;
          gap: 10px; align-items: center; padding: 6px 10px; font-size: 12.5px;
          background: var(--panel); border: 1px solid var(--border); border-radius: var(--r); }

/* ======================================================= the reports (2026-08-29)
   The accountants' eight questions. Built on .ins so the window picker, the
   segmented controls and the tabular numerals are the SAME ones the rest of the
   insight tier uses — two date pickers that behave differently on two screens is
   how a month-end figure gets run for the wrong month.

   R-UI-1 is doing real work here: the controls card is sized to its controls and
   the tables span, because a table of every product at every branch genuinely
   needs the width and a four-field filter genuinely does not. */
.rep-tabs { margin-bottom: 14px; }

/* The controls card: content-width, never page-width (R-UI-1). */
.rep-controls { max-width: 1100px; padding: 13px 15px; display: flex; flex-direction: column; gap: 10px; }
.rep-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 16px; }
.rep-row .ins-window { margin: 0; }
.rep-f { display: flex; flex-direction: column; gap: 3px; font-size: 11.5px; font-weight: 600; color: var(--muted); }
.rep-f > span { white-space: nowrap; }
.rep-f input[type=search] { min-width: 220px; }
.rep-check { flex-direction: row; align-items: center; gap: 6px; }
/* The action belongs to the card it acts on (R-UI-2) — not a page-wide bar. */
.rep-actions { justify-content: flex-end; border-top: 1px solid var(--border); padding-top: 10px; }

/* Branch multi-select. A dropdown can say "one branch" or "every branch"; the
   real question at month end is often two of the four. */
.rep-branches { position: relative; }
.rep-branches > summary { cursor: pointer; list-style: none; padding: 6px 11px; font-size: 12.5px; font-weight: 600;
  color: var(--text); background: var(--panel); border: 1px solid var(--border-strong); border-radius: var(--r-sm); }
.rep-branches > summary::-webkit-details-marker { display: none; }
.rep-branches > summary::after { content: " ▾"; color: var(--faint); }
.rep-branch-list { position: absolute; z-index: 40; margin-top: 4px; min-width: 240px; max-height: 320px; overflow: auto;
  display: flex; flex-direction: column; gap: 5px; padding: 10px 12px;
  background: var(--panel); border: 1px solid var(--border-strong); border-radius: var(--r); box-shadow: var(--shadow); }
.rep-branch { display: flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 500; color: var(--text); }

/* The figures. One tile per number that answers the question the page asks. */
.rep-summary { padding: 15px; }
.rep-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; }
.rep-tile { display: flex; flex-direction: column; gap: 2px; padding: 11px 13px;
  background: var(--panel3); border: 1px solid var(--border); border-radius: var(--r); }
.rep-tile-l { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.rep-tile-v { font-size: 21px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text); }
.rep-tile-s { font-size: 11.5px; color: var(--faint); }

/* The basis sentence. Small, but never absent: a figure an accountant cannot
   explain is a figure they cannot sign (R-DATA-2). */
.rep-basis { margin: 11px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--faint); max-width: 90ch; }
.rep-note { margin: 11px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--text);
  padding: 9px 12px; background: var(--warn-bg); border: 1px solid var(--warn-bd); border-radius: var(--r); max-width: 90ch; }

/* A day with no photograph. An answer, not an error — so it is not styled as one. */
.rep-none { max-width: 640px; padding: 16px 18px; }
.rep-none h3 { margin: 0 0 6px; }
.rep-none p { margin: 0 0 8px; font-size: 13px; line-height: 1.55; }

/* P0-10: the nine reports render twelve tables and not one of them sat in a
   `.card.np`, so on a narrow window they pushed the whole page sideways
   instead of scrolling inside their own box. They do now — but `.card.np`
   assumes a document table, where columns 2-4 are names that wrap. A report is
   a wall of figures: the label column wraps, every number column does not. */
.card.np.rep-card td, .card.np.rep-card th { white-space: nowrap; min-width: 0; }
.card.np.rep-card td:first-child { white-space: normal; min-width: 14ch; }
.rep-tbl { width: 100%; font-variant-numeric: tabular-nums; }
.rep-tbl .num { text-align: right; font-family: var(--mono); white-space: nowrap; }
.rep-tbl .sub { display: block; font-size: 11px; color: var(--faint); font-weight: 500; }
.rep-tbl .warn { color: var(--warn); font-weight: 700; }
.rep-void td { opacity: .5; text-decoration: line-through; }

/* A pack's caveats ride under its row: doubt goes first, always. */
.pk-caveat td { padding: 8px 15px; font-size: 12px; line-height: 1.5; color: var(--text);
  background: var(--warn-bg); border-top: 0; white-space: pre-line; }
.rep-tbl tr.on > td { background: var(--accent-wash); }

/* Closing the books. Two cards side by side (.cols): where the line is, and the
   control that moves it — R-UI-2, the control beside the thing it controls. */
.pl-inner { padding: 14px 16px 16px; }
.pl-state.on { border-color: var(--ok-bd); }
.pl-big { margin: 0 0 8px; font-size: 15px; line-height: 1.5; color: var(--muted); }
.pl-big b { display: inline-block; margin-top: 2px; font-size: 22px; color: var(--text); }
.pl-f { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px;
        font-size: 11.5px; font-weight: 600; color: var(--muted); max-width: 420px; }
.pl-hint { margin: 10px 0 0; font-size: 11.5px; line-height: 1.5; max-width: 60ch; }

/* ---- whose app is this (0127) --------------------------------------------
   The wordmark comes from the database now, so it can be any shape: a wide SVG
   wordmark, a square PNG, a logo somebody exported at 2000px. Constrained here
   rather than trusted, because a logo that pushes the nav off the screen is a
   company that cannot use the app until somebody edits SQL. */
.brand-mark { display: block; }
.brand-mark svg, .brand-mark img { display: block; max-width: 100%; max-height: 46px; width: auto; height: auto; }

/* The company switcher. Only ever rendered when there IS more than one. */
.co-switch > summary { cursor: pointer; list-style: none; display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; font-size: 12.5px; font-weight: 700; color: var(--text);
  background: var(--panel3); border: 1px solid var(--border); border-radius: var(--r-sm); }
.co-switch > summary::-webkit-details-marker { display: none; }
.co-switch > summary::after { content: "⇄"; margin-left: auto; color: var(--faint); font-weight: 400; }
.co-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex: none; }
.co-list { display: flex; flex-direction: column; gap: 4px; padding: 8px 2px 2px; }
.co-hint { margin: 0 0 4px; font-size: 11px; line-height: 1.45; color: var(--faint); }
.co-item { display: flex; flex-direction: column; gap: 1px; padding: 7px 10px; font-size: 12.5px; font-weight: 600;
  color: var(--text); border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--panel); }
.co-item:hover { border-color: var(--accent); }
.co-url { font-size: 10.5px; font-weight: 500; color: var(--faint); font-family: var(--mono); }

/* TWO companies is one button. A disclosure triangle you open and then click
   inside is two presses to do one thing, and with exactly one place to go there
   is nothing to disclose. Same chassis as the summary above, so the two shapes
   sit identically in the sidebar foot. */
.co-switch-solo { display: block; }
.co-switch-one { display: flex; align-items: center; gap: 8px; padding: 7px 10px; font-size: 12.5px;
  font-weight: 700; color: var(--text); background: var(--panel3); border: 1px solid var(--border);
  border-radius: var(--r-sm); }
.co-switch-one::after { content: "⇄"; margin-left: auto; color: var(--faint); font-weight: 400; }
a.co-switch-one:hover { border-color: var(--accent); background: var(--panel2); }
.co-switch-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
/* Not running: the same shape, visibly inert, naming the command instead of
   leaving somebody to wonder why a button did nothing. */
.co-switch-one.is-down, .co-item.is-down { cursor: default; opacity: 0.62; }
.co-switch-one.is-down::after { content: "—"; }
.co-switch-one.is-down .co-dot, .co-item.is-down .co-dot { background: var(--faint); }

/* Appearance: the mark, previewed at the size it will actually be worn. */
.br-preview { display: flex; align-items: center; justify-content: center; min-height: 76px; margin-bottom: 12px;
  padding: 12px; background: var(--panel3); border: 1px dashed var(--border-strong); border-radius: var(--r); }
.br-preview svg, .br-preview img { max-width: 100%; max-height: 52px; width: auto; height: auto; }
.br-colour { display: flex; align-items: center; gap: 8px; }
.br-colour input[type=color] { width: 42px; height: 32px; padding: 2px; border: 1px solid var(--border-strong);
  border-radius: var(--r-sm); background: var(--panel); cursor: pointer; }
.br-colour input[type=text] { width: 100px; font-family: var(--mono); }

/* ===========================================================================
   SORT & PRINT and the SETS (0129).
   Additive: everything above still styles the parts of the fulfilment screen
   that survived. What follows is only what the five lists and the three piles
   introduced.
   =========================================================================== */

/* The filter bar, and the three sub-tabs every list gets. */
.fu-fb { padding: 10px 12px; margin-bottom: 14px; }
.fu-f { display: inline-flex; align-items: center; gap: 6px; }

/* The service/carrier dropdowns on a ready row (0182). One line, never
   wrapped: the app-wide select rule is width:100%, which in a flex row makes
   every dropdown demand the whole line and stack. The compact padding is the
   same pattern the rest of the app uses for small selects (.ps-step,
   .pd-rank), so these read like every other select on the page. `comp` is the
   tighter form used in the item-grain tables (accessories / bulky). */
.fu-fc { display: inline-flex; align-items: center; gap: 6px; flex-wrap: nowrap; }
.fu-fc select { width: auto; margin: 0; padding: 5px 9px; font-size: 13px; line-height: 1.35;
  flex: 0 1 auto; max-width: 200px; cursor: pointer; }
/* The label must never wrap to a second line when the row is full. */
.fu-fc .btn { white-space: nowrap; }
.fu-fc-s { max-width: 150px; }
.fu-fc-c { max-width: 200px; }
.fu-fc-s.comp { max-width: 116px; }
.fu-fc-c.comp { max-width: 150px; }
.fu-fc.comp { gap: 3px; }
.fu-stabs { display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
  padding: 10px 14px; border-bottom: 1px solid var(--border); }
.fu-stab { font: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer;
  border: 1px solid var(--border); background: var(--chip); color: var(--muted);
  border-radius: var(--r-pill); padding: 3px 11px; }
.fu-stab:hover { background: var(--panel2); color: var(--text); }
.fu-stab.on { background: var(--accent-wash); border-color: var(--accent-wash-bd); color: var(--accent-deep); }
.fu-stab b { margin-left: 6px; font-variant-numeric: tabular-nums; color: var(--text); }
.fu-stab.on b { color: var(--accent-deep); }

/* A ticked line, on either grain. */
.fu-sub.sel { background: var(--accent-wash); border-color: var(--accent-wash-bd); }

/* The short name a merged box label prints. */
.fu-tick { font-size: 12px; font-weight: 600; color: var(--accent-deep);
  background: var(--accent-wash); border: 1px solid var(--accent-wash-bd);
  border-radius: var(--r-sm); padding: 0 5px; }

/* Which dimensions the connote will use. A boxed line's own size is struck out
   because the box replaced it — that is what the packer needs to see. */
.fu-dims { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.fu-dims.killed { text-decoration: line-through; text-decoration-color: var(--faint); opacity: 0.65; }
.fu-dimlink { color: var(--accent); text-decoration: none; cursor: pointer; }
.fu-dimlink:hover { text-decoration: underline; }
.fu-err { display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; }
/* The Printed tab's last column, two lines like the item column: the buttons,
   then what happened (state, carrier, consignment, price — or why not). Neither
   line wraps: the column widens instead, so a row is always exactly two lines
   tall — in the narrow table cells a wrap put Void on a line of its own. */
.fu-pcell { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.fu-pcell-btns, .fu-pcell-info { display: flex; gap: 6px; justify-content: flex-end; align-items: center;
  flex-wrap: nowrap; white-space: nowrap; }
.fu-pcell-info { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.fu-err a { color: var(--accent); }
.fu-cart { display: inline-flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.fu-cartcell { display: inline-flex; align-items: center; gap: 7px; flex-wrap: wrap; }

/* The pieces a carrier is actually handed. The payoff of merging, without sums. */
.fu-conn { display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
  margin: 9px 0 2px; padding: 9px 11px; border-radius: var(--r);
  background: var(--panel3); border: 1px solid var(--border); }
.fu-conn .lab { font-size: 10.5px; font-weight: 800; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--faint); margin-right: 3px; }
.fu-pc { display: inline-flex; align-items: center; gap: 6px; font-size: 12px;
  padding: 2px 9px; border-radius: var(--r-pill); border: 1px solid var(--border); background: var(--panel); }
.fu-pc b { font-weight: 600; font-variant-numeric: tabular-nums; }
.fu-pc.pc { border-color: var(--accent-wash-bd); background: var(--accent-wash); }
.fu-pc.box { border-color: var(--info-bd); background: var(--info-bg); padding-right: 4px; }
/* REQUESTS (0325): an opened request is its facts beside its parcels, each the
   width of what it holds (R-UI-1) — not two halves of the page. */
.rq-body { display: grid; grid-template-columns: minmax(260px, 380px) minmax(420px, 540px); gap: 28px;
  padding: 6px 0 4px; align-items: start; }
.rq-facts { display: grid; grid-template-columns: 84px 1fr; gap: 6px 12px; font-size: 13.5px; }
.rq-k { font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--faint); padding-top: 3px; }
.rq-notes { white-space: pre-wrap; }
.rq-parcels > .rq-k { margin-bottom: 6px; }
.rq-ph, .rq-p { display: grid; gap: 6px; align-items: center;
  grid-template-columns: minmax(130px, 1fr) 62px 62px 62px 68px 50px 28px; }
.rq-ph { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--faint); padding-bottom: 4px; }
.rq-ph .r, .rq-p .r { text-align: right; font-variant-numeric: tabular-nums; }
.rq-p { margin-bottom: 4px; }
.rq-p input, .rq-p select { width: 100%; margin: 0; padding: 4px 7px; font-size: 13px; }
.rq-p.ro { font-size: 13.5px; margin-bottom: 2px; }
.modal.rq-form { max-width: 760px; }
@media (max-width: 1000px) { .rq-body { grid-template-columns: 1fr; } }
/* What the box's label prints (0158). The derived line is quiet — it is a
   restatement of the rows above it — and a line somebody chose is not, because
   the whole point of typing one is that it overrules what is in the box. */
.fu-note { font-size: 12px; color: var(--muted); max-width: 46ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fu-note.own { color: var(--text); font-weight: 600; }
.fu-pc .btn.xs.chg { background: transparent; border-color: transparent; color: var(--muted);
  padding: 1px 6px; font-weight: 600; }
.fu-pc .btn.xs.chg:hover { background: var(--panel); border-color: var(--border); color: var(--text); }

.scrollx { overflow-x: auto; }
table.fu-items { width: 100%; }
.btn.arm { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--warn); }

/* ---- the three piles ---- */
.lb-sets { display: grid; grid-template-columns: repeat(auto-fit, minmax(268px, 1fr)); gap: 14px; }
.lb-set { border: 1px solid var(--border-strong); border-radius: var(--r-lg);
  background: var(--panel); box-shadow: var(--shadow-sm); overflow: hidden; }
.lb-set .h { padding: 12px 14px 10px; border-bottom: 1px solid var(--border);
  border-left: 5px solid var(--faint); }
.lb-set.rts .h { border-left-color: var(--ok); }
.lb-set.acc .h { border-left-color: var(--accent); }
.lb-set.mon .h { border-left-color: var(--info); }
.lb-set .h .nm { display: block; font-size: 19px; font-weight: 800; letter-spacing: -0.02em; }
.lb-set .h .wh { display: block; font-size: 12.5px; color: var(--muted); margin-top: 3px; }
.lb-set .b { padding: 11px 14px 13px; }
.lb-set .cnt { font-size: 30px; font-weight: 800; letter-spacing: -0.03em;
  line-height: 1; font-variant-numeric: tabular-nums; }
.lb-set .cnt small { font-size: 12.5px; font-weight: 600; color: var(--muted); letter-spacing: 0; margin-left: 6px; }
.lb-set .f { display: flex; justify-content: space-between; align-items: center; gap: 10px;
  padding: 10px 14px; border-top: 1px solid var(--border); background: var(--panel3); }
/* A stack of paper, shrinking as it goes on. */
.lb-set .fan { display: flex; gap: 2px; align-items: flex-end; height: 30px; margin-bottom: 9px; }
.lb-set .fan i { width: 6px; height: 100%; display: block; border-radius: 2px 2px 0 0;
  background: var(--chip); border: 1px solid var(--border-strong); }
.lb-set .fan i.gone { opacity: 0.22; height: 68%; }

.lb-row { display: grid; grid-template-columns: 54px minmax(160px, 1fr) minmax(180px, 1.2fr) auto;
  gap: 12px; align-items: center; padding: 9px 15px; border-top: 1px solid var(--border); font-size: 13px; }
.lb-row:hover { background: var(--panel3); }
.lb-row .pn { font-size: 11px; color: var(--muted); text-align: center;
  border-right: 1px solid var(--border); padding-right: 8px; }

.lb-sheets { display: flex; flex-direction: column; gap: 6px; padding: 10px 4px 12px 34px; }
.lb-sheet { display: grid; grid-template-columns: 54px auto minmax(160px, 1fr) auto auto;
  gap: 10px; align-items: center; padding: 7px 10px; font-size: 12.5px;
  border: 1px solid var(--border); border-radius: var(--r); background: var(--panel); }
.lb-sheet.on { border-color: var(--ok-bd); background: var(--ok-bg); }
.lb-sheet .pn { font-size: 11px; color: var(--muted); }
.lb-sheet .tick { color: var(--accent-deep); word-break: break-word; }
tr.done td { opacity: 0.62; }

/* ============================================================================
   THE RMA ROAD (0130) — the register is step one, and the road is the nav.

   Replaces two components that carried the same numbers stacked on top of each
   other: `.kpi-row.rma-kpis` (five tiles) and the eight-entry `.tabs` strip
   under it. "Awaiting the gate" was a tile AND a tab badge, printed one
   directly above the other, and the page spent ~240px saying it before any
   work appeared.

   NAMING. Every state modifier in here is prefixed `rw-`. styles.css owns bare
   single-word utility classes at the top level — `.soon` (line ~221) is a 10px
   grey pill — so an unprefixed `.rw-row.soon` silently inherits one and the
   component comes out looking broken in a way that reads as your own bug.
   ========================================================================== */

/* --------------------------------------------------------- the one-liner --
   One sentence under the rail saying what this stop is and what is wrong with
   it. It replaces the stack of muted paragraphs each lens used to open with. */
.rw-line { max-width: 920px; font-size: 13.5px; color: var(--muted); margin: 14px 0; line-height: 1.62; }
.rw-line b { color: var(--text); }
.rw-line .hot { color: var(--bad); font-weight: 700; }

/* ------------------------------------------------------------- the list --
   A grid, not a ten-column table where four columns say "—" on every row. */
.rw-list { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm); overflow: hidden; margin-bottom: 14px; }
.rw-head { display: flex; align-items: center; gap: 12px; padding: 11px 15px;
  border-bottom: 1px solid var(--border); background: var(--panel3); }
.rw-head h2 { margin: 0; font-size: 12px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted); }
.rw-head .rw-sub { margin-top: 0; font-size: 12.5px; color: var(--faint); font-weight: 500; }
.rw-head .sp { flex: 1; }

.rw-cols, .rw-row { display: grid; align-items: center; gap: 14px; padding: 10px 15px;
  grid-template-columns: var(--rw-grid, 20px minmax(200px, 2.1fr) minmax(126px, 1.1fr)
    minmax(150px, 1.3fr) 108px 82px minmax(118px, auto)); }
.rw-cols { padding-top: 8px; padding-bottom: 8px; font-size: 10.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--faint); border-bottom: 1px solid var(--border); }
.rw-row { border-bottom: 1px solid var(--border); position: relative; transition: background .1s; }
.rw-row:last-child { border-bottom: 0; }
.rw-row:hover { background: var(--panel3); }
/* ONE STRIPE, not a row washed red. A red row shouts the same sentence the
   pill inside it already said, and the standing rule is one alarm per fact.
   The stripe is a pointer; the pill is the statement. */
.rw-row::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: transparent; }
.rw-row.rw-late::before { background: var(--bad); }
.rw-row.rw-soon::before { background: var(--warn); }
.rw-p1, .rw-p2 { display: block; }
.rw-p1 { font-weight: 700; font-size: 14px; letter-spacing: -.008em; }
.rw-p2 { font-size: 12px; color: var(--muted); margin-top: 1px; }
.rw-p1.sm { font-size: 13px; }
.rw-r { text-align: right; }
.rw-empty { padding: 26px 16px; text-align: center; color: var(--muted); font-size: 13.5px; }
/* pageFoot() drops its own `.fb-foot` in; it only needs a rule off the rows. */
.rw-list .fb-foot { border-top: 1px solid var(--border); }

/* ---------------------------------------------------------- the age bar --
   How long it has sat there, as a length. A column of numbers makes the reader
   compare by eye; a bar makes the worst row obvious from across the desk. */
.rw-age { display: flex; align-items: center; gap: 8px; }
.rw-age i { flex: 1; height: 5px; border-radius: 3px; background: var(--panel2); overflow: hidden; display: block; }
.rw-age i > b { display: block; height: 100%; background: var(--ok); border-radius: 3px; }
.rw-age.rw-soon i > b { background: var(--warn); }
.rw-age.rw-late i > b { background: var(--bad); }
.rw-age span { font-size: 12.5px; font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; }
.rw-age.rw-late span { color: var(--bad); }

/* ----------------------------------------- the shipment a run of rows is in --
   RMA's Shipped / Packed / Closed lists (2026-09-16). It spans the whole grid
   rather than sitting in a column, because it is a heading over the rows under
   it and not a value belonging to any one of them — `grid-column: 1 / -1` is
   what makes a band possible inside a `.rw-list` without leaving the grid. */
.rw-band { grid-column: 1 / -1; display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 9px 15px; background: var(--panel3); border-bottom: 1px solid var(--border);
  border-top: 1px solid var(--border); font-size: 12.5px; color: var(--muted); }
.rw-row + .rw-band { margin-top: 2px; }
.rw-band-id { font-weight: 700; font-size: 13.5px; letter-spacing: -.008em; }
.rw-band-sup { font-weight: 600; color: var(--text); }
.rw-band-f b { color: var(--text); font-weight: 600; }
/* Pushes the alarm and the verb to the far end, so every band's right edge
   lines up however many facts the left-hand side happens to carry. */
.rw-band-sp { flex: 1; min-width: 0; }

/* ------------------------------------------------- how much of it is back --
   The RMA shipments list (2026-09-15). Same idea as the age bar and
   deliberately NOT the same class: this one fills up as a good thing happens,
   so it is green at 100% where the age bar is red, and sitting them on one
   rule would mean one of the two lying about what full means. */
.rw-back { display: block; }
.rw-back i { display: block; height: 5px; border-radius: 3px; background: var(--panel2); overflow: hidden; }
.rw-back i > b { display: block; height: 100%; background: var(--ok); border-radius: 3px; }

/* A line that is already settled steps back: the rows that still need a person
   are the ones the eye should land on. */
tr.rw-settled > td { background: var(--panel3); }

.rw-acts { display: flex; gap: 6px; justify-content: flex-end; align-items: center; }
/* Secondary verbs wait for the mouse. The primary one never hides — a queue
   with an invisible verb is a queue nobody works. */

/* WHO IT GOES BACK TO, typed into the row itself (RMA, approved). A datalist
   input rather than a select: the vendor list is long enough to want typing
   and short enough not to want a modal. It carries a row's height, not a
   form field's, so a table of them still reads as a table. */
/* The supplier picker in an RMA row. A select since 2026-09-16 — it was a bare
   `<input list=…>`, which is the one control in this app that browsers each
   draw their own way and that carries none of the house chrome. Sized down a
   notch because it sits inside a table row, not a form. */
.sup-in { padding: 6px 9px; font-size: 13px; }
.pd-f dd .sup-in { width: 240px; text-align: left; font-weight: 500; }

/* ------------------------------------------------------ the consignment --
   Was a card head, a six-field definition list, then a table. The two numbers a
   dispatcher actually has — can this go today, and how much of it is in the
   carton — were the fourth and fifth things down the page. */
/* The pack tick IS a tick. It was a button labelled "Packed it" that became a
   button labelled "Un-pack" — two words to read before one click, forty times
   a morning, standing up, holding a card. */

/* ----------------------------------------------------- queue and panel --
   The bench is the one place somebody sits and works a list. The app sent them
   to a full page per part and back again; twenty parts was forty navigations
   and forty lost scroll positions. */

/* The verdict buttons say what they DO, because three of the four take the
   part back out of the pipeline and that is not recoverable by guessing. */

/* ---------------------------------------------------------- the journey --
   The detail page spread one part's life over four cards. It is a road. */
.rw-tl { list-style: none; margin: 0; padding: 14px 17px 16px 22px; }
.rw-tl li { position: relative; padding: 0 0 15px 22px; border-left: 2px solid var(--border-strong); }
.rw-tl li:last-child { padding-bottom: 0; border-left-color: transparent; }
.rw-tl li::before { content: ""; position: absolute; left: -7px; top: 3px; width: 11px; height: 11px;
  border-radius: 50%; background: var(--panel); border: 2px solid var(--border-strong); }
.rw-tl li.done::before { background: var(--ok); border-color: var(--ok); }
.rw-tl li.now::before { background: var(--accent); border-color: var(--accent); box-shadow: 0 0 0 4px var(--accent-wash); }
.rw-tl .t { font-size: 13.5px; font-weight: 700; }
.rw-tl .s { font-size: 12.5px; color: var(--muted); }
.rw-tl li.todo .t { color: var(--faint); font-weight: 600; }

/* A DISABLED BUTTON MUST LOOK DISABLED. There was no rule for this anywhere in
   the file, so "Hand to logistics" on a bucket with two unpacked parts rendered
   at full-strength Aftershock red and read as clickable. Global on purpose:
   every disabled button in the app was lying, not just this one. */
.btn:disabled, .btn[disabled] { opacity: .42; cursor: not-allowed; box-shadow: none; }
.btn:disabled:hover, .btn[disabled]:hover { background: var(--panel); border-color: var(--border-strong); filter: none; }
.btn.primary:disabled:hover, .btn.primary[disabled]:hover { background: var(--accent-deep); border-color: var(--accent-deep); }

/* ---- money on the sales order (0125's model, finally on a screen) ----------
   THE SAME TREATMENT THE INVOICE'S TOTALS BLOCK ALREADY USES, deliberately.
   The first pass invented its own — a warn-coloured band behind the balance,
   five type sizes, a forty-word grey paragraph — and read as a pile of parts
   rather than a panel. One concept, one visual language.

   It shares the bottom row with Notes (.so-foot). On its own it was a narrow
   card marooned on a full-width row with a metre of nothing beside it — and
   Notes was doing the same thing directly underneath. Two lonely cards make a
   worse page than one paired row. The grid sizes it now, so it needs no
   max-width of its own; the button still sits in the card's own head (R-UI-2). */
.so-foot { align-items: flex-start; }
.so-foot > .so-money { flex: 1 1 380px; max-width: 560px; }
/* The notes column is not a .card — it is a heading plus a mounted panel — so it
   needs the flex behaviour .cols only gives its card children. */
.so-foot-notes { flex: 1 1 380px; min-width: 260px; }
.so-foot-notes .sec-head { margin-top: 0; }
.so-money-sum { padding: 12px 16px 14px; }
.so-money .mrow { display: flex; justify-content: space-between; gap: 16px; padding: 3px 0; font-size: 13px; }
/* The one figure somebody opened this card for: weighted and ruled off, the way
   "Balance due" is on the invoice. No colour — a coloured block for a number
   that is USUALLY non-zero is a warning that cries every day. */
.so-money .mrow.strong { font-weight: 600; margin-top: 7px; padding-top: 9px; border-top: 1px solid var(--border); }
/* Except this one, which is genuinely exceptional: we are holding their money. */
.so-money .mrow.back { color: var(--bad); }
/* A hold that has been CLEARED. History, not an alarm — one muted line where
   the red bar used to be, so "who let that order out, and what did they find"
   is answerable without opening the ledger. A second red banner for a hold
   somebody already dealt with is the same fact alarming twice. */
.so-hold-past { font-size: 12.5px; margin: -2px 0 12px; }
.so-money-note { font-size: 11.5px; line-height: 1.5; margin-top: 9px; }
.so-money-empty { font-size: 12.5px; padding: 0 16px 16px; }
.so-money table { border-top: 1px solid var(--border); }
.so-money .chip { margin-left: 4px; }

/* A QUIETER chip: a provenance note, not a status. "entered by hand" beside a
   payment is context, and giving it the same weight as the amount would make
   every row shout. */
.chip.q { background: transparent; border-style: dashed; color: var(--faint); font-weight: 400; }

/* ============================================================================
   0131 — buckets as tiles, and the packing screen.

   The rail, the flow diagram and the per-row pills are GONE. The navigation is
   the app's own `.tabs` component over `rma_requests.status`, which is why
   there is nothing here for it: nine statuses, ordinary words, no new chrome.
   ========================================================================== */

/* Plain facts on a row. Not a pill — a pill is an alarm, and "2 files" is not
   an alarm. Half this module's ugliness was colour with no meaning behind it. */
.plain { color: var(--muted); font-size: 12.5px; }

/* ------------------------------------------------------------- the tiles --
   One per supplier per branch, which is the grain a person actually works at:
   you are packing Leader Computers this morning, not "the outbound queue".
   Replaces a page of stacked cards you had to scroll past four other people's
   consignments to reach your own. */
.rw-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; max-width: 1080px; }
.rw-tile { position: relative; display: block; text-decoration: none; color: var(--text);
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: 14px 16px 13px; box-shadow: var(--shadow-sm);
  transition: border-color .12s, box-shadow .12s, transform .06s; }
.rw-tile:hover { border-color: var(--border-strong); box-shadow: var(--shadow-md); }
.rw-tile:active { transform: translateY(1px); }
.rw-tile.hot { border-color: var(--bad-bd); }
.rw-tile .sup { display: block; font-weight: 700; font-size: 15px; letter-spacing: -.01em; }
.rw-tile .wh { display: block; font-size: 12px; color: var(--muted); }
.rw-tile .big { display: block; font-size: 30px; font-weight: 800; letter-spacing: -.03em; line-height: 1.1;
  margin-top: 8px; font-variant-numeric: tabular-nums; }
.rw-tile .big span { font-size: 13px; font-weight: 600; color: var(--muted); letter-spacing: 0; }
.rw-tile .money { display: block; font-size: 13px; font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; }
.rw-tile .foot { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.rw-tile .bar { flex: 1; height: 6px; border-radius: 4px; background: var(--panel2); overflow: hidden; }
.rw-tile .bar i { display: block; height: 100%; background: var(--info); }
.rw-tile .bar.done i { background: var(--ok); }
.rw-tile .pct { font-size: 11.5px; font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; }
.rw-tile .age { position: absolute; top: 13px; right: 14px; font-size: 11.5px; font-weight: 700;
  color: var(--muted); font-variant-numeric: tabular-nums; }
.rw-tile.hot .age { color: var(--bad); }

/* --------------------------------------------------------- the packing --
   Parts on the left, boxes on the right. Assigning a part to a box is what
   "packed" now means, so it is one dropdown and not a button whose label flips
   between two words. */
.rw-boxwrap { display: grid; grid-template-columns: minmax(360px, 1.25fr) minmax(330px, 1fr);
  gap: 14px; align-items: start; max-width: 1140px; }
@media (max-width: 1120px) { .rw-boxwrap { grid-template-columns: 1fr; } }

.rw-prt { display: grid; gap: 10px; align-items: center; padding: 9px 15px; font-size: 13.5px;
  grid-template-columns: minmax(150px, 1.7fr) minmax(88px, .8fr) minmax(84px, .8fr) 124px;
  border-bottom: 1px solid var(--border); }
.rw-prt:last-child { border-bottom: 0; }
.rw-prt:hover { background: var(--panel3); }
/* A boxed part reads as done at a glance, so the eye lands on what is left. */
.rw-prt.inbox { background: color-mix(in srgb, var(--ok-bg) 30%, transparent); }
.rw-prt > span > b { display: block; }
.rw-prt select { font: inherit; font-size: 13px; width: 100%; padding: 4px 8px; border-radius: var(--r);
  border: 1px solid var(--border-strong); background: var(--panel); color: var(--text); }

.rw-box { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm); margin-bottom: 12px; overflow: hidden; }
.rw-box.full { border-color: var(--ok-bd); }
.rw-bh { display: flex; align-items: center; gap: 10px; padding: 11px 15px;
  background: var(--panel3); border-bottom: 1px solid var(--border); }
.rw-bh b { font-size: 14px; }
.rw-bh .sp { flex: 1; }
.rw-dims { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; padding: 12px 15px; }
.rw-dims label { display: block; font-size: 10.5px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--faint); margin-bottom: 3px; }
.rw-dims input { font: inherit; font-size: 13.5px; width: 100%; padding: 6px 9px; border-radius: var(--r);
  border: 1px solid var(--border-strong); background: var(--panel); color: var(--text);
  font-variant-numeric: tabular-nums; }
/* A blank measurement is the thing stopping the consignment, so it is marked
   on the field itself rather than only in a sentence underneath. */
.rw-dims input.miss { border-color: var(--bad-bd); background: color-mix(in srgb, var(--bad-bg) 40%, var(--panel)); }
.rw-contents { padding: 0 15px 12px; font-size: 12.5px; color: var(--muted); }
.rw-contents .chip { display: inline-block; font-size: 11.5px; font-weight: 600; padding: 1px 8px;
  margin: 2px 3px 0 0; border-radius: var(--r-pill); background: var(--chip);
  border: 1px solid var(--border); color: var(--text); }

.rw-send { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 13px 15px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--shadow-sm); }
.rw-why { font-size: 12.5px; color: var(--muted); }
.rw-why.ready { color: var(--ok); font-weight: 600; }

/* A bench that holds PARTS (0131) reads as its own thing on the plan: loose
   components stand on it, not machines, so the whole box is tinted rather than
   just its head. 0156 made that the `parts_here` flag instead of the
   `test_bench` key, so a diagnosis bay the owner invents gets it too.
   Everything else about it — drag, resize, spots — is an ordinary station. */
.pf-st.parts { border-color: var(--info-bd); background: color-mix(in srgb, var(--info-bg) 45%, var(--panel)); }

/* KINDS OF PLACE (0156): the furniture catalogue, under the floor editor's
   toolbar. A wide table because it is one — eight tickboxes per row — but the
   tickbox columns are hugged so the names and the actions stay readable. */
.pf-kinds { margin-bottom: 12px; }
.pf-kinds-t th.c, .pf-kinds-t td.c { width: 1%; text-align: center; white-space: nowrap; padding-left: 6px; padding-right: 6px; }
.pf-kinds-t td.nowrap { white-space: nowrap; }
.pf-kinds-t input[type="text"] { width: 72px; }
.pf-kinds-t select { min-width: 84px; }

/* ---- The sales order header: ONE panel (owner, 2026-09-01) ----------------
 *
 * It was five `.card`s in `.cols`, and `.cols > .card { flex: 1; min-width: 260px }`
 * is the wrong rule for five things with nothing in common: a one-line "Ship
 * from" was handed the same width as a five-line address, every card stretched
 * to the height of the tallest, and the Promise card's own `max-width: 620px`
 * fought the flex share it had just been given. That is where the jank came
 * from — not the content.
 *
 * These facts are read together, so they are ONE object: columns divided by
 * hairlines rather than five boxes floating on the page. Fixed fractional
 * columns, never auto-fit — auto-fit is what stretches a short cell to fill
 * space, which is the bug we are leaving behind (R-UI-1). The buttons stay in
 * the cell they act on (R-UI-2).
 */
.so-panel { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
            box-shadow: var(--shadow-sm); overflow: hidden; margin-bottom: 14px; }
/* A cell's label is a title, not an eyebrow (P0-1/P0-2, deslop 2026-09-21):
   sentence case at the tile-label size, the same as `.stat-label`. Uppercase
   small-caps survive in two places only — `thead th` and the masthead kicker —
   and 10.5px is below the type ramp. */
.so-panel .kicker { display: block; font-size: 12.5px; font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--muted); }

.so-facts { display: grid; grid-template-columns: minmax(215px, 1.05fr) minmax(215px, 1.05fr) minmax(150px, 0.6fr) minmax(245px, 1fr); }
.so-facts.no-due { grid-template-columns: minmax(215px, 1.15fr) minmax(215px, 1.15fr) minmax(150px, 0.7fr); }
.so-facts > .f { padding: 14px 18px 16px; border-left: 1px solid var(--border); min-width: 0; }
.so-facts > .f:first-child { border-left: 0; }
/* The promise is the one cell here you ACT on, so it is tinted. */
.so-facts > .f.due { background: var(--panel3); }
.f-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 24px; margin-bottom: 8px; }
.so-facts .f b { font-size: 14px; }
.so-facts .f .line { font-size: 12.5px; margin-top: 3px; }
.so-facts .f .line.dim { color: var(--muted); }
.so-facts .f .addr { font-size: 12.5px; margin-top: 5px; line-height: 1.65; }
.so-due-big { font-size: 26px; font-weight: 650; letter-spacing: -0.01em; line-height: 1.15; font-variant-numeric: tabular-nums; }
.so-due-why { font-size: 12px; color: var(--muted); margin-top: 5px; line-height: 1.55; }
/* Two columns before one: a 640px-wide pane should pair the addresses, not
   stack four cells into a ribbon. */
@media (max-width: 1060px) {
  .so-facts, .so-facts.no-due { grid-template-columns: 1fr 1fr; }
  .so-facts > .f:nth-child(odd) { border-left: 0; }
  .so-facts > .f:nth-child(n + 3) { border-top: 1px solid var(--border); }
}
@media (max-width: 640px) {
  .so-facts, .so-facts.no-due { grid-template-columns: 1fr; }
  .so-facts > .f { border-left: 0; }
  .so-facts > .f + .f { border-top: 1px solid var(--border); }
}

/* The timeline is a SEQUENCE, so it reads left-to-right across the panel's
   foot instead of down a card. A step that has not happened greys out and says
   so — "not yet" and "—" are different facts, and the dash was doing both. */
.so-rail { display: flex; flex-wrap: wrap; border-top: 1px solid var(--border); background: var(--panel3); }
.so-rail .r-step { flex: 1; min-width: 132px; padding: 9px 18px; border-left: 1px solid var(--border); }
.so-rail .r-step:first-child { border-left: 0; }
.so-rail .r-val { font-size: 13px; font-weight: 600; margin-top: 2px; font-variant-numeric: tabular-nums; }
.so-rail .r-step.pending .r-val { color: var(--faint); font-weight: 500; }
.so-rail .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ok);
                display: inline-block; margin-right: 6px; vertical-align: 1px; }
.so-rail .r-step.pending .dot { background: var(--border-strong); }
.so-rail .r-step.bad .dot { background: var(--bad); }

/* Why the date is the date, at the panel's foot. The MACHINES are not here —
   their step dates are columns on the Production card further down the page,
   which is already one row per machine (owner, 2026-09-01). */
.so-pcs { border-top: 1px solid var(--border); padding: 13px 18px 15px; }
.so-why { font-size: 12px; line-height: 1.75; color: var(--muted); }
/* The explanation folds behind one line; the warnings and the original promise
   stay out because they are alarms, not explanation (say it once). */
.so-why-fold > summary { cursor: pointer; list-style: none; }
.so-why-fold > summary::-webkit-details-marker { display: none; }
.so-why-fold[open] > summary { margin-bottom: 4px; }
.so-pcs .banner { margin-top: 10px; }
.so-pcs .banner.so-small { font-size: 12px; }
/* Rhythm inside the panel's cells, named so the page carries no inline style. */
.so-facts .f .so-note { margin-top: 4px; }
.so-facts .f .tag { margin-top: 8px; }
/* The consignment sits INSIDE "Deliver to" — how this delivery is travelling
   to the address printed above it, not a fifth subject. A hairline separates
   it; it never gets a column, because a fifth column wraps `.so-facts` onto a
   second row and drags the promise date down with it (owner, 2026-09-21). */
.so-consign { margin-top: 11px; padding-top: 9px; border-top: 1px solid var(--border); }
.so-consign .kicker { margin-bottom: 3px; }
.so-consign b { font-size: 13px; }
.so-consign b + .line { display: inline; margin-left: 7px; }
.so-consign .btn { margin-top: 7px; }
.so-jtq { margin-top: 7px; }
.so-more .pop-menu { min-width: 220px; }

/* THE LINES TABLE (deslop 2026-09-21). One Item column — the code on its own
   line, the name under it — where SKU and Name were two: eleven columns across
   1,120px had the code breaking mid-word and the table 20px wider than its
   card. Numbers never wrap; the item cell is the one column that gives. The
   `.card.np.so-lines` chain is what beats the global `td:nth-child(2/3)` rule. */
.card.np.so-lines td.item-cell { white-space: normal; min-width: 22ch; max-width: 36ch; }
.card.np.so-lines td.item-cell .item-code { white-space: nowrap; }
.card.np.so-lines td.item-cell .item-id { margin-top: 2px; }
.card.np.so-lines td.num { white-space: nowrap; min-width: 0; }
.card.np.so-lines input.ful { width: 90px; }
.line-note { margin-top: 2px; }
.line-note.neg { color: var(--bad); }
.spec-fold { margin-top: 4px; }
.spec-fold > summary { cursor: pointer; }
.spec-list { line-height: 1.7; }

/* Money at the foot of a document: label left, figure right, the total last.
   The invoice page draws this block inline today and can adopt these. */
.money-block { margin-left: auto; width: 330px; max-width: 100%; padding: 10px 16px 4px; font-size: 13px; }
.money-row { display: flex; justify-content: space-between; gap: 16px; padding: 3px 0; }
.money-block .total { padding: 8px 0 0; }

/* The Production card at the order's foot (mfg.js renderSalesOrderProduction):
   step dates on one line, greyed once the step is done. */
#so-production td.date { font-size: 12px; white-space: nowrap; }
#so-production td.date.done { color: var(--faint); }
.so-prod-foot { padding: 10px 14px; }

/* ---- THE HOLD WINDOW (0148, holds.js) -------------------------------------
   One window for every HOLD button. The reasons are BUTTONS rather than radios
   because the choice is the whole first half of the dialog and a radio dot is a
   4px target for a thing somebody presses twenty times a day; the selected one
   is outlined, not tinted, so a red reason keeps reading red.

   `.hd-what` is the part that did not exist before: what this hold is about to
   do, in sentences, above the button that does it. */
.hd-reasons { display: flex; flex-direction: column; gap: 5px; }
.hd-reason { display: block; width: 100%; text-align: left; cursor: pointer;
  background: var(--panel3); border: 1px solid var(--border); border-radius: var(--r); padding: 8px 11px; }
.hd-reason:hover { border-color: var(--border-strong); }
.hd-reason.on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; background: var(--panel2); }
.hd-reason small { display: block; margin-top: 3px; font-size: 12px; color: var(--muted); line-height: 1.5; }
.hd-what { margin-top: 12px; border-top: 1px solid var(--border); padding-top: 10px;
  font-size: 12.5px; line-height: 1.7; color: var(--muted); }
.hd-what div::before { content: "→ "; color: var(--faint); }

/* The settings table behind it. A row is wide on purpose — R-UI-1 says a card is
   the width of its content, and this one's content is eleven decisions about a
   single reason, read across. */
.hd-set td { vertical-align: top; }
.hd-set .hd-where { display: flex; gap: 9px; white-space: nowrap; font-size: 12.5px; }
.hd-set .hd-where label { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; }
.hd-set input[type="text"] { width: 100%; min-width: 11em; }
.hd-set select { min-width: 8.5em; }
.hd-set .hd-aud { display: flex; flex-wrap: wrap; gap: 3px 9px; max-width: 19em; font-size: 12.5px; }
.hd-set .hd-aud label { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; }
.hd-set tr.off { opacity: .55; }

/* ===========================================================================
   CUSTOMER SERVICE — REQUESTS (0147)
   ===========================================================================
   The status vocabulary is DATA: a row in `request_statuses` carries a colour
   token, and these classes are what a token paints. Nine of them, because the
   owner may add statuses and a tenth is one line here — not a code change in
   four files. Nothing branches on colour; it is a label. */
:root {
  --st-slate: #5a6274;  --st-slate-bg: #eef1f6;  --st-slate-bd: #d8dee8;
  --st-mint: #11745a;   --st-mint-bg: #ddf6ec;   --st-mint-bd: #a5e3cd;
  --st-amber: #8a6100;  --st-amber-bg: #fdf0d2;  --st-amber-bd: #f0d99a;
  --st-blue: #1f5fbf;   --st-blue-bg: #e4eefd;   --st-blue-bd: #bcd5f6;
  --st-violet: #5b46b8; --st-violet-bg: #eee9fb; --st-violet-bd: #cfc2f2;
  --st-rose: #b02a5b;   --st-rose-bg: #fde8ef;   --st-rose-bd: #f4c4d6;
  --st-lime: #4a7a10;   --st-lime-bg: #edf7d9;   --st-lime-bd: #cfe6a4;
  --st-teal: #0f6f74;   --st-teal-bg: #dff5f5;   --st-teal-bd: #a9e0e2;
  --st-red: #c62828;    --st-red-bg: #fde5e5;    --st-red-bd: #f3bfbf;
}
:root[data-theme="dark"], body.dark {
  --st-slate: #99a1b1;  --st-slate-bg: #1f232d;  --st-slate-bd: #343a47;
  --st-mint: #5fd0a8;   --st-mint-bg: #0f2620;   --st-mint-bd: #1c4638;
  --st-amber: #e3b341;  --st-amber-bg: #2a2310;  --st-amber-bd: #4d3f16;
  --st-blue: #7aa9f0;   --st-blue-bg: #121d2e;   --st-blue-bd: #1f3350;
  --st-violet: #a894f5; --st-violet-bg: #1c1730; --st-violet-bd: #332a56;
  --st-rose: #f07aa4;   --st-rose-bg: #2c1420;   --st-rose-bd: #4d2135;
  --st-lime: #a8d45e;   --st-lime-bg: #1d2610;   --st-lime-bd: #3a4a1c;
  --st-teal: #5ed0d4;   --st-teal-bg: #0f2426;   --st-teal-bd: #1c4245;
  --st-red: #ff6b6b;    --st-red-bg: #2a1416;    --st-red-bd: #4d2024;
}
.st-slate  { background: var(--st-slate-bg);  border-color: var(--st-slate-bd);  color: var(--st-slate); }
.st-mint   { background: var(--st-mint-bg);   border-color: var(--st-mint-bd);   color: var(--st-mint); }
.st-amber  { background: var(--st-amber-bg);  border-color: var(--st-amber-bd);  color: var(--st-amber); }
.st-blue   { background: var(--st-blue-bg);   border-color: var(--st-blue-bd);   color: var(--st-blue); }
.st-violet { background: var(--st-violet-bg); border-color: var(--st-violet-bd); color: var(--st-violet); }
.st-rose   { background: var(--st-rose-bg);   border-color: var(--st-rose-bd);   color: var(--st-rose); }
.st-lime   { background: var(--st-lime-bg);   border-color: var(--st-lime-bd);   color: var(--st-lime); }
.st-teal   { background: var(--st-teal-bg);   border-color: var(--st-teal-bd);   color: var(--st-teal); }
.st-red    { background: var(--st-red-bg);    border-color: var(--st-red-bd);    color: var(--st-red); }

/* ============================================================================
   CUSTOMER SERVICE — the cs- dialect (2026-09-16)
   Replaces the rq- one wholesale. The old inbox was a ten-column table in which
   the question — the only thing on the screen a person actually reads — was
   column five, truncated, at the same weight as nine other columns and fighting
   five coloured things per row. Here the question IS the row, and the only
   colour is the status dot, with red kept for late.
   ========================================================================= */

/* The status pill IS the control. Clicking it opens every status, in every
   direction — there is no transition table anywhere in this feature, so the
   pill never has a refusal to explain. Sentence case, one dot, and the rung on
   the customer ladder as a numeral instead of FIRST or FINAL in capitals. */
.cs-st { display: inline-flex; align-items: center; gap: 6px; background: none; border: 1px solid transparent;
         border-radius: var(--r-pill); padding: 2px 9px 2px 7px; font: inherit; font-size: 12.5px;
         font-weight: 600; color: var(--text); cursor: pointer; white-space: nowrap; }
.cs-st .dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: none; }
.cs-st .txt { color: var(--text); }
.cs-st .n { font-family: var(--mono); font-size: 11px; font-weight: 600; color: var(--muted); }
.cs-st .car { font-size: 9px; color: var(--faint); }
.cs-st:hover { background: var(--panel2); border-color: var(--border); }
.cs-st.solid { border-color: currentColor; padding: 4px 12px 4px 10px; font-size: 13px; }
.cs-st.solid .txt { color: currentColor; }
.cs-st.solid .n { color: currentColor; opacity: 0.75; }
.cs-st.flat { cursor: default; }
.cs-st.flat:hover { background: none; border-color: transparent; }

/* The vocabulary's colour, as ink rather than a filled badge. */
.tone-slate  { color: var(--st-slate); }
.tone-mint   { color: var(--st-mint); }
.tone-amber  { color: var(--st-amber); }
.tone-blue   { color: var(--st-blue); }
.tone-violet { color: var(--st-violet); }
.tone-rose   { color: var(--st-rose); }
.tone-lime   { color: var(--st-lime); }
.tone-teal   { color: var(--st-teal); }
.tone-red    { color: var(--st-red); }
.cs-st.solid.tone-slate  { background: var(--st-slate-bg); }
.cs-st.solid.tone-mint   { background: var(--st-mint-bg); }
.cs-st.solid.tone-amber  { background: var(--st-amber-bg); }
.cs-st.solid.tone-blue   { background: var(--st-blue-bg); }
.cs-st.solid.tone-violet { background: var(--st-violet-bg); }
.cs-st.solid.tone-rose   { background: var(--st-rose-bg); }
.cs-st.solid.tone-lime   { background: var(--st-lime-bg); }
.cs-st.solid.tone-teal   { background: var(--st-teal-bg); }
.cs-st.solid.tone-red    { background: var(--st-red-bg); }

/* The status menu. One menu, opened from a row or from the request itself. */
.cs-menu { position: absolute; z-index: 220; background: var(--panel); border: 1px solid var(--border-strong);
           border-radius: var(--r); box-shadow: var(--shadow-lg); padding: 5px; min-width: 280px; }
.cs-menu .mg { font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--faint);
               font-weight: 700; padding: 7px 9px 4px; }
.cs-menu .mi { display: flex; align-items: center; gap: 9px; width: 100%; background: transparent; border: 0;
               border-radius: var(--r-sm); padding: 6px 9px; font: inherit; font-size: 13px; color: var(--text);
               cursor: pointer; text-align: left; }
.cs-menu .mi:hover { background: var(--panel2); }
.cs-menu .mi .dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: none; }
/* The row's own colour on its dot. `.cs-menu .mi` sets the text colour, which
   outranks the one-class .tone-* and turned every dot black (owner, 2026-09-26). */
.cs-menu .mi .dot { width: 9px; height: 9px; }
.cs-menu .mi.tone-slate .dot { background: var(--st-slate); }
.cs-menu .mi.tone-mint .dot { background: var(--st-mint); }
.cs-menu .mi.tone-amber .dot { background: var(--st-amber); }
.cs-menu .mi.tone-blue .dot { background: var(--st-blue); }
.cs-menu .mi.tone-violet .dot { background: var(--st-violet); }
.cs-menu .mi.tone-rose .dot { background: var(--st-rose); }
.cs-menu .mi.tone-lime .dot { background: var(--st-lime); }
.cs-menu .mi.tone-teal .dot { background: var(--st-teal); }
.cs-menu .mi.tone-red .dot { background: var(--st-red); }
/* ---- the colour picker (owner, 2026-09-26; colour.js, 0310) ----
   Every colour on every settings screen is chosen here: drag the circle across
   the square for how strong and how light, drag along the bar for the hue, or
   type the code. The nine quick picks are the colours the app has always used. */
.cpk { display: inline-flex; align-items: center; gap: 8px; padding: 4px 10px 4px 5px; border: 1px solid var(--border-strong);
       border-radius: var(--r-sm); background: var(--panel); color: var(--text); font: inherit; font-size: 13px; cursor: pointer; }
.cpk:hover { border-color: var(--muted); }
.cpk:focus-visible { outline: none; box-shadow: var(--ring); }
.cpk:disabled { cursor: default; opacity: .6; }
.cpk-sw { width: 20px; height: 20px; border-radius: 5px; background: var(--c); flex: none;
          border: 1px solid color-mix(in oklab, var(--c) 70%, var(--text)); }
.cpk-code { font-family: var(--mono); font-size: 12px; color: var(--muted); }
.cpk-pop { position: absolute; z-index: 1200; width: 244px; background: var(--panel); border: 1px solid var(--border-strong);
           border-radius: var(--r); box-shadow: var(--shadow-lg); padding: 10px; }
.cpk-sv { position: relative; height: 156px; border-radius: var(--r-sm); cursor: crosshair; touch-action: none;
          background: linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(var(--h, 0) 100% 50%)); }
.cpk-knob, .cpk-hknob { position: absolute; width: 18px; height: 18px; margin: -9px 0 0 -9px; border-radius: 50%;
          border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, .5), 0 1px 4px rgba(0, 0, 0, .45); pointer-events: none; }
.cpk-hue { position: relative; height: 14px; margin-top: 12px; border-radius: 7px; cursor: ew-resize; touch-action: none;
           background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); }
.cpk-hknob { top: 50%; background: transparent; }
.cpk-sv:focus-visible, .cpk-hue:focus-visible { outline: none; box-shadow: var(--ring); }
.cpk-bar { display: flex; align-items: center; gap: 7px; margin-top: 12px; }
.cpk-now { width: 28px; height: 28px; border-radius: var(--r-sm); border: 1px solid var(--border-strong); flex: none; }
.cpk-hex { flex: 1; min-width: 0; width: auto; margin: 0; font-family: var(--mono); font-size: 13px; padding: 5px 8px; }
.cpk-presets { display: grid; grid-template-columns: repeat(9, 1fr); gap: 5px; margin-top: 10px; }
.cpk-presets button { aspect-ratio: 1; padding: 0; border-radius: 5px; background: var(--c); cursor: pointer;
                      border: 1px solid color-mix(in oklab, var(--c) 70%, var(--text)); }
.cpk-presets button:hover { outline: 2px solid var(--text); outline-offset: 1px; }

/* A PICKED COLOUR, drawn. One hex code rides as --c; the tint, the border and
   the ink are derived from it against the theme's own panel and text, so the
   same code reads in the light theme and the dark one. A named colour keeps
   its own class and never reaches these rules. */
.hx { --hx-ink: color-mix(in oklab, var(--c) 74%, var(--text)); --hx-bg: color-mix(in oklab, var(--c) 15%, var(--panel));
      --hx-bd: color-mix(in oklab, var(--c) 45%, var(--panel)); }
.badge.hx { background: var(--hx-bg); border-color: var(--hx-bd); color: var(--hx-ink); }
.sw.hx { background: var(--c); border-color: var(--hx-bd); }
.cs-st.hx, .cs-lane-h.hx, .cs-state.hx { color: var(--hx-ink); }
.cs-st.solid.hx { background: var(--hx-bg); }
.cs-menu .mi.hx .dot { background: var(--c); }
.pf-st.t-hx .pf-head { background: var(--hx-bg); color: var(--hx-ink); border-color: var(--hx-bd); }
.cs-menu .mi .lb { color: var(--text); flex: 1; }
.cs-menu .mi .lb .n { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.cs-menu .mi .who { font-size: 11.5px; color: var(--faint); }
.cs-menu .mi.on .lb { font-weight: 700; }
.cs-menu .mi .tick { color: var(--ok); font-weight: 800; }

.cs-av { width: 24px; height: 24px; border-radius: 50%; background: var(--panel2);
         border: 1px solid var(--border-strong); display: inline-flex; align-items: center;
         justify-content: center; font-size: 10.5px; font-weight: 700; color: var(--muted); flex: none; }
.cs-av.none { border-style: dashed; color: var(--faint); font-weight: 400; }

/* An escalation is a SECOND FACT about a request, not a second opinion about
   its status (0174), so on a row it is one small red word beside where the
   request is sitting — never a pill competing with the status. */
.esc-tag { display: inline-flex; align-items: center; font-size: 10.5px; font-weight: 700; letter-spacing: 0.03em;
           text-transform: uppercase; color: var(--bad); border: 1px solid var(--bad-bd); background: var(--bad-bg);
           border-radius: var(--r-sm); padding: 0 5px; margin-right: 5px; }

/* The split and the order inside it are two questions, so they are two selects,
   read as one control because they answer about the same list. */
.cs-sortby { display: inline-flex; align-items: center; gap: 1px; background: var(--panel2);
             border: 1px solid var(--border); border-radius: var(--r); padding: 2px; }
.cs-sortby select { border-color: transparent; background: transparent; font-size: 13px; padding: 5px 7px; }
.cs-sortby select:hover { border-color: var(--border-strong); background: var(--panel); }

/* --------------------------------------------------------------- the inbox */
.cs-grp { display: flex; align-items: center; gap: 8px; padding: 8px 16px; background: var(--panel3);
          border-bottom: 1px solid var(--border); border-top: 1px solid var(--border);
          font-size: 12.5px; font-weight: 700; color: var(--muted); }
.cs-grp:first-child { border-top: 0; }
.cs-grp .n { font-family: var(--mono); font-weight: 600; color: var(--faint); }

.cs-row { display: grid; grid-template-columns: 200px minmax(0, 1fr) 190px; gap: 14px; align-items: start;
          padding: 11px 16px 12px; border-bottom: 1px solid var(--border); cursor: pointer; }
.cs-row:last-child { border-bottom: 0; }
.cs-row:hover { background: var(--panel3); }
.cs-row .held { display: block; font-size: 11.5px; color: var(--faint); margin: 2px 0 0 16px; }
.cs-row .q { font-size: 14.5px; font-weight: 600; line-height: 1.4; color: var(--text);
             display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.cs-row .meta { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-top: 4px;
                font-size: 12.5px; color: var(--muted); }
.cs-row .meta .ref { font-family: var(--mono); font-size: 12px; color: var(--text); background: var(--panel2);
                     border: 1px solid var(--border); border-radius: var(--r-sm); padding: 0 6px; }
.cs-row .meta .late, .cs-state .late, .cs-cd .late { color: var(--bad); font-weight: 600; }
.cs-row .right { display: flex; align-items: center; gap: 9px; justify-content: flex-end; }
.cs-row .age { font-family: var(--mono); font-size: 11.5px; color: var(--faint); text-align: right;
               line-height: 1.3; white-space: nowrap; }
.cs-row .age b { display: block; color: var(--muted); font-weight: 600; }
.cs-row .age.hot b { color: var(--bad); }
.pri { font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--bad);
       border: 1px solid var(--bad-bd); background: var(--bad-bg); border-radius: var(--r-sm); padding: 0 5px; }
.pri.high { color: var(--warn); border-color: var(--warn-bd); background: var(--warn-bg); }

/* ------------------------------------------------------------- one request */
.cs-doc { display: grid; grid-template-columns: minmax(0, 1fr) 316px; gap: 14px; align-items: start; }
@media (max-width: 1020px) { .cs-doc { grid-template-columns: 1fr; } }
.cs-doc-head { margin: 0 0 13px; }
.cs-doc-head .line1 { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-bottom: 5px; }
.cs-doc-head .back { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.cs-doc-head .back:hover { color: var(--accent); }
.cs-doc-head .id { font-family: var(--mono); font-size: 12.5px; color: var(--faint); }
.cs-doc-head h1 { font-size: 25px; line-height: 1.22; margin: 0; max-width: 26ch; letter-spacing: -0.015em; }
.cs-doc-head .askrow { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; flex-wrap: wrap; }
.cs-doc-head .subject { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px;
                        font-size: 13px; color: var(--muted); }
.cs-doc-head .subject .ref { font-family: var(--mono); font-size: 12.5px; background: var(--panel2);
                             border: 1px solid var(--border); border-radius: var(--r-sm); padding: 1px 7px; }

/* ONE line for everything the system noticed. It was four stacked banners, and
   say-it-once says a fact gets one alarm. Nothing in here is a gate. */
.cs-state { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; background: var(--panel);
            border: 1px solid var(--border); border-left: 3px solid currentColor; border-radius: var(--r);
            padding: 9px 14px; margin: 0 0 14px; font-size: 13.5px; box-shadow: var(--shadow-sm); }
.cs-state > span { color: var(--text); }
.cs-state > span.muted { color: var(--muted); }
.cs-state b { font-weight: 600; }

/* The escalation, across the top of the task it is about. */
.cs-esc { display: flex; align-items: flex-start; gap: 11px; background: var(--bad-bg); border: 1px solid var(--bad-bd);
          border-radius: var(--r); padding: 10px 14px; margin: 0 0 14px; font-size: 13.5px; color: var(--bad); }
.cs-esc svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 1.8;
              stroke-linecap: round; stroke-linejoin: round; flex: none; margin-top: 2px; }
.cs-esc b { display: block; }
.cs-esc span { color: var(--bad); opacity: 0.85; }
.cs-esc .sp { flex: 1; }
.cs-esc .btn { background: var(--panel); border-color: var(--bad-bd); color: var(--bad); }
.cs-esc .btn:hover { border-color: var(--bad); color: var(--bad); }

/* Writing the note IS the work, so it is at the top and it carries the only two
   buttons on the page (owner, 2026-09-16). */
.cs-compose { border: 1px solid var(--border-strong); border-radius: var(--r); background: var(--panel2);
              padding: 9px; margin: 0 0 14px; }
.cs-compose textarea { width: 100%; background: var(--panel); border: 1px solid var(--border);
                       border-radius: var(--r-sm); padding: 9px 11px; font: inherit; font-size: 14px;
                       color: var(--text); resize: vertical; min-height: 62px; }
.cs-compose .cbar { display: flex; align-items: center; gap: 7px; margin-top: 8px; flex-wrap: wrap; }
.cs-compose .cbar .sp { flex: 1; }

.cs-tl { position: relative; padding-left: 30px; }
.cs-tl:before { content: ""; position: absolute; left: 11px; top: 6px; bottom: 6px; width: 1px; background: var(--border); }
.cs-ev { position: relative; padding: 0 0 15px; }
.cs-ev .mk { position: absolute; left: -30px; top: 2px; width: 23px; height: 23px; border-radius: 50%;
             background: var(--panel2); border: 1px solid var(--border-strong); display: flex;
             align-items: center; justify-content: center; font-size: 10px; font-weight: 700; color: var(--muted); }
.cs-ev .mk svg { width: 12px; height: 12px; stroke: var(--muted); fill: none; stroke-width: 2;
                 stroke-linecap: round; stroke-linejoin: round; }
.cs-ev .top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 12.5px; color: var(--muted); }
.cs-ev .top b { color: var(--text); font-size: 13.5px; font-weight: 600; }
.cs-ev .body { font-size: 14px; margin-top: 3px; white-space: pre-wrap; line-height: 1.55; }
.cs-ev.sys .body { color: var(--muted); font-size: 13.5px; }
.cs-ev.sys .top b { font-weight: 500; color: var(--muted); }

/* A card the width of its content (R-UI-1): the facts are a column, not a page. */
.cs-facts { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
            box-shadow: var(--shadow-sm); padding: 4px 16px 12px; position: sticky; top: 14px; }
.cs-f { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 10px; align-items: baseline;
        padding: 8px 0; border-bottom: 1px solid var(--border); margin: 0; }
.cs-f:last-of-type { border-bottom: 0; }
.cs-f dt { font-size: 12.5px; color: var(--muted); }
.cs-f dd { margin: 0; font-size: 13.5px; overflow-wrap: anywhere; }
.cs-f dd select, .cs-f dd input { width: 100%; background: var(--panel); border: 1px solid var(--border);
                                  border-radius: var(--r-sm); padding: 4px 7px; font: inherit;
                                  font-size: 13.5px; color: var(--text); }
.cs-f dd select:hover, .cs-f dd input:hover { border-color: var(--border-strong); }
.cs-f dd .sub { display: block; font-size: 12px; color: var(--faint); margin-top: 2px; }
.cs-hold { background: var(--warn-bg); border: 1px solid var(--warn-bd); color: var(--warn);
           border-radius: var(--r); padding: 8px 11px; margin: 12px 0 0; font-size: 12.5px; }

/* --------------------------------------------------------------- the board */
/* Same register as the list, in columns — and every lane accepts every card.
   There is no Escalated lane: an escalation is not a status (0174). */
.cs-lanes { display: flex; gap: 11px; overflow-x: auto; padding-bottom: 12px; align-items: flex-start; }
.cs-lane { flex: 0 0 268px; background: var(--panel3); border: 1px solid var(--border);
           border-radius: var(--r-lg); padding-bottom: 8px; }
.cs-lane.dragover { outline: 2px dashed var(--accent); outline-offset: -3px; }
.cs-lane-h { display: flex; align-items: center; gap: 7px; padding: 10px 12px 3px; font-size: 13px; font-weight: 600; }
.cs-lane-h .dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: none; }
.cs-lane-h .lb { color: var(--text); }
.cs-lane-h .lb .n { font-family: var(--mono); font-size: 12px; color: var(--muted); }
.cs-lane-h .n { margin-left: auto; font-family: var(--mono); font-size: 12px; color: var(--faint); font-weight: 600; }
.cs-lane-sub { padding: 0 12px 8px 27px; font-size: 11.5px; color: var(--faint); }
.cs-lane-b { padding: 0 8px; display: flex; flex-direction: column; gap: 8px; min-height: 40px; }
.cs-lane-b .empty { border: 0; padding: 6px 0; font-size: 12px; }
.cs-cd { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r);
         box-shadow: var(--shadow-sm); padding: 10px 11px; cursor: grab; }
.cs-cd:hover { border-color: var(--border-strong); box-shadow: var(--shadow-md); }
.cs-cd.dragging { opacity: 0.35; }
.cs-cd .q { font-size: 13.5px; font-weight: 600; line-height: 1.38; display: -webkit-box;
            -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.cs-cd .meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 7px;
               font-size: 12px; color: var(--muted); }
.cs-cd .meta .ref { font-family: var(--mono); font-size: 11.5px; background: var(--panel2);
                    border: 1px solid var(--border); border-radius: var(--r-sm); padding: 0 5px; color: var(--text); }
.cs-cd .foot { display: flex; align-items: center; gap: 7px; margin-top: 8px; padding-top: 7px;
               border-top: 1px solid var(--border); font-family: var(--mono); font-size: 11.5px; color: var(--faint); }
.cs-cd .foot .sp { flex: 1; }

/* Departments, in user settings. A drag handle, a swatch, a code, a count. */
.dept-row { display: flex; align-items: center; gap: 10px; padding: 8px 11px; border: 1px solid var(--border);
            border-radius: var(--r); background: var(--panel); margin-bottom: 7px; }
.dept-row .sw { width: 16px; height: 16px; border-radius: 5px; border: 1px solid; flex: none; }
.dept-row .nm { font-weight: 700; font-size: 13.4px; min-width: 170px; }
.dept-row .cd { font-size: 11.4px; color: var(--muted); min-width: 46px; }
.dept-row .ct { font-size: 12.2px; color: var(--muted); }
.dept-row .sp { flex: 1; }
.dept-row.off { opacity: 0.55; }

/* ============================================================================
   THE SHELL ON A SMALL SCREEN (2026-09-15)
   Until today the shell had no small-screen form at all: the rail was 238px at
   every width and .view kept its 32px gutters, so a 390px phone was left with
   73px of page — an 11-column table read through a keyhole. The sixty-odd
   media queries above this line are all INSIDE that keyhole; none of them
   could help. This section is the shell's answer, and it sits at the END of
   the file on purpose: every override here is same-specificity as the rule it
   overrides, so source order is what makes it win.

   Two numbers, both already in use above, so there is no third breakpoint to
   learn:  900px — the rail becomes a drawer (a portrait tablet is one-handed;
   a landscape one keeps the rail);  640/760px — the phone tier, where the
   search box gives way to the palette and form controls go to 16px.
   ========================================================================== */

/* The burger and the search glyph exist at every width and are simply not
   shown on a desk. Two flags in the stylesheet, not two DOM trees. */
.nav-burger, .topbar-searchbtn {
  display: none;
  align-items: center; justify-content: center; flex: none;
  width: 40px; height: 40px; padding: 0; border-radius: var(--r);
  border: 1px solid var(--border); background: var(--panel); color: var(--muted); cursor: pointer;
  transition: color .12s, background .12s, border-color .12s;
}
.nav-burger svg, .topbar-searchbtn svg { width: 20px; height: 20px; }
.nav-burger:hover, .topbar-searchbtn:hover { color: var(--text); background: var(--panel2); border-color: var(--border-strong); }
.nav-burger:focus-visible, .topbar-searchbtn:focus-visible { outline: none; box-shadow: var(--ring); }
.scrim { display: none; }

@media (max-width: 900px) {
  /* The rail leaves the flow and slides in over the page. `visibility` rides
     along so a closed drawer is out of the tab order — otherwise a keyboard
     walks through eleven invisible links before it reaches the page. The
     0s/.18s delay pairs let the slide-out finish before the drawer vanishes. */
  .sidebar {
    position: fixed; inset: 0 auto 0 0; height: auto;
    width: min(300px, 86vw);
    z-index: 90;
    transform: translateX(-102%);
    visibility: hidden;
    transition: transform .18s ease-out, visibility 0s linear .18s;
  }
  html.nav-open .sidebar { transform: none; visibility: visible; box-shadow: var(--shadow-lg);
    transition: transform .18s ease-out, visibility 0s; }
  .scrim { position: fixed; inset: 0; z-index: 89; background: var(--overlay); }
  html.nav-open .scrim { display: block; }
  /* The page holds still under the scrim; the drawer scrolls on its own. */
  html.nav-open { overflow: hidden; }
  .nav-burger { display: inline-flex; }

  .topbar { padding: 0 12px; gap: 10px; }
  .view { padding: 16px 14px 90px; }
  .page-head { margin-bottom: 16px; }
  .page-head h1 { font-size: 22px; }
  .card { padding: 15px; }

  /* The table doctrine (see "One table system") puts every table in a .card.np
     so the CARD scrolls sideways and the page never does. About thirty tables
     never joined; on a desk the card was wide enough to hide that, on a phone
     each one drags the whole page sideways. This is the doctrine applied to
     them from the outside, narrow screens only. The scroll clips a popover
     that hangs below the table — a trade the house already lives with, since
     the Build-a-PC slot picker sits inside a scrolling .card.np today. */
  .card:has(> table):not(.np) { overflow-x: auto; scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }

  /* 380px of toast on a 390px screen hung off the left edge. */
  .toast { left: 12px; right: 12px; bottom: 12px; max-width: none; }
}
@media (prefers-reduced-motion: reduce) { .sidebar, html.nav-open .sidebar { transition: none; } }

/* Fingers, not pointers — but only where the drawer is also up. A landscape
   tablet has a coarse pointer too, and 44px rows on every .btn.sm would cost
   it a third of every table for a target that is already close enough. */
@media (pointer: coarse) and (max-width: 900px) {
  .btn { min-height: 44px; }
  .btn.sm { min-height: 38px; }
  #nav a { padding: 11px; }
  .topbar-jump a { min-height: 40px; }
  .theme-flip, .lang-flip { padding: 10px 12px; }
}

/* iOS Safari zooms the whole page into any field whose text is under 16px,
   and then leaves it there. Every control in this file is 13–14px, some of
   them by a two-class rule, so this is the [hidden] argument again: one rule
   that always wins beats remembering it at forty call sites. */
@media (max-width: 760px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]),
  select, textarea { font-size: 16px !important; }
}

@media (max-width: 640px) {
  /* The search BOX goes; search does not. The palette is the same
     wireGlobalSearch behind a full-width, 17px field — a better phone search
     than a 200px input with a 200px dropdown was ever going to be. */
  .topbar-search { display: none; }
  .topbar-searchbtn { display: inline-flex; }
  .palette { padding: 10px; padding-top: 7vh; }
  /* Title, then the buttons on their own row — predictable, rather than
     "wraps when it has to" deciding differently on every page. */
  .page-head { flex-direction: column; align-items: stretch; }
  .filter-bar select { flex: 1 1 140px; max-width: none; }
  .modal-overlay { padding: 12px; }
  .modal { padding: 18px 16px; }
  .modal-actions { flex-wrap: wrap; }
}

/* Machines picked onto an order (unitpicker.js). A pick STAYS IN THE LIST and
   turns green at the top of it, rather than leaving the list and reappearing as
   a tag above it: with twenty batches on screen a tag says "something was
   added" and the row says "THAT one was added" (owner, 2026-09-18). */
.up-row.up-on > td { background: var(--ok-bg); }
.up-row.up-on > td:first-child { box-shadow: inset 3px 0 0 var(--ok); }

/* ============================================================================
   PEOPLE — the merged Users + Departments screen (ch 18 §12.1)

   A team is a card and its people are inside it, so the page is a grid of cards
   sized to their contents rather than one page-wide table of everybody (R-UI-1).
   The controls that act on a team sit in that team's head (R-UI-2), which is the
   whole reason the two screens became one: the control that was missing was
   "which team is this person in", and it had nowhere to live while the people
   were on one page and the teams on another.
   ========================================================================== */
/* 540px is not a round number, it is the width of a person: a name and an email
   on the left, and on the right the four controls that act on them, without the
   row wrapping. Below that the row wraps in two halves and still reads. */
/* `minmax(540px, …)` is a FLOOR, so on any window narrower than 540px the track
   stayed 540px and the tiles hung off the side of the page — the same escape as
   the controls below, reached by shrinking the window instead of filling the
   row. `min()` lets the floor give way to the screen when the screen is the
   smaller of the two; above 540px nothing changes. */
.people-teams { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(540px, 100%), 1fr));
                gap: 14px; align-items: start; margin-bottom: 14px; }
.people-teams > .card { margin-bottom: 0; }

.team-head { display: flex; align-items: center; gap: 9px; margin: 0 0 3px; }
.team-head .sw { width: 15px; height: 15px; border-radius: 5px; border: 1px solid; flex: none; }
.team-head .nm { font-weight: 700; font-size: 14.5px; letter-spacing: -0.006em; }
.team-head .cd { font-size: 11px; font-weight: 700; letter-spacing: 0.05em; color: var(--muted); text-transform: uppercase; }
.team-head .sp { flex: 1; }
.team-nums { font-size: 12.2px; color: var(--muted); margin: 0 0 9px; }

/* Not placed yet is the first card and it is meant to be noticed: it shrinks to
   nothing as people are given a department. */
.card.no-team { border-color: var(--warn-bd); grid-column: 1 / -1; }
.no-team-count { font-size: 11.5px; font-weight: 700; padding: 1px 8px; border-radius: var(--r-pill);
                 background: var(--warn-bg); border: 1px solid var(--warn-bd); color: var(--warn); }

/* --------------------------------------------------------------------------
   DEPARTMENT → TEAM → POSITION (0212)

   A department is a BAND, not a card: its teams are already cards and a card
   around cards is a box around a box (R-UI-1). The band is a rule and a heading
   with the buttons that act on the department beside it (R-UI-2), and the team
   tiles sit under it in the same grid they always used.
   -------------------------------------------------------------------------- */
.dept-band { margin: 0 0 6px; }
.dept-band.off { opacity: 0.6; }
.dept-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
             padding: 0 2px 7px; margin: 16px 0 11px; border-bottom: 1px solid var(--border); }
.dept-head h2 { margin: 0; font-size: 16px; letter-spacing: -0.01em; }
.dept-head .sw { width: 13px; height: 13px; border-radius: 4px; border: 1px solid; flex: none; }
.dept-head .cd { font-size: 11px; font-weight: 700; letter-spacing: 0.05em; color: var(--muted); text-transform: uppercase; }
.dept-head .dept-nums { font-size: 12.2px; color: var(--muted); }
.dept-head .sp { flex: 1; }
.card.dept-empty { max-width: 460px; }

/* A team on the Positions tab: a sub-heading over that team's position cards. */
.team-band { margin: 0 0 4px; }
.team-band-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin: 0 2px 9px; }
.team-band-head .sw { width: 11px; height: 11px; border-radius: 4px; border: 1px solid; flex: none; }
.team-band-head .nm { font-weight: 700; font-size: 13.5px; }
.team-band-head .cd { font-size: 10.5px; font-weight: 700; letter-spacing: 0.05em; color: var(--muted); text-transform: uppercase; }
.team-band-head .sp { flex: 1; }

/* Two blocks, not seven: the person and the controls that act on them. They
   wrap as units, so a narrow card puts the controls on their own line instead of
   stranding one button under an email address. */
.person-row { display: flex; align-items: center; gap: 8px 10px; padding: 7px 0;
              border-top: 1px solid var(--border); flex-wrap: wrap; }
.person-row .who { flex: 1 1 190px; min-width: 0; }
.person-row .nm { display: block; font-weight: 600; font-size: 13.3px; }
.person-row .em { display: block; font-size: 11.6px; color: var(--muted);
                  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* `flex: 0 0 auto` on a row of two selects and five buttons is how the controls
   came to be painted 150px OUTSIDE the team tile, with the page scrolling 700px
   sideways to follow them: the block could neither shrink nor wrap, and its
   content wanted 675px of a card 517px wide inside. Most of those buttons now
   live in the ⋯ menu, and this is the net under that decision — the block wraps
   within the card rather than leaving it, whatever ends up in here later. */
.person-row .ctl { display: flex; align-items: center; justify-content: flex-end;
                   gap: 5px; flex: 0 1 auto; flex-wrap: wrap; min-width: 0; margin-left: auto; }
/* THREE SELECTS, AND THE CARD IS 517px INSIDE. They are deliberately narrow and
   `.ctl` wraps, so the cascade fits on one line in a wide window and folds onto
   a second in a narrow one — rather than painting out through the right-hand
   edge of the tile, which is what a row that could neither shrink nor wrap did
   with two of them (owner, 2026-09-22). */
.person-row select.p-dept { font-size: 12.2px; padding: 3px 6px; width: 112px; }
.person-row select.p-team { font-size: 12.2px; padding: 3px 6px; width: 118px; }
.person-row select.p-role { font-size: 12.2px; padding: 3px 6px; width: 134px; }
/* A row mid-cascade: the department or the team has moved and no position has
   been chosen yet, so nothing has been written. The chip says which half of the
   gesture is missing rather than leaving a row that looks saved and is not. */
.person-row.pending select { border-color: var(--warn-bd); }
.person-row .p-pending { font-size: 11px; }
.person-row .btn.sm { white-space: nowrap; }
.person-row .p-more > summary { color: var(--muted); }
.person-row .p-more[open] > summary { color: var(--text); background: var(--panel2); }
.person-row.off { opacity: 0.6; }

.card.team-retired, .card.people-gone { max-width: 640px; }

/* Deactivated people: a shut tile, opened when somebody asks (owner 2026-09-22).
   The same native <details> as .fsvc and .ins-cat — the count reads from the
   outside, so opening it is a choice rather than the way to find out. */
.card.people-gone { padding: 0; }
.card.people-gone > summary { display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
                              padding: 13px 19px; cursor: pointer; list-style: none; border-radius: var(--r-lg); }
.card.people-gone > summary::-webkit-details-marker { display: none; }
.card.people-gone > summary::after { content: "▾"; color: var(--faint); margin-left: auto; }
.card.people-gone[open] > summary::after { content: "▴"; }
.card.people-gone > summary:hover { background: var(--panel2); }
.card.people-gone > summary .nm { font-weight: 700; font-size: 14.5px; letter-spacing: -0.006em; }
.card.people-gone .gone-why { font-size: 12.2px; color: var(--muted); }
.card.people-gone[open] > summary { border-bottom: 1px solid var(--border); border-radius: var(--r-lg) var(--r-lg) 0 0; }
.card.people-gone .gone-body { padding: 2px 19px 15px; }
.card.people-gone .gone-body .person-row:first-child { border-top: 0; }

.role-card .perm-group { display: flex; gap: 10px; padding: 6px 0; border-top: 1px solid var(--border); }
.role-card .pg-k { font-size: 11.5px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
                   color: var(--muted); min-width: 92px; padding-top: 3px; }
.role-card .pg-v { display: flex; flex-wrap: wrap; gap: 4px; }
.role-card .pg-v .chip { font-size: 11.5px; padding: 1px 7px; }

/* --------------------------------------------------------------------------
   THE ROLES TAB (0193)

   A role card is compact until you open it: name, who holds it, how many doors,
   and which parts of the business it touches. "Edit doors" swaps the summary for
   the checklist and the card takes the full width of the grid, because a
   checklist of thirty-two doors in a 540px column is a column of wrapped text.
   -------------------------------------------------------------------------- */
.role-card.open { grid-column: 1 / -1; }
.role-card.off { opacity: 0.6; }
.role-desc { font-size: 12.8px; color: var(--muted); margin: 0 0 9px; }
.role-mods { display: flex; flex-wrap: wrap; gap: 4px; }
.role-mods .chip { font-size: 11.5px; padding: 1px 8px; }
.chip.priv { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--warn); font-weight: 600; }
.card.role-note { max-width: 680px; }
.card.role-note p { margin: 0; font-size: 13px; line-height: 1.55; }

/* The other company's people (0208) — the same rows, a different question. */
.card.peer-blocked p { font-size: 13px; line-height: 1.55; margin: 0 0 10px; }
.card.peer-blocked h3 { margin: 12px 0 6px; }
.card.peer-blocked ol { margin: 0 0 10px; padding-left: 20px; font-size: 13px; line-height: 1.6; }
.card.peer-blocked li { margin-bottom: 5px; }
.card.peer-blocked p:last-child { margin-bottom: 0; }

.perm-list { border-top: 1px solid var(--border); margin-top: 4px; }
.role-card .perm-group { display: flex; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--border); }
.role-card .perm-group:last-child { border-bottom: none; }
.role-card .pg-k { font-size: 11.5px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
                   color: var(--muted); flex: 0 0 148px; padding-top: 3px; }
.role-card .pg-v { display: grid; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); gap: 4px 14px; flex: 1; }

.perm { display: grid; grid-template-columns: auto 1fr; gap: 1px 8px; align-items: start;
        padding: 5px 7px; border-radius: var(--r); cursor: pointer; }
.perm:hover { background: var(--panel2); }
.perm input { grid-row: 1 / span 3; margin-top: 2px; }
.perm .pl, .print-permission-row .pl { font-size: 13px; font-weight: 600; color: var(--text); }
.perm .pd { font-size: 11.8px; line-height: 1.45; color: var(--muted); }
.perm .pk, .print-permission-row .pk { font-size: 10.8px; color: var(--muted); opacity: 0.62; font-family: var(--mono, monospace); }
.perm .pa { font-size: 9.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
            color: var(--warn); border: 1px solid var(--warn-bd); background: var(--warn-bg);
            border-radius: var(--r-pill); padding: 0 5px; margin-left: 6px; vertical-align: 1px; }

label.checkline { display: flex; align-items: center; gap: 8px; cursor: pointer; }
label.checkline input { margin: 0; }

/* Ch 19 — what somebody ASKED FOR on a machine, ruled off under the line the
   work is actually in. It used to hold the jobs as well; since 0220 the jobs
   are in the Assembly table in their slots, and what is left here is the
   decision — who wanted what, why, and how much of it is still open. */
.xw { border-top: 1px solid var(--border); padding: 8px 14px 6px; }
.xw-t { font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--faint); margin-bottom: 4px; }
.xw-row + .xw-row { border-top: 1px dashed var(--border); margin-top: 4px; padding-top: 4px; }
.xw-head { display: flex; align-items: center; gap: 8px; font-size: 12.5px; }
.xw-note { color: var(--muted); font-size: 12px; margin: 2px 0 0; }

/* A DESK JOB'S OWN STEPS (0220), under its row in the line. Ticked here and
   nowhere else — it never reaches the floor app, which is the whole point of
   it being a desk job. Indented to the depth of the task name above so the
   list reads as belonging to that job rather than to the table. */
.sc-stoprow td { padding-top: 0 !important; padding-bottom: 8px !important; white-space: normal !important; }
.sc-stops { display: flex; flex-direction: column; gap: 2px; padding-left: 14px;
            border-left: 2px solid var(--border); }
.sc-stop { display: flex; align-items: center; gap: 8px; font-size: 12.5px; cursor: pointer; }
.sc-stop input { margin: 0; }
.sc-stop.is-done > span:first-of-type { color: var(--muted); text-decoration: line-through; }
.sc-stop .muted { font-size: 11.5px; }
.sc-stops .btn { align-self: flex-start; margin-top: 6px; }

/* Ch 19 — the Extra work tab in Production settings. Three sections in one
   card, because "build / testing / packing" is how the owner wrote the lists
   and splitting them into three cards would put a scroll between things that
   are read together. */
.ps-xw-h { margin: 18px 0 2px; padding: 0 16px; font-size: 13px; letter-spacing: .02em; }
.ps-xw-h:first-of-type { margin-top: 10px; }
.ps-chk { display: block; font-size: 12.5px; margin: 2px 0; }
.ps-chk input { margin-right: 6px; }
/* The route editor gains two more selects than the build-type one: what each
   job is filed as, and why — and those two are why it needs a wider dialog than
   the other vocabulary forms. At 620px the eight columns add up to more than
   the box, so the only flexible one (the job's NAME) collapsed to 20px and the
   modal grew a sideways scrollbar: the editor the owner was told to use to
   change what a task does had no room to type what the task is called. A dialog
   is the width of its content. */
.modal:has(#ps-xsteps) { max-width: 880px; }
.ps-xstep { grid-template-columns: 22px 104px minmax(160px, 1fr) 150px 150px auto auto auto; }

/* A DESK JOB'S OWN STEPS (0220), under the job they belong to. Indented and
   ruled to the job above so a list of four things reads as that job's work
   rather than as four more jobs. */
.ps-xstops { margin: -2px 0 10px 28px; padding-left: 10px; border-left: 2px solid var(--line); }
.ps-xstop { display: grid; grid-template-columns: 1fr auto auto; gap: 6px; align-items: center; margin-bottom: 4px; }
.ps-xstop input[type="text"] { margin: 0; padding: 4px 9px; font-size: 13px; }
.ps-xstop .ps-chk { margin: 0; white-space: nowrap; font-size: 12px; color: var(--muted); }
.ps-xstop .btn.sm { padding: 4px 8px; min-width: 30px; }

/* Enrolling a bench tablet (0196): the code the tablet scans, sized to itself. */
.enrol-qr { display: flex; justify-content: center; margin: 12px 0; }
.enrol-qr svg { width: 220px; height: 220px; background: #fff; border-radius: 6px; padding: 6px; }

/* Your account (ch 18 §6.3): the chip is a link, the current session is marked, the banner sits above the page. */
a.user-chip { text-decoration: none; color: inherit; }
a.user-chip:hover .user-name { text-decoration: underline; }
#signin-banner .banner { margin: 0 0 12px; }
tr.me-current > td { background: var(--panel3); }

/* What a promise move does (0206). Three levels in one table — the order, each
   machine, then that machine's route indented under it — so a reader sees the
   shape of the floor rather than a flat run of rows with the serial repeated on
   every line. The machine line is the one carrying a name, so it is the one with
   weight; a step is indented and lighter; a row that does not move is quieter
   again, because it is context and not a change. */
.dd-moves td { padding-top: 6px; padding-bottom: 6px; }
.dd-moves tr.dd-order td { font-weight: 700; background: var(--panel3); }
.dd-moves tr.dd-machine td:first-child { font-weight: 650; }
.dd-moves tr.dd-machine td { border-top: 1px solid var(--border); }
.dd-moves tr.dd-step td:first-child { padding-left: 26px; }
.dd-moves tr.dd-still td { color: var(--muted); }
.dd-indent { color: var(--muted); margin-left: -14px; margin-right: 2px; }
/* A date that lands BEHIND today. `tr.warn` was on these rows since 0091 and had
   never matched a rule, so the one line the banner above is counting looked
   exactly like the eleven that are fine. */
.dd-moves tr.warn td { background: var(--warn-bg); }
.dd-moves tr.warn td:last-child { color: var(--warn); font-weight: 650; }
/* ---- Print & Documents (#/print, print*.js) ----------------------------------
   Everything else on these screens is the house chassis (page-head, tabs, seg,
   card/card-head, form-card, badge/chip, filterBar, pageFoot). These rules only
   cover what that chassis has no class for. */
.prt .card:not(.np) > .card-head { padding: 0 0 12px; }
.prt .card.np > .card-head { padding-bottom: 12px; }
.prt-sub { margin-bottom: 14px; }

/* Settings navigation belongs to its content panel, below the main module navigation. */
.prt-settings-panel.card { padding:0; min-width:0; }
.prt-settings-menu { padding:16px 20px; border-bottom:1px solid var(--border); }
.prt-settings-tabs { max-width:100%; flex-wrap:wrap; }
.prt-settings-tabs .seg-btn { white-space:nowrap; text-decoration:none; }
.prt-settings-content { min-width:0; padding:16px 20px 20px; }
.prt-settings-content > .fb-compact > .fb-list-card { padding:0; margin:0; border:0; border-radius:0; box-shadow:none; background:transparent; }
.prt-settings-content > .fb-compact > .fb-list-card > .card-head { padding:0 0 16px !important; }
.prt-settings-content :is(.prt-card-narrow, .card.narrow) { max-width:none; }
.prt-settings-content .prt-iu-lede { display:block; margin:0 0 16px; max-width:none; }
.prt-settings-content .prt-iu-grid { grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr)); gap:14px; }
.prt-settings-content .prt-iu-card { background:var(--panel3); padding:16px; box-shadow:none; }
.prt-settings-content .prt-iu-name { font-size:16px; }
.prt-iu-category { display:block; margin-bottom:10px; font-size:11px; font-weight:700; color:var(--muted); letter-spacing:.04em; text-transform:uppercase; }
.prt-settings-content .prt-iu-purpose { max-width:48ch; }
.prt-settings-content .prt-iu-foot { padding-top:4px; }

/* Settings has five parts (Printer Locations and Printer Types since 0208): on a phone the switch
   scrolls sideways inside itself, never the page. */
.prt-sub { max-width: 100%; overflow-x: auto; scrollbar-width: thin; }
.prt-sub .seg-btn { flex: none; white-space: nowrap; }
.prt-sub a.seg-btn { text-decoration: none; }
.prt-note { padding: 10px 16px 14px; border-top: 1px solid var(--border); }
.prt-facts { display: grid; grid-template-columns: minmax(120px, max-content) minmax(0, 1fr); gap: 7px 16px; font-size: 13px; margin: 0; }
.prt-facts > dt { color: var(--muted); }
.prt-facts > dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.prt-more > summary { cursor: pointer; color: var(--muted); width: fit-content; }
.prt-more[open] > summary { margin-bottom: 10px; }
.prt-pre { white-space: pre-wrap; overflow-wrap: anywhere; font-family: var(--mono); font-size: 12px; max-height: 320px; overflow: auto; background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 10px; margin: 0; }
.prt-checks { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 8px 16px; }
.prt-choices { display: grid; grid-template-columns: minmax(150px, max-content) minmax(220px, 1fr); gap: 12px 18px; align-items: start; }
.prt-choices select { width: 100%; }
.prt-family { display: inline-flex; align-items: center; gap: 0; vertical-align: middle; }
.prt-family:has(> details) > [data-family-primary] { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.prt-family > details > summary { border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: -1px; }
.prt-family .pop-menu { min-width: 240px; max-width: min(340px, 85vw); white-space: normal; }
.prt-family-settings { margin-bottom: 14px; }
.prt-dialog-form .form-actions { justify-content: flex-end; }
.prt-card-narrow { max-width: 760px; }
.cols > .prt-facts { flex: 1 1 260px; }
.prt-inner { box-shadow: none; margin: 0; }
.prt-checks .hint { display: block; }
/* A payload shape is a tree and has to read as one: the rule is the nesting, not decoration.
   Bare lists have no house style, so this is also the reset the browser default needs. */
.prt-tree, .prt-tree ul { list-style: none; margin: 0; padding: 0; }
.prt-tree ul { margin: 2px 0 2px 7px; padding-left: 11px; border-left: 1px solid var(--border); }
.prt-tree li { padding: 2px 0; font-size: 13px; }

/* Data Sources (PRINT-28). `.card.np` deliberately keeps every cell of a wide
   table on one line; the Data Source list carries a whole sentence in its status
   column, and that has to wrap — the same exception `.cat-cell` makes. */
.card.np td.prt-wrap { white-space: normal; max-width: 46ch; }

/* Configurations follow the catalogue's primary row and smaller secondary line. */
.prt-tc-list table { width:100%; table-layout:fixed; }
.prt-tc-list th, .prt-tc-list td { white-space:normal; min-width:0; overflow-wrap:anywhere; }
.prt-tc-list th:nth-child(1) { width:25%; }
.prt-tc-list th:nth-child(2) { width:12%; }
.prt-tc-list th:nth-child(3) { width:22%; }
.prt-tc-list th:nth-child(4) { width:16%; }
.prt-tc-list th:nth-child(5) { width:12%; }
.prt-tc-list th:nth-child(6) { width:13%; }
.prt-tc-list .prt-config-line { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.prt-tc-list .badge { white-space:normal; }
@container print-shell (max-width:1100px) {
  .prt-tc-list .prt-config-updated { display:none; }
  .prt-tc-list :is(th, td) { padding-left:10px; padding-right:10px; }
}
@container print-shell (max-width:740px) {
  .prt-tc-list :is(.prt-config-use, .prt-config-media) { display:none; }
  .prt-tc-list th:nth-child(1) { width:44%; }
  .prt-tc-list th:nth-child(2) { width:26%; }
  .prt-tc-list th:nth-child(4) { width:30%; }
  .prt-tc-list :is(th, td) { padding-left:8px; padding-right:8px; }
}
.prt-tc-empty > p { max-width: 74ch; margin: 0; }
/* New print template (printwizard.js): the three questions and Create on the left, the
   compact What happens next panel beside them (kept in view down to Create); one column,
   the panel under the questions and above Create, on a narrower screen. */
.prt-tc-new { display: grid; grid-template-columns: minmax(0, 760px) minmax(240px, 300px); grid-template-areas: "ask next" "create next"; column-gap: 18px; align-items: start; }
.prt-tc-ask { grid-area: ask; min-width: 0; }
.prt-tc-create { grid-area: create; min-width: 0; }
.prt-tc-next { grid-area: next; position: sticky; top: 16px; }
.prt-tc-new .form-card { max-width: none; }
.prt-tc-steps { margin: 8px 0 10px; padding-left: 20px; font-size: 13px; line-height: 1.45; }
.prt-tc-steps > li { margin: 6px 0; }
.prt-tc-steps > li::marker { color: var(--muted); font-weight: 700; }
.prt-tc-next > p:last-child { margin-bottom: 0; }
@media (max-width: 1100px) {
  .prt-tc-new { grid-template-columns: minmax(0, 760px); grid-template-areas: "ask" "next" "create"; }
  .prt-tc-next { position: static; }
}

/* Available Data (printsources.js) and the Advanced tab (printrouting.js). The list is
   five short columns and one sentence, so it stops at a readable width rather than
   stretching across the page (R-UI-1). A field row carries its own description under
   its name, indented past the fold caret so the two line up; the name cell wraps. */
.prt-ad-list { max-width: 1120px; }
.prt-ad-lede { max-width: 74ch; margin: 4px 0 8px; }
.card.np td.prt-ad-field { white-space: normal; min-width: 24ch; max-width: 60ch; }
.prt-ad-desc { display: block; padding-left: 16px; }
.prt-ad-links { margin: 10px 0 0; padding-left: 20px; line-height: 1.55; }
.prt-ad-tech { margin-top: 12px; }

/* The field tree is a table, and a table has no nesting, so the indent and the
   fold control are classes on the first cell. Depth never exceeds four here
   (collection -> item -> nested collection -> item). */
.prt-d0, .prt-d1, .prt-d2, .prt-d3, .prt-d4 { display: block; }
.prt-d1 { padding-left: 18px; }
.prt-d2 { padding-left: 36px; }
.prt-d3 { padding-left: 54px; }
.prt-d4 { padding-left: 72px; }
.prt-caret { background: none; border: 0; padding: 0 5px 0 0; color: var(--muted); cursor: pointer; font: inherit; line-height: 1; }
.prt-caret svg { width: 11px; height: 11px; vertical-align: -1px; transform: rotate(-90deg); transition: transform 0.12s; }
.prt-caret[aria-expanded="true"] svg { transform: none; }
.prt-caret-gap { display: inline-block; width: 16px; }

/* Load Test Record: three panes inside the one `.card.np`, so each pane brings
   the padding the card gave up, and a 30 kB JSON needs more than the 320px the
   shared `.prt-pre` allows. A value can be a long line of prose. */
.prt-pane { padding: 0 16px 14px; }
.prt-pane > .prt-pre { margin-top: 10px; }
.prt-pre-tall { max-height: 60vh; }
.card.np td.prt-val { white-space: normal; max-width: 42ch; overflow-wrap: anywhere; }

@media (max-width: 640px) { .prt-choices, .prt-facts { grid-template-columns: minmax(0, 1fr); } .prt-choices { gap: 4px 0; } }

/* ---- Print & Documents: the low-code editors (printprofiles.js, printmapping.js) ----
   Two trees the house chassis has no class for: a field tree drawn as table rows
   (which cannot nest, so depth is an attribute) and a mapping tree of real nested
   boxes (which can, so depth is the DOM). Everything else on both screens is the
   chassis. The 1px rules are tree guides, not decoration. */
.prt [data-depth="1"] { --prt-i: 1; }
.prt [data-depth="2"] { --prt-i: 2; }
.prt [data-depth="3"] { --prt-i: 3; }
.prt [data-depth="4"] { --prt-i: 4; }
.prt [data-depth="5"] { --prt-i: 5; }
.prt [data-depth="6"] { --prt-i: 6; }
.prt .prt-fname { padding-left: calc(var(--prt-i, 0) * 14px); font-weight: 600; }
.prt-frow.prt-on > td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
.prt-frow.prt-issue > td, .prt-frow.prt-policy > td { padding-top: 4px; padding-bottom: 8px; border-top: 0; }
.prt-frow.prt-issue > td { padding-left: calc(16px + var(--prt-i, 0) * 14px); font-size: 13px; }
.prt-policy > td { padding-left: calc(16px + var(--prt-i, 0) * 14px); }
/* `.card.np` keeps every cell on one line, and the Field column carries a sentence
   of description: the table ran to ~1,470px in a 963px card, and the Use select
   (`width: 100%` in an auto-width cell) was crushed to 34px. The Field column
   wraps (its path breaks at the dots, via <wbr>), the one two-word header wraps,
   and the select is the width of its longest word. At 1280 the table fits its
   card; on a phone it scrolls inside the card like every `.card.np` table. */
.card.np .prt-ftable td:first-child { white-space: normal; min-width: 20ch; }
.card.np .prt-ftable thead th { white-space: normal; }
.prt-frow select[data-use] { width: auto; min-width: 8.5em; padding: 5px 9px; }
.prt-policy-f, .prt-node-f { display: inline-flex; align-items: center; gap: 6px; margin-right: 16px; }
.prt-policy-f > label, .prt-node-f > label { color: var(--muted); font-size: 13px; white-space: nowrap; }
.prt-tree { display: grid; gap: 10px; }
.prt-node { min-width: 0; }
.prt-node .prt-node, .prt-node .prt-cond { margin-left: 12px; padding-left: 10px; border-left: 1px solid var(--border); }
.prt-node-head, .prt-node-row, .prt-cond-row, .prt-sortkey { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 6px 0; min-width: 0; }
.prt-node-name { font-weight: 600; }
.prt-node-head .prt-kind { max-width: 260px; }
.prt-node-empty { margin: 4px 0; }
.prt-node-add { margin: 6px 0; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.prt-node-issue { font-size: 13px; margin: 2px 0; }
.prt-cond, .prt-cond-group { margin: 8px 0; }
.prt-cond-group { border-left: 1px solid var(--border); padding-left: 10px; }
.prt-preview { margin-top: 12px; }
.prt-preview > .prt-facts { margin-bottom: 12px; }
@media (max-width: 640px) {
  .prt .prt-fname { padding-left: calc(var(--prt-i, 0) * 8px); }
  .prt-frow.prt-issue > td, .prt-policy > td { padding-left: calc(12px + var(--prt-i, 0) * 8px); }
  .prt-node-f, .prt-policy-f { margin-right: 8px; }
  .prt-node-head .prt-kind { max-width: 100%; }
}
/* ---- Print Studio → HTML2PDF Renderer layouts (printhtmllayout.js) -------------------------
   The list is a short table, sized to it (R-UI-1). The editor is the one page here that
   spans the screen on purpose: the BUILDER column on the left — the controls that build the
   layout, Page setup first — and the page itself on the right, as Preview or as Code, with a
   divider between them that can be dragged. Both panes are as tall as the window so neither
   pushes the page into scrolling, and both open with the same bar at the top so a line drawn
   across them meets the same place (ARCHITECTURE §11). Under 900px they stack, the page under
   the builder. */
.prt-hl-lede { max-width: 74ch; margin: 0 0 14px; }
.prt-hl-list { max-width: 980px; }
.card.np.prt-hl-list td:first-child { white-space: normal; min-width: 22ch; }
.prt-hl-list .badge { white-space: nowrap; }
/* On a phone the list is two columns: what it is and whether it is published; the rest moves under the name. */
.prt-hl-narrow { display: none; }
.prt-hl code { font-size: 12px; background: var(--chip); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 0 4px; overflow-wrap: anywhere; }
/* The masthead: the name and description ARE the title, edited where they are read. */
.prt-hl-name, .prt-hl-desc { display: block; width: 100%; background: transparent; border: 1px solid transparent; box-shadow: none; border-radius: var(--r-sm); }
.prt-hl-name { max-width: 34em; font-size: 25px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.15; padding: 1px 6px; margin: 3px 0 0 -7px; }
.prt-hl-desc { max-width: 74ch; font-size: 14px; color: var(--muted); padding: 2px 6px; margin: 2px 0 0 -7px; }
.prt-hl-name:not([readonly]):hover, .prt-hl-desc:not([readonly]):hover { border-color: var(--border); }
.prt-hl-name:focus, .prt-hl-desc:focus { background: var(--panel); color: var(--text); }
.prt-hl-bad { color: var(--bad); }
.prt-hl-saved { display: inline-flex; align-items: center; gap: 8px; max-width: 46ch; }
.prt-hl-saved > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* Under the save state and Publish, the layout's Enrolment in Print Studio: Enrol, or Enrolled with
   its Available switch and Unenrol (R-UI-2). The browse list's Enrol sits in its row's cell. */
.prt-hl-enrol { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 6px 10px; max-width: 46em; padding: 6px 10px; border: 1px solid var(--border); border-radius: var(--r); background: var(--panel); }
.prt-hl-enrol:empty { display: none; }
.prt-hl-enrol .prt-lib-switch { margin: 0; }
.prt-hl-enrol-note { flex-basis: 100%; text-align: right; }
.prt-hl-enrol-cell { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
.prt-hl-blockers { margin: 6px 0 10px; padding-left: 20px; }
/* ONE FRAME (owner #105). The editor is a single bordered box: the mode strip is its top
   edge, and the two columns live inside it. Not a card inside a panel inside a border. */
.prt-hl-editor { display: flex; flex-direction: column; height: max(520px, calc(100vh - 250px)); margin-bottom: 14px; border: 1px solid var(--border-strong); border-radius: var(--r); background: var(--panel); overflow: hidden; }
/* The mode strip - Body, Style, Variables, Assets - across the whole frame, because a mode is
   what the WHOLE editor is doing, not a tab over one half of it. */
.tabs.prt-hl-modes { flex: none; margin: 0; padding: 6px 12px 0; border-bottom: 1px solid var(--border); background: var(--panel2); }
/* The split. The two fr values are the divider's position (printhtmllayout.js wireDivider). */
.prt-hl-split { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(0, var(--prt-hl-left, 1fr)) 12px minmax(0, var(--prt-hl-right, 1fr)); }
.prt-hl-split.is-dragging { cursor: col-resize; user-select: none; }
.prt-hl-split.is-dragging iframe { pointer-events: none; }
/* The two columns: the mode's own controls, and the page. Neither is a card - the frame around
   them both is the only border, and the divider is the only line between them. */
.prt-hl-work, .prt-hl-page { display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }
.prt-hl-divider { display: flex; align-items: center; justify-content: center; cursor: col-resize; touch-action: none; border-left: 1px solid var(--border); border-right: 1px solid var(--border); background: var(--panel2); }
.prt-hl-divider::before { content: ""; width: 3px; height: 40px; border-radius: var(--r-pill); background: var(--border-strong); }
.prt-hl-divider:hover::before, .prt-hl-divider:focus-visible::before { background: var(--accent); }
.prt-hl-divider:focus-visible { outline: none; }
/* The page column's own bar: Preview | Code, and the actions for what it shows. It is the top
   of the right-hand column only - the mode strip above spans both. */
.prt-hl-bar { flex: none; display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 0 12px; padding: 6px 12px 0; border-bottom: 1px solid var(--border); }
.prt-hl-bar > .tabs.prt-hl-tabs { flex: 1 1 auto; margin: 0; padding: 0; border-bottom: 0; }
.prt-hl-pact { align-items: center; padding-bottom: 6px; }
.tabs.prt-hl-tabs { margin: 0; flex: none; }
.prt-hl-tabs .count, .prt-hl-modes .count { background: var(--bad-bg); border-color: var(--bad-bd); color: var(--bad); }
/* The mode's own controls on the left, and the page column's two views: each fills its column. */
.prt-hl-preview, .prt-hl-codeview { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.prt-hl-build, .prt-hl-panel { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 8px; padding: 12px 16px; }
.prt-hl-build, .prt-hl-acode { overflow-y: auto; }
.prt-hl-sub { flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; }
.prt-hl-sub > h3 { margin: 0; font-size: 14px; font-weight: 600; }
.prt-hl-sub > .btn { margin-left: auto; }
.prt-hl-foot { flex: none; margin: 0; }
/* A code editor: line numbers in a gutter beside a plain textarea, both in the same
   mono type and line height (20px — jump-to-line counts in it), scrolled together. */
.prt-hl-code { flex: 1 1 auto; min-height: 160px; display: flex; border: 1px solid var(--border-strong); border-radius: var(--r); overflow: hidden; background: var(--panel); }
.prt-hl-code:focus-within { border-color: var(--accent); box-shadow: var(--ring); }
.prt-hl-gutter { flex: none; margin: 0; min-width: 3.4em; padding: 10px 8px 40px 10px; overflow: hidden; text-align: right; white-space: pre; user-select: none; font-family: var(--mono); font-size: 12.5px; line-height: 20px; color: var(--faint); background: var(--panel3); border-right: 1px solid var(--border); }
.prt-hl-code textarea { flex: 1 1 auto; min-width: 0; height: auto; margin: 0; padding: 10px 12px; border: 0; border-radius: 0; box-shadow: none; resize: none; overflow: auto; white-space: pre; tab-size: 2; font-family: var(--mono); font-size: 12.5px; line-height: 20px; background: transparent; }
.prt-hl-code textarea:focus { box-shadow: none; }
/* A mode's column scrolls as a whole: its list keeps its own height rather than shrinking under the footer. */
.prt-hl-tools, .prt-hl-alistbox { flex: none; }
.prt-hl-alist, .prt-hl-dlist { list-style: none; margin: 0; padding: 0; }
.prt-hl-alist > li { display: flex; align-items: center; justify-content: space-between; gap: 8px 12px; padding: 6px 0; border-top: 1px solid var(--border); font-size: 13px; }
.prt-hl-alist > li:first-child { border-top: 0; }
.prt-hl-aname { min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 2px 8px; }
.prt-hl-alist .row-gap { flex: none; }
/* THE BODY'S TOOLBOX (owner #106): sections of tools, each one a name, the choices it needs
   and the button that writes it into the Body. The same two-column grid Page setup uses, so
   the labels line up down the column whichever mode is showing (ARCHITECTURE §11). */
.prt-hl-tools { display: flex; flex-direction: column; }
.prt-hl-body-intro { margin: 0 0 16px; line-height: 1.5; }
.prt-hl-tgroup { border-top: 1px solid var(--border); }
.prt-hl-tgroup > summary { display: flex; align-items: center; gap: 10px; padding: 16px 0; cursor: pointer; list-style: none; }
.prt-hl-tgroup > summary::-webkit-details-marker { display: none; }
.prt-hl-tgroup > summary::before { content: "›"; font-size: 20px; transition: transform .15s; }
.prt-hl-tgroup[open] > summary::before { transform: rotate(90deg); }
.prt-hl-tgroup > summary h3 { display: inline; margin: 0; font-size: 14px; }
.prt-hl-tgroup > summary .tiny { display: block; margin-top: 4px; }
.prt-hl-tgroup > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.prt-hl-tool-content { display: grid; gap: 18px; padding: 0 0 20px; }
.prt-hl-tool { display: grid; grid-template-columns: max-content minmax(0, 1fr); align-items: center; gap: 4px 12px; }
.prt-hl-tool > .prt-hl-dctl, .prt-hl-tnote { grid-column: 2; }
.prt-hl-tnote { margin: 0; max-width: 64ch; }
/* A picker names a variable AND shows the {{…}} it writes, so it wants the width the column
   has rather than the 22em the settings selects keep; two of them share a row and wrap. */
.prt-hl-tool .prt-hl-dsel { flex: 1 1 11em; max-width: 100%; }
.prt-hl-tool .prt-hl-dsel.wide { flex: 2 1 15em; }
.prt-hl-tval { flex: 1 1 11em; min-width: 0; max-width: 100%; }
/* The problems, under the editor they belong to; each one opens its line. */
.prt-hl-diags { flex: 0 1 auto; max-height: 34%; overflow-y: auto; padding: 10px 16px 12px; border-top: 1px solid var(--border); background: var(--panel3); }
.prt-hl-dlist { margin-top: 6px; }
.prt-hl-diag { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; width: 100%; padding: 5px 6px; margin: 0 -6px; border: 0; border-radius: var(--r-sm); background: none; color: var(--text); font: inherit; font-size: 13px; text-align: left; cursor: pointer; }
.prt-hl-diag:hover, .prt-hl-diag:focus-visible { background: var(--panel2); outline: none; }
.prt-hl-where { color: var(--muted); white-space: nowrap; }
.prt-hl-msg { flex: 1 1 24ch; min-width: 0; overflow-wrap: anywhere; }
/* The preview: the page as paper, white in both themes because paper is. */
.prt-hl-data { flex: none; padding: 8px 16px; }
.prt-hl-renderer { flex: none; padding: 0 16px 8px; }
.prt-hl-renderer:empty { display: none; }
.prt-hl-view, .prt-hl-pdf { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.prt-hl-view { border-top: 1px solid var(--border); background: var(--panel3); }
.prt-hl-frame { flex: 1 1 auto; width: 100%; min-height: 0; border: 0; background: #fff; color-scheme: light; }
.prt-hl-notice { flex: none; }
.prt-hl-say { padding: 14px 16px; max-width: 74ch; }
.prt-hl-say > h3 { margin: 0 0 6px; font-size: 14px; font-weight: 600; }
.prt-hl-say > p { margin: 4px 0; }
.prt-hl-pdfbar { flex: none; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 10px; padding: 8px 16px; border-bottom: 1px solid var(--border); background: var(--panel); }
/* A mode's own controls: assisted editing over the real HTML and CSS. One grid — label,
   control — so Page setup and the Style controls that come later line up down the column
   (ARCHITECTURE §11), and the controls are the compact size the editor's other panels use,
   not full-width form fields. */
.prt-hl-design { gap: 0; }
.prt-hl-dgroup { flex: none; display: flex; flex-direction: column; gap: 6px; padding-bottom: 12px; }
.prt-hl-dgroup + .prt-hl-dgroup { padding-top: 12px; border-top: 1px solid var(--border); }
.prt-hl-dgrid { display: grid; grid-template-columns: max-content minmax(0, 1fr); align-items: center; gap: 6px 12px; }
.prt-hl-dlabel { font-size: 13px; color: var(--muted); }
.prt-hl-dctl { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; min-width: 0; }
.prt-hl-dctl select, .prt-hl-dctl input:not([type="checkbox"]) { width: auto; padding: 6px 9px; font-size: 13px; }
.prt-hl-dsel { max-width: 22em; }
.prt-hl-dsel.wide { flex: 0 1 26em; }
.prt-hl-dnum { width: 6.5em; }
.prt-hl-dpair { display: inline-flex; align-items: center; gap: 4px; }
.prt-hl-dcheck { display: inline-flex; align-items: center; gap: 6px; margin: 0; font-size: 13px; white-space: nowrap; }
.prt-hl-dsay { margin: 0; max-width: 78ch; }
.prt-hl-dactions { display: flex; gap: 8px; }
/* The Variables list — the one place a variable is created, edited and removed (owner #106;
   the Body only inserts one). The same row treatment as the Assets list, one step of indent
   per level of the data, and its actions where that list keeps its own. */
.prt-hl-vlist { list-style: none; margin: 0; padding: 0; }
.prt-hl-v { font-size: 13px; }
.prt-hl-vname { min-width: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.prt-hl-variable > summary { padding: 12px 0; }
.prt-hl-variable > .prt-hl-tool-content { padding-left: 20px; gap: 10px; }
.prt-hl-variable code { justify-self: start; max-width: 100%; overflow-wrap: anywhere; }
.prt-hl-vtype { color: var(--muted); }
.prt-hl-vexample { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
/* Refresh schema (owner #123): a field the Data Source supplied, not the example; a place it could not describe. */
.prt-hl-vfrom { color: var(--muted); font-style: italic; }
.prt-hl-vnote { margin: 0; overflow-wrap: anywhere; }
.prt-sr-basis { margin: 4px 0 0; }
.prt-sr-h { font-size: 13px; margin: 12px 0 4px; }
.prt-sr-list { margin: 0; padding-left: 18px; overflow-wrap: anywhere; }
.prt-hl-vacts { display: flex; flex-wrap: wrap; gap: 6px; }
/* The image tags under Code: the exact line the Body writes to draw each image. */
.prt-hl-acodelist { flex: none; margin: 0; padding: 12px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; border: 1px solid var(--border-strong); border-radius: var(--r); background: var(--panel3); font-family: var(--mono); font-size: 12.5px; line-height: 20px; }
.prt-hl-acodename { display: block; color: var(--muted); font-family: inherit; font-size: 12px; }
@media (max-width: 560px) {
  .prt-hl-dgrid, .prt-hl-tool { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .prt-hl-dgrid > .prt-hl-dctl, .prt-hl-tool > .prt-hl-dctl { margin-bottom: 6px; }
  .prt-hl-tool > .prt-hl-dctl, .prt-hl-tnote { grid-column: 1; }
}
.prt-hl-tech { margin-top: 4px; }
/* The Danger zone at the foot of the layout editor: one line and its button, side by side
   (R-UI-2), the same card the other Delete-this-record areas use, kept compact. */
.prt-hl-danger { display: flex; align-items: center; justify-content: space-between; gap: 12px 16px; flex-wrap: wrap; margin-top: 6px; padding: 10px 12px; }
.prt-hl-danger .prt-hl-danger-say { min-width: 0; flex: 1 1 32em; }
.prt-hl-danger h3 { margin: 0; }
.prt-hl-danger p { margin: 2px 0 0; }
@media (max-width: 760px) {
  /* Above the 16px every field is held to on a phone (so iOS does not zoom): the name is the page's title. */
  .prt .prt-page-title > input.prt-hl-name[data-name] { font-size: 22px !important; }
}
@media (max-width: 640px) {
  .prt-hl-wide { display: none; }
  .prt-hl-narrow { display: block; }
  .card.np.prt-hl-list td:first-child { min-width: 0; }
}
@media (max-width: 900px) {
  .prt-hl-editor { height: auto; }
  .prt-hl-split { display: block; }
  .prt-hl-divider { display: none; }
  .prt-hl-work { border-bottom: 1px solid var(--border); }
  .prt-hl-build, .prt-hl-panel { min-height: 0; }
  .prt-hl-code { height: 55vh; flex: none; }
  .prt-hl-diags { max-height: 40vh; }
  .prt-hl-view { height: 75vh; flex: none; }
  .prt-hl-enrol { justify-content: flex-start; }
  .prt-hl-enrol-note { text-align: left; }
}

/* ---- Print & Documents: the help tip (printkit.helpTip) and the Guide tab (printguide.js) ----
   One "?" per term that needs explaining; its text shows on hover and on focus, so a tap
   or Tab reaches it too. Table heads are upper-case, so the tip resets that. */
.prt-help { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; margin-left: 5px; vertical-align: 1px;
  border: 1px solid var(--border-strong); border-radius: 50%; color: var(--muted); font-size: 10.5px; font-weight: 700; line-height: 1;
  text-transform: none; letter-spacing: 0; cursor: help; position: relative; }
.prt-help:hover, .prt-help:focus-visible { color: var(--text); border-color: var(--accent); outline: none; }
.prt-help:hover::after, .prt-help:focus::after { content: attr(data-help); position: absolute; top: calc(100% + 6px); left: -8px; z-index: 60;
  width: max-content; max-width: 280px; padding: 8px 10px; background: var(--panel3); color: var(--text); border: 1px solid var(--border);
  border-radius: var(--r); box-shadow: var(--shadow-md); font-size: 13px; font-weight: 400; line-height: 1.45; text-align: left;
  text-transform: none; letter-spacing: 0; white-space: normal; }
/* Shared Print Studio header: title/description left, states/actions or QA right.
   The shell navigation forms its bottom edge; narrow screens stack without page overflow. */
.prt .prt-page-head { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,auto); gap:8px 20px; align-items:start; margin-bottom:16px; padding-bottom:12px; }
.prt-page-title { min-width:0; }
.prt-page-title > .kicker { display:block; }
.prt-page-title > .prt-page-description { margin:4px 0 0; }
.prt-page-side { display:flex; flex-direction:column; align-items:flex-end; gap:8px; min-width:0; max-width: min(52vw,760px); }
.prt-page-rail { display:flex; justify-content:flex-end; align-items:center; flex-wrap:wrap; gap:8px; }
.prt-page-rail > [data-save-state]:empty { display:none; }
.prt-control-box { padding:12px; border:1px solid var(--border); border-radius:var(--r); background:var(--panel); justify-self:end; gap:10px; }
.prt-control-box .prt-page-rail { width:100%; justify-content:space-between; gap:10px 16px; }
.prt-control-state, .prt-control-actions { display:flex; flex-wrap:wrap; align-items:center; gap:8px; min-width:0; }
.prt-control-actions { margin-left:auto; justify-content:flex-end; }
.prt-control-actions > span { display: contents; }
.prt-page-head > .prt-control-box .btn,
.prt-page-head > .prt-control-box .btn:is(.primary,.danger,.danger-solid) { background:var(--panel); color:var(--text); border-color:var(--border-strong); box-shadow:var(--shadow-sm); filter:none; }
.prt-page-head > .prt-control-box .btn:hover,
.prt-page-head > .prt-control-box .btn:is(.primary,.danger,.danger-solid):hover { background:var(--panel2); border-color:var(--border-strong); filter:none; }
.prt-page-head > .prt-control-box .btn:focus-visible,
.prt-page-head > .prt-control-box .btn:is(.primary,.danger,.danger-solid):focus-visible { outline:2px solid var(--muted); outline-offset:2px; box-shadow:none; }
.prt-dialog-heading { display:flex; flex:none; align-items:center; gap:16px; margin-bottom:8px; }
.prt-dialog-heading > .modal-title { flex:1; min-width:0; margin:0; }
.prt-dialog-controls { display:flex; flex:none; align-items:center; justify-content:flex-end; gap:8px; }
.prt-control-state:empty, .prt-control-actions:empty { display:none; }
.prt-control-extra { width:100%; }
.prt-control-extra > .prt-hl-enrol { max-width:none; padding:0; border:0; border-radius:0; background:none; }
.prt-control-extra:has(.prt-hl-enrol:empty) { display:none; }
.prt-control-box .prt-control-meta { display:flex; flex-wrap:wrap; align-items:center; justify-content:flex-end; gap:4px 8px; width:100%; font-size:12px; line-height:1.4; }
.prt-control-meta > :empty { display:none; }
.prt-control-meta:not(:has(> :not(:empty))) { display:none; }
.prt-control-box .prt-hl-enrol-note { font-size:12px; }
/* A shared, compact control area aligned with the detail heading. Keep status,
   actions and save feedback in the same positions, even on simpler pages. */
.prt-page-head:has(> .prt-control-box) > .prt-page-title { min-height:88px; }
.prt-page-head > .prt-control-box { box-sizing:border-box; width:560px; max-width:48vw; min-height:88px; padding:10px 12px; display:grid; grid-template-columns:auto minmax(0,1fr); grid-template-rows:minmax(30px,auto) minmax(22px,auto); align-content:start; gap:8px; }
.prt-page-head > .prt-control-box > .prt-page-rail { grid-column:1 / -1; grid-row:1; flex-wrap:nowrap; gap:8px; }
.prt-page-head > .prt-control-box > .prt-control-extra { grid-column:1; grid-row:2; align-self:center; width:auto; }
.prt-page-head > .prt-control-box > .prt-control-meta { grid-column:2; grid-row:2; align-self:center; width:auto; }
.prt-page-head > .prt-control-box:not(:has(> .prt-control-extra)) > .prt-control-meta { grid-column:1 / -1; }
.prt-page-head > .prt-control-box:not(:has(> .prt-control-meta)) > .prt-control-extra { grid-column:1 / -1; }
.prt-control-meta time { font-size:12px; white-space:nowrap; }
.prt-control-menu { position:relative; }
.prt-control-menu > summary { list-style:none; cursor:pointer; }
.prt-control-menu > summary::-webkit-details-marker { display:none; }
.prt-control-menu > summary::after { content:' ▾'; }
.prt-control-menu-items { position:absolute; right:0; top:calc(100% + 6px); z-index:50; min-width:200px; max-width:calc(100vw - 48px); padding:6px; display:flex; flex-direction:column; gap:4px; border:1px solid var(--border); border-radius:var(--r); background:var(--panel); box-shadow:var(--shadow-md); }
.prt-control-menu-items .btn { width:100%; text-align:left; }
.prt-control-menu-items > span { display:contents; }
.prt-page-head > .prt-control-box .btn { min-height:30px; padding:5px 10px; font-size:13px; line-height:18px; white-space:nowrap; }
.prt-page-head > .prt-control-box .prt-hl-enrol { gap:6px 8px; }
.prt-page-head > .prt-control-box .prt-hl-saved { max-width:100%; }
/* List entries target two lines; real content is always allowed to grow. */
.prt [data-compact-filters] table > tbody > tr > td { height:72px; box-sizing:border-box; vertical-align:top; padding-top:12px; padding-bottom:12px; line-height:22px; }
.prt [data-compact-filters] table > tbody .sub { margin-top:4px; color:var(--muted); font-size:12px; line-height:18px; }
.prt .prt-history-table { min-width:1080px; }
.prt .prt-history-table th:first-child { width:180px; }
.prt .prt-history-table th:nth-child(2) { width:24%; }
.prt .prt-history-table th:nth-child(3) { width:16%; }
.prt .prt-history-table th:nth-child(4) { width:130px; }
.prt .prt-history-table th:nth-child(5) { width:24%; }
.prt .prt-history-table th:last-child { min-width:140px; }
.prt .prt-history-table time, .prt .prt-history-table .badge { white-space:nowrap; }
.prt-history-detail { container:prt-history / inline-size; }
.prt-history-overview { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); align-items:stretch; gap:14px; margin-bottom:14px; }
.prt-history-overview > .card { margin:0; min-width:0; }
.prt-history-detail .card-head { margin-bottom:16px; }
.prt-history-detail .prt-more { margin-top:16px; }
.prt-history-detail .prt-more > summary { color:var(--muted); cursor:pointer; }
.prt-history-detail .prt-more[open] > summary { margin-bottom:14px; }
.prt-history-selection { display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin:12px 0; font-size:13px; }
.prt-history-output { padding:18px 0; border-top:1px solid var(--border); }
.prt-history-output-head { display:flex; flex-wrap:wrap; justify-content:space-between; align-items:flex-start; gap:16px; margin-bottom:14px; }
.prt-history-output-head > div { min-width:0; }
.prt-history-output-head h3 { margin:0 0 8px; font-size:14px; overflow-wrap:anywhere; }
.prt-history-output-head > .btn { flex:none; }
.prt-history-output-meta { display:flex; align-items:center; flex-wrap:wrap; gap:8px; }
.prt-history-output-facts { grid-template-columns:120px minmax(0,1fr); }
.prt-history-output-advanced > .cols { margin-top:14px; }
.prt-history-outputs { padding-bottom:4px; grid-column:1 / -1; }
.prt-history-skipped { padding-bottom:16px; }
@container prt-history (min-width:1180px) { .prt-history-overview { grid-template-columns:repeat(3,minmax(0,1fr)); } .prt-history-outputs { grid-column:auto; } }
@container prt-history (max-width:739px) { .prt-history-overview { grid-template-columns:minmax(0,1fr); } }
@media (max-width:480px) { .prt-history-output-head { flex-direction:column; } .prt-history-output-facts { grid-template-columns:minmax(0,1fr); } }
@media (max-width:1100px) {
  .prt .prt-page-head:has(> .prt-control-box) { grid-template-columns:minmax(0,1fr); }
  .prt-page-head:has(> .prt-control-box) > .prt-page-title { min-height:0; }
  .prt-page-head > .prt-control-box { width:560px; max-width:100%; }
}
@media (max-width:600px) {
  .prt-page-head > .prt-control-box { width:100%; }
  .prt-page-head > .prt-control-box > .prt-page-rail { flex-wrap:wrap; }
  .prt-page-head > .prt-control-box > .prt-control-meta { flex-wrap:wrap; }
}
.prt:has(> .prt-head) { container:print-shell / inline-size; }
.prt .prt-head { padding-bottom:0; row-gap:0; grid-template-columns:minmax(220px,1fr) minmax(0,auto); }
.prt-head > .prt-page-title { grid-column:1; grid-row:1 / span 2; padding-bottom:8px; }
.prt-head > .prt-qa { grid-column:2; grid-row:1; justify-self:end; text-align:right; font-size:10px; line-height:1.25; overflow-wrap:anywhere; max-width:72ch; }
.prt-head > .prt-qa .badge { font-size:9px; padding:1px 5px; }
.prt-qa-build { display:none; }
.prt-page-nav { grid-column:2; grid-row:2; min-width:0; align-self:end; }
.prt-page-nav > .tabs { margin:0; border-bottom:0; }
.prt-page-nav > .tabs { flex-wrap:nowrap; overflow-x:auto; overflow-y:hidden; scrollbar-width:none; justify-content:flex-start; }
.prt-page-nav > .tabs::-webkit-scrollbar { display:none; }
.prt-page-nav .tab { flex:none; white-space:nowrap; padding:8px 12px; }
.prt-page-nav .tab:first-child { margin-left:auto; }
.prt-page-nav .tabs { background:color-mix(in srgb,var(--panel) 70%,transparent); border-radius:var(--r) var(--r) 0 0; }
.prt-page-nav .tab:hover, .prt-page-nav .tab:focus-visible { background:var(--panel3); }
.prt-page-nav .tab.active { background:color-mix(in srgb,var(--accent) 9%,var(--panel)); }
.prt-row-open { border:0; padding:0; background:none; color:var(--accent); text-decoration:underline; font:inherit; cursor:pointer; text-align:left; }
.prt-pp-media .prt-row-open { color:var(--text); text-decoration:none; }
.prt a.prt-catalogue-name { color:var(--text); text-decoration:none; }
.prt button.prt-catalogue-name { color:var(--text); text-decoration:none; }
.prt-media-subsets { display:grid; gap:12px; }
.prt-media-subsets > div { display:flex; align-items:center; flex-wrap:wrap; gap:8px 16px; }
.prt-media-subsets > div > button:first-child { min-width:100px; text-align:left; }
.prt-media-subsets > div > .danger { margin-left:auto; }
.prt-catalogue-name:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
.prt-pp-enrolled .prt-pp-printer > a, .prt-pp-found td:first-child > a { color:var(--text); text-decoration:none; }
.prt-server-badge { color:hsl(var(--server-hue) 65% 32%); background:hsl(var(--server-hue) 65% 95%); border-color:hsl(var(--server-hue) 50% 80%); }
:root[data-theme="dark"] .prt-server-badge { color:hsl(var(--server-hue) 65% 75%); background:hsl(var(--server-hue) 35% 14%); border-color:hsl(var(--server-hue) 35% 30%); }
/* Same geometry for all status chips; colour comes from the existing ERP badge tones. */
.prt .badge { font-size:12px; font-weight:600; line-height:1.4; padding:3px 9px; border-radius:var(--r-pill); white-space:nowrap; }
html:has(.view > .prt) { scrollbar-gutter:stable; }
@media (max-width: 760px) {
  .prt .prt-page-head { grid-template-columns:minmax(0,1fr); }
  .prt-page-side { align-items:flex-start; max-width:100%; }
  .prt-control-box { justify-self:stretch; }
  .prt-control-box .prt-control-meta { justify-content:flex-start; }
  .prt-page-rail { justify-content:flex-start; }
  .prt .prt-hl-enrol { justify-content:flex-start; }
  .prt .prt-hl-enrol-note { text-align:left; }
}
/* Respond to the module's usable width, including when the ERP sidebar is open. */
@container print-shell (max-width:1100px) {
  .prt .prt-head { grid-template-columns:minmax(0,1fr) minmax(0,auto); }
  .prt-head > .prt-page-title { grid-row:1; }
  .prt-head > .prt-page-nav { grid-column:1 / -1; grid-row:2; }
}
@container print-shell (max-width:740px) {
  .prt .prt-head { column-gap:12px; }
  .prt-head > .prt-qa { font-size:10px; white-space:nowrap; }
  .prt-qa-full { display:none; }
  .prt-qa-build { display:inline; }
  .prt-page-nav .tab { font-size:13px; padding:8px 10px; }
}
/* Printers (printprinters.js): Printers Found | Print Media half and half, stacking when
   halves would be too narrow to read; Enrolled Printers is the one wide table, its text
   columns wrapping so it fits a 1280 laptop (narrower, the card scrolls sideways, as every
   wide table does); Printer Roles is five short columns and stops at a readable width (R-UI-1). */
/* Under Enrolled Printers: Printers Found | Print Media, side by side where there is room (two
   420px halves), stacked below that. Side by side the two cards are ONE height and reach down to
   the bottom of the window (owner, 2026-09-19): printprinters.js measures the space left below
   the pair and sets --prt-pp-fill; the row is never shorter than --prt-pp-pair-h. Inside each card
   the head (title, Sync printers / New media, the Found card's sources line) stays put, the table's
   own column headers stay visible and the rows scroll, so a long list never makes the row or the
   page taller. Both cards are built the same way (owner, 2026-09-20), from ONE helper in
   printprinters.js, with the SAME four sections in the same order: the head, the
   `.prt-pp-card-info` band, the table's column headers and the table's rows — so a horizontal line
   drawn across the pair meets the same section on both. Stacked (a narrow screen) the cards keep
   their natural size, each table scrolling at 440px. The box is the container
   that decides "side by side", not the viewport: the desk's sidebar takes a different share of
   each screen. */
.prt-pp-pairbox { container: prtpair / inline-size; margin-bottom: 14px; --prt-pp-pair-h: 520px; }
/* The pair is the last thing on the Printers page and reaches the bottom of the window, so the page
   keeps the normal 24px gap under it instead of the desk's 90px. */
.view:has(.prt-pp-pairbox), .view:has(.prt-pd-grid) { padding-bottom: 24px; }
.view .prt-pp-pairbox:last-child { margin-bottom: 0; }
.prt-pp-pair { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; align-items: start; }
.prt-pp-pair > .card { margin-bottom: 0; min-width: 0; }
/* The one scrolling body BOTH cards of the pair use (owner, 2026-09-20: Printers Found is the same
   component as Print Media). Stacked, each table scrolls at the same height, never wider than the
   screen; side by side the rule below lets it take whatever height is left in the card. */
.prt-pp-card-body { max-height: 440px; overflow: auto; scrollbar-width: thin; }
.prt-pp-card-body thead th { position: sticky; top: 0; z-index: 1; box-shadow: inset 0 -1px 0 var(--border); }
/* One column-header height for both cards, so the first row of each table starts at the same level
   even when one card's heading wraps to two lines and the other's does not. `height` on a table cell
   is a minimum, so a heading that needs three lines still gets them. */
.prt-pp-card-body thead th { height: 60px; }
/* The band under the head, on BOTH cards of the pair: Printers Found says where its printers come
   from (and warns about a VIRTUAL stand-in), Print Media says what it is. Its `min-height` is one
   line of `.small` plus the bottom padding, so the band keeps its height on a card whose line is
   short — or absent, as Printers Found's is when no integration is enabled — and the two cards'
   column headers still start at the same height. */
/* Compact (owner, 2026-09-20): the band is one line of text, not a block of air. The same
   numbers on both cards — they share this class — so the column headers below still start level. */
.prt-pp-card-info { padding: 6px 16px 8px; min-height: 20px; }
@container prtpair (min-width: 854px) {
  .prt-pp-pair {
    grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: minmax(0, 1fr); align-items: stretch;
    height: max(var(--prt-pp-pair-h), var(--prt-pp-fill, 0px));
  }
  .prt-pp-pair > .card { display: flex; flex-direction: column; overflow: hidden; min-height: 0; }
  .prt-pp-pair > .card > * { flex: 0 0 auto; }
  .prt-pp-pair > .card > .prt-pp-card-body { flex: 1 1 auto; min-height: 0; max-height: none; }
}
/* Side by side, the two cards' sections are the SAME three rows of the pair's own grid (head, info
   band, body), so each band is as tall as the taller of the two and they grow together when one of
   them wraps — no per-card numbers. Where subgrid is not supported the flex rule above still holds,
   and the band's `min-height` keeps the common one-line case level. */
@supports (grid-template-rows: subgrid) {
  @container prtpair (min-width: 854px) {
    /* The 14px gap is BETWEEN the cards, not between a card's own sections: as rows of this grid
       the sections would inherit it twice over, above and below the band (owner, 2026-09-20 —
       that was the excess air). Each section brings its own padding instead. */
    .prt-pp-pair { grid-template-rows: auto auto minmax(0, 1fr); column-gap: 14px; row-gap: 0; }
    .prt-pp-pair > .card { display: grid; grid-template-rows: subgrid; grid-row: span 3; }
  }
}
/* A phone: tighter cells, and no column holds a minimum width, so Enrol — and Edit and Remove —
   stay on screen. */
@container prtpair (max-width: 480px) {
  .card.np .prt-pp-card-body th, .card.np .prt-pp-card-body td { padding-left: 8px; padding-right: 8px; min-width: 0; }
}
/* The PrintNode computer a printer is on — the Found card's Computer / server column, and the line
   under an enrolled printer's name: the one fact that tells two similarly named printers apart, so
   it reads in the text colour rather than as another badge. */
.prt-pp-computer { color: var(--text); }
/* Settings → Printer Locations / Printer Types (printrefdata.js): one short list in a narrow card. */
.prt-ref-lede { margin: 0 16px 12px; max-width: 74ch; }
.card.np.prt-ref td:first-child { min-width: 12ch; }
/* Edit and Remove side by side wherever there is room; on a phone they may stack. */
@media (min-width: 560px) { .prt-ref td .row-gap { flex-wrap: nowrap; } }
/* Printers Found says where its printers come from — the enabled printer integrations, from data,
   each with its badges — and warns about a FAKE LOCAL one only when one of them is. */
.prt-pp-sources { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; }
.prt-pp-source { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.prt-pp-source .badge { white-space: nowrap; }
.prt-pp-lede { margin: 6px 16px 12px; }
/* One Enrolled Printer's "3 Print Media on it" list (printprinters.js). */
.prt-pp-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--border); }
.prt-pp-list > li { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 16px; border-bottom: 1px solid var(--border); }
.prt-pp-list > li:last-child { border-bottom: 0; }
.prt-pp-stack { display: flex; flex-wrap: wrap; gap: 4px 6px; align-items: center; }
.prt-pp-stack > span { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.prt-pp-stack .chip, .prt-pp-stack .badge, .prt-pp-table .badge, .prt-pp-list .badge, .prt-pp-found td .badge { white-space: nowrap; }
/* The integration and its problem read under one another: the Found card is half the page wide. */
.prt-pp-table .prt-pp-provider, .prt-pp-table .prt-pp-flag,
.prt-pp-found td .prt-pp-provider, .prt-pp-found td .prt-pp-flag { display: block; }
.prt-pp-table .prt-pp-provider .badge { margin-top: 3px; }
.prt-pp-flag { font-weight: 600; }
.prt-pp-lines { flex-direction: column; align-items: flex-start; }
.card.np table.prt-pp-table th, .card.np table.prt-pp-table td { padding-left: 10px; padding-right: 10px; }
.card.np table.prt-pp-table thead th { white-space: normal; vertical-align: bottom; }
.card.np table.prt-pp-table td { white-space: normal; }
.card.np table.prt-pp-table td.nowrap { white-space: nowrap; }
.card.np table.prt-pp-table td.prt-pp-printer { min-width: 16ch; }
/* Enrolled Printers opens with Print Studio's own outputs (Save as PDF, BUILT-IN) in a group of
   their own, then the printers, each group under a small heading row (printprinters.js). The
   headings are labels, not a second table head; the built-in rows link nowhere, so they do not
   light up on hover like a printer row. */
.card.np table.prt-pp-table tr.prt-pp-group > th { padding: 12px 10px 6px; font-size: 12px; font-weight: 700; color: var(--muted); letter-spacing: 0.02em; white-space: nowrap; }
.card.np table.prt-pp-table tbody tr.prt-pp-group:hover,
.card.np table.prt-pp-table tbody tr.prt-pp-builtin:hover,
.card.np table.prt-pp-table tbody tr.prt-pp-builtin-note:hover { background: none; }
.card.np table.prt-pp-table tr.prt-pp-builtin-note > td { white-space: normal; padding-top: 0; }
/* Its one line: where the table scrolls sideways (a phone) it stays in view and wraps within the
   visible width rather than across the whole table. */
.prt-pp-builtin-note > td > div { position: sticky; left: 10px; max-width: min(100ch, calc(100vw - 72px)); }
/* One Enrolled Printer (printprinters.js): numbered section titles, as a Print Template numbers its sections. */
.prt-pd-h { display: flex; align-items: center; gap: 9px; margin: 0; }
.prt-pd-notes { margin: 12px 0; max-width: 560px; }
/* 3 Print Media is a six-column table with a proof line and two verbs per row: it gets the room it needs. */
.prt-pd .prt-card-narrow { max-width: 1000px; }
.prt-guide-sec + .prt-guide-sec { border-top: 1px solid var(--border); margin-top: 22px; }
.prt-guide-sec > h2:first-child { margin-top: 18px; }
.help-body .prt-guide-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 16px; }
.help-body .prt-guide-actions a { color: var(--text); text-decoration: none; }
/* ---- Print / Actions menu (printmenu.js) — the shared entity-page menu. It is
   the existing .pop-pick / .pop-menu / .pop-item popover with three changes: the
   menu is wider because a printer name or a refusal sits under each item, a long
   reason wraps instead of running off the edge, and the heading of a second
   Action is separated from the items above it. */
.prt-menu .pop-menu { min-width: 288px; max-width: 340px; max-height: min(60vh, 420px); overflow-y: auto; }
.prt-menu .pop-item .sub { white-space: normal; }
.prt-menu .pop-item + .pop-title { margin-top: 6px; padding-top: 9px; border-top: 1px solid var(--border); }
.prt-menu-note { padding: 7px 8px; }

/* Print Studio → one Print Template, as seven numbered sections (printtemplate.js, lane
   template-workflow). The page reads top to bottom like a document, so every section is
   one card in one column, sized to a comfortable reading width rather than the page
   (R-UI-1); each section's verbs sit in its own head (R-UI-2). The step list at the top
   jumps to a section: its number takes the section's state colour and nothing else. */
.prt-tw > .card, .prt-tw-steps { max-width: 920px; }
.prt-tw-sec { scroll-margin-top: 16px; }
.prt-tw-sec > .card-head h2 { display: flex; align-items: center; gap: 9px; margin: 0; }
.prt-tw-n { display: inline-flex; align-items: center; justify-content: center; flex: none; min-width: 22px; height: 22px; padding: 0 6px; border-radius: var(--r-pill); background: var(--panel3); border: 1px solid var(--border); color: var(--muted); font-size: 12px; font-weight: 700; }
.prt-tw-steps .is-done .prt-tw-n, .prt-tw-sec.is-done > .card-head .prt-tw-n, .prt-tw-flow > .is-done > .prt-tw-n { background: var(--ok-bg); border-color: var(--ok-bd); color: var(--ok); }
.prt-tw-steps .is-attention .prt-tw-n, .prt-tw-sec.is-attention > .card-head .prt-tw-n { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--warn); }
.prt-tw-steps { display: flex; flex-wrap: wrap; gap: 6px; list-style: none; margin: 0 0 14px; padding: 0; }
.prt-tw-steps button { display: inline-flex; align-items: center; gap: 7px; padding: 4px 11px 4px 5px; border: 1px solid var(--border); border-radius: var(--r-pill); background: var(--panel); color: var(--text); font: inherit; font-size: 13px; cursor: pointer; }
.prt-tw-steps button:hover, .prt-tw-steps button:focus-visible { border-color: var(--border-strong); outline: none; }
.prt-tw-state { color: var(--muted); font-size: 12px; }
.prt-tw-lead { margin: 0 0 6px; }
.prt-tw-list { margin: 0 0 10px; padding-left: 18px; }
.prt-tw-list > li { margin: 4px 0; }
.prt-tw-opt { border: 1px solid var(--border); border-radius: var(--r); padding: 10px 14px; margin: 10px 0; }
.prt-tw-opt-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; }
.prt-tw-opt p { margin: 6px 0 0; }
label.check.prt-tw-choice { align-items: flex-start; padding: 5px 0; }
.prt-tw-choice input { margin-top: 3px; }
.prt-tw-choice .hint { display: block; margin-top: 1px; }
.prt-tw-form { max-width: 700px; }
.prt-tw-inline { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; }
.prt-tw-minutes { max-width: 130px; }
.prt-tw-map { margin: 4px 0 8px; }
.prt-tw-map th, .prt-tw-map td { padding: 7px 12px 7px 0; font-size: 13px; }
.prt-tw-map td:first-child { width: 38%; }
.prt-tw-block + .prt-tw-block { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.prt-tw-block h4 { font-size: 13px; font-weight: 600; color: var(--muted); margin: 10px 0 6px; }
.prt-tw-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 14px; }
.prt-tw-row > .field { margin-bottom: 0; min-width: 200px; }
.prt-tw-row-verbs { padding-bottom: 2px; }
.prt-tw-two { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 8px 24px; }
.prt-tw-verdict { margin: 8px 0; }
.prt-tw-problems { margin: 6px 0; padding-left: 18px; font-size: 13px; }
.prt-tw-problems > li { margin: 3px 0; }
.prt-tw-flow { list-style: none; margin: 8px 0 0; padding: 0; }
.prt-tw-flow > li { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 4px 12px; align-items: start; padding: 10px 0; border-top: 1px solid var(--border); }
.prt-tw-flow > li:first-child { border-top: 0; }
.prt-tw-flow > .is-now > .prt-tw-n { background: var(--accent-wash); border-color: var(--accent-wash-bd); color: var(--accent); }
.prt-tw-flow > .is-later b { color: var(--muted); }
@media (max-width: 640px) {
  .prt-tw-flow > li { grid-template-columns: auto minmax(0, 1fr); }
  .prt-tw-flow > li > .row-gap { grid-column: 2; }
  .prt-tw-map td:first-child { width: auto; }
}
/* Print Studio → the field selection editor (printprofiles.js), mounted in a print
   template's section 2 and on its own page, and the mapping editor mounted in section 5.
   Inside a section the editors' own cards drop their shadow, so a card never floats
   inside a card. The field list scrolls within its block so the sections below stay close. */
.prt-tw-fs-card { max-width: 1040px; }
.prt-tw-fs-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; }
.prt-tw-fs-head + p { margin: 6px 0 10px; }
.prt-tw-fs-who { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px 22px; margin: 4px 0 8px; }
.prt-tw-fs-who > .field { margin-bottom: 0; }
.prt-tw-fs-search { flex: 1 1 260px; }
.prt-tw-fs-list { max-height: 70vh; overflow: auto; border: 1px solid var(--border); border-radius: var(--r); }
.prt-tw-fs-list > .empty { margin: 12px; }
.prt-tw-fs-table { margin: 0; }
.prt-tw-fs-table thead th { position: sticky; top: 0; z-index: 1; background: var(--panel3); }
.prt-tw-fs-table td { padding: 8px 12px; font-size: 13px; }
.prt-tw-fs-table td:last-child { width: 1%; white-space: nowrap; }
.prt-tw-fs-use { display: flex; align-items: center; gap: 6px; }
/* The Data picker groups its fields by business area (Build information, Delivery
   information…): one group per area inside the scrolling list, each opened on its own. */
.prt-fs-area + .prt-fs-area { border-top: 1px solid var(--border); }
.prt-fs-area > summary { cursor: pointer; padding: 9px 12px; }
.prt-fs-area > summary .muted { font-weight: 400; }
.prt-tw-embed .card { box-shadow: none; margin-bottom: 12px; }
.prt-tw-map-host { margin-top: 12px; }
.prt-tw-map-state { margin-bottom: 10px; }
@media (max-width: 640px) {
  .prt-tw-fs-table td:last-child { white-space: normal; }
  .prt-tw-fs-use { flex-wrap: wrap; }
}
/* The Print Configuration editor (printtemplate.js + printtpl-*.js, lanes editor-shell and
   configure): the autosave line in the header, why a section needs attention, the Output
   section's delivery rows, the Publish block's three steps and the owner's paper check. */
.prt-te-head-state { align-items: center; gap: 6px 12px; }
.prt-te-save { font-size: 13px; }
.prt-te-save.is-failed { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.prt-te-why { margin: -4px 0 10px; color: var(--warn); }
.prt-te-check-list { list-style: none; margin: 6px 0 0; padding: 0; }
.prt-te-check-list > li { margin: 8px 0; }
/* Print Configuration v2 (lane configure-v2, owner 2026-09-19): one section open at a time — the
   others fold to one line under their head; the Template section's two choices; Mapping's rows
   (template field · filled from · use · value · verbs), which need the room of a table. */
.prt-tw-sec.is-compact { padding-top: 13px; padding-bottom: 13px; }
.prt-tw-sec.is-compact > .card-head { margin-bottom: 2px; }
.prt-tw-sec.is-compact > .card-head h2 { cursor: pointer; }
.prt-te-compact { font-size: 14px; overflow-wrap: anywhere; }
.prt-tw-steps .is-now button { border-color: var(--accent-wash-bd); background: var(--accent-wash); }
.prt-tw > .card.prt-tw-sec.is-open[data-sec="mapping"] { max-width: 1180px; }
.prt-te-choices { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; max-width: 760px; }
.prt-te-choice { border: 1px solid var(--border); border-radius: var(--r); padding: 12px 14px; }
.prt-te-choice h3 { margin: 0 0 4px; }
.prt-te-choice .form-actions { margin-top: 8px; }
.prt-te-try { max-width: 760px; margin: 6px 0 12px; }
.prt-te-try p { margin: 6px 0 0; }
.prt-te-map-wrap { overflow-x: auto; }
.prt-te-map-rows th, .prt-te-map-rows td { padding: 8px 12px 8px 0; font-size: 13px; vertical-align: top; }
.prt-te-map-rows td:first-child { min-width: 16ch; }
.prt-te-map-rows td:last-child { width: 1%; }
.prt-te-map-rows tr[data-depth="1"] > td:first-child { padding-left: 16px; }
.prt-te-map-rows tr[data-depth="2"] > td:first-child { padding-left: 32px; }
.prt-te-map-rows tr[data-depth="3"] > td:first-child,
.prt-te-map-rows tr[data-depth="4"] > td:first-child,
.prt-te-map-rows tr[data-depth="5"] > td:first-child,
.prt-te-map-rows tr[data-depth="6"] > td:first-child { padding-left: 48px; }
.prt-te-map-group > td { padding-top: 14px; }
.prt-te-map-rows .prt-te-map-verbs { display: grid; grid-template-columns: repeat(2, max-content); justify-content: end; gap: 4px; }
.prt-te-map-verbs > .btn:first-child { grid-column: 1 / -1; justify-self: end; }
.prt-te-map-verbs .btn { white-space: nowrap; }
.prt-te-map-use { min-width: 118px; padding: 5px 8px; min-height: 30px; }
.prt-te-map-advanced { margin-top: 14px; }
.prt-te-json { max-height: 360px; }
@media (max-width: 760px) {
  .prt-te-map-rows thead { display: none; }
  .prt-te-map-rows tr { display: block; padding: 8px 0; border-top: 1px solid var(--border); }
  .prt-te-map-rows td, .prt-te-map-rows tr[data-depth] > td:first-child, .prt-te-map-rows td:last-child { display: block; width: auto; padding: 3px 0; }
  .prt-te-map-rows .prt-te-map-verbs { display: flex; flex-wrap: wrap; justify-content: flex-start; }
}
.prt-te-map-note p { margin: 0 0 8px; }
.prt-te-fields-changed { max-width: 720px; margin: 6px 0; }
.prt-plain { list-style: none; margin: 0; padding: 0; }
.prt-te-fields-changed p { margin: 0 0 4px; }
.prt-te-fields-changed ul { margin: 0 0 6px; padding-left: 20px; }
.prt-te-out { max-width: 720px; border: 1px solid var(--border); border-radius: var(--r); padding: 10px 14px; margin: 10px 0; }
.prt-te-out-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }
.prt-te-out-head h3 { margin: 0; }
.prt-te-out-head > .btn:first-of-type, .prt-te-out-head > span.muted { margin-left: auto; }
.prt-te-out > p:last-child { margin-bottom: 0; }
/* Print Studio → Configuration → Integrations (printintegrations.js). The catalogue is
   one grid per category of the SAME summary card: three across at desktop widths, two
   at medium, one when narrow. The count comes from the grid's own width (auto-fill,
   272px minimum, capped at 1120px so a fourth never fits), so the sidebar opening or
   closing cannot break it, and a lone card keeps its one column instead of stretching.
   Where subgrid exists the four blocks of every card (head, purpose, status, Manage)
   share row tracks, so cards in a row line up block for block and Manage never jumps. */
.prt-iu-lede { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; max-width: 1120px; margin: 0 0 18px; }
.prt-iu-lede > p { margin: 0; font-size: 13px; }
.prt-iu-sec { max-width: 1120px; }
.prt-iu-sec + .prt-iu-sec { margin-top: 28px; }
.prt-iu-sec-title { font-size: 15px; font-weight: 700; letter-spacing: -0.005em; margin: 0; }
.prt-iu-sec-lede { margin: 2px 0 12px; font-size: 13px; color: var(--muted); }
.prt-iu-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 272px), 1fr)); gap: 14px; }
.prt-iu-card { display: flex; flex-direction: column; gap: 10px; min-width: 0; margin: 0; padding: 16px 18px; }
@supports (grid-template-rows: subgrid) {
  .prt-iu-card { display: grid; grid-row: span 4; grid-template-rows: subgrid; row-gap: 10px; }
}
.prt-iu-head { min-width: 0; }
.prt-iu-title { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.prt-iu-card .prt-iu-name { font-size: 14.5px; font-weight: 700; margin: 0; min-width: 0; overflow-wrap: anywhere; line-height: 1.35; }
.prt-iu-title > .badge, .prt-iu-title > .chip { flex: none; }
.prt-iu-fake { margin-top: 4px; font-size: 12.5px; font-weight: 600; color: var(--warn); }
.prt-iu-purpose { margin: 0; font-size: 13px; color: var(--muted); line-height: 1.45; }
.prt-iu-status { display: grid; grid-template-columns: minmax(84px, max-content) minmax(0, 1fr); gap: 6px 14px; margin: 0; padding-top: 10px; border-top: 1px solid var(--border); font-size: 13px; align-content: start; }
.prt-iu-status > dt { color: var(--muted); }
.prt-iu-status > dd { margin: 0; min-width: 0; font-variant-numeric: tabular-nums; }
.prt-iu-foot { display: flex; justify-content: flex-end; margin-top: auto; align-self: end; }
.badge.prt-iu-real { background: var(--text); border-color: var(--text); color: var(--panel); }
/* One integration's own page. */
.prt-iu-doc-sub { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin-top: 6px; max-width: 80ch; }
.prt-iu-acts { margin-top: 6px; align-items: center; }
.prt-iu-inline { margin: 6px 0 0; }
/* Print Studio → triggers: the one trigger picker (printtriggers.js), and a Print Template's
   attached triggers and their settings (printtemplate.js sections 1, 6 and 7; lane
   trigger-picker). The picker and each trigger's settings are bordered blocks inside a
   section, sized to what is in them (R-UI-1), each with its verbs in its own head (R-UI-2):
   Attach sits beside the chosen trigger's line, under its three selectors. */
.prt-trg-picker, .prt-trg-card { max-width: 720px; border: 1px solid var(--border); border-radius: var(--r); padding: 12px 14px; margin: 10px 0; }
.prt-trg-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px; }
.prt-trg-head h3 { margin: 0; }
.prt-trg-card > .prt-trg-head { margin-bottom: 8px; }
.prt-trg-card > p { margin: 6px 0; }
/* The three selectors (Module, Model, Action) always share one row, in step, and stack on a phone. */
.prt-trg-sel { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.prt-trg-sel > .field { margin-bottom: 0; }
@media (max-width: 640px) { .prt-trg-sel { grid-template-columns: minmax(0, 1fr); } }
.prt-trg-chosen { margin-top: 10px; }
/* The role question, then Attach beside it (or alone, at the right, when there is no role). */
.prt-trg-do { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px 12px; margin: 8px 0 4px; }
.prt-trg-do > .btn { margin-left: auto; }
.prt-trg-role { flex: 0 1 320px; min-width: 0; margin: 0; }
.prt-trg-chosen > p { margin: 6px 0; }
.prt-trg-used { list-style: none; margin: 0 0 10px; padding: 0; max-width: 720px; }
.prt-trg-item { padding: 8px 0; border-top: 1px solid var(--border); }
.prt-trg-item:first-child { border-top: 0; padding-top: 2px; }
.prt-trg-item-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }
.prt-trg-item-head > .row-gap { margin-left: auto; }
.prt-trg-switches { list-style: none; margin: 6px 0 0; padding: 0; }
.prt-trg-switches > li { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; padding: 3px 0; font-size: 13px; }
.prt-trg-copies input { max-width: 110px; }
.prt-trg-facts-head { margin: 8px 0 4px; }
/* Templates (printlibrary.js; lane templates-enrol). Enrolled Templates is the one wide table, full
   width, its text columns wrapping so it fits a 1280 laptop (a status pill stays whole). Under it the
   two sources, CraftMyPDF | HTML2PDF Renderer: two halves where there is room for two readable ones,
   stacked below that. The box is the container that decides, not the viewport: the desk's sidebar
   takes a different share of each screen. */
.card.np.prt-lib-enrolled th, .card.np.prt-lib-enrolled td { white-space: normal; }
.card.np.prt-lib-enrolled td:first-child { min-width: 18ch; }
.card.np.prt-lib-enrolled td.nowrap, .prt-lib-enrolled :is(.badge, .chip) { white-space: nowrap; }
.prt-lib-sourcebox { container: prtlibsrc / inline-size; }
/* The source cards — CraftMyPDF, HTML2PDF Renderer, Email Templates (owner, 2026-09-19): three columns,
   then two, then one as the row narrows; the cards in a row are one height, each is a column whose
   lines (what it holds, its buttons under it) sit at its foot, so the buttons line up along the bottom.
   One skeleton for all three (owner #83, 2026-09-20): title, what it is, what it holds, what the reader
   needs to know, its buttons — one gap between sections everywhere (`--prt-lib-sec`, set once here),
   the card's own padding the only padding, and the action area anchored at the foot with `margin-top:
   auto`, so Browse layouts and Create new sit at the same level on every card whatever a card leaves out. */
.prt-lib-sources { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; align-items: stretch; }
.prt-lib-source { --prt-lib-sec: 10px; margin-bottom: 0; min-width: 0; display: flex; flex-direction: column; gap: var(--prt-lib-sec); }
/* The title sits in the card's own padding like every other section, not inset again by the card head. */
.prt-lib-source > .card-head { padding: 0; }
.prt-lib-source > .prt-lib-acts { margin-top: auto; }
/* Side by side, the three cards' sections are the SAME five rows of this grid (title, what it is,
   what it holds, what to know, its buttons), so Browse layouts sits at one height on every card and
   Create new likewise, however much text a card carries above them (owner, 2026-09-20). The 14px
   gap stays BETWEEN the cards; inside a card the sections keep their own `--prt-lib-sec` rhythm. */
@supports (grid-template-rows: subgrid) {
  @container prtlibsrc (min-width: 600px) {
    .prt-lib-sources { grid-template-rows: repeat(5, auto); column-gap: 14px; row-gap: var(--prt-lib-sec, 10px); }
    .prt-lib-source { display: grid; grid-template-rows: subgrid; grid-row: span 5; gap: 0; }
    .prt-lib-source > .prt-lib-acts { margin-top: 0; align-self: start; }
    /* An empty slot still HOLDS its row: hiding it would let the card below it slide up into that
       row, which is exactly how the buttons came to sit at different heights. It draws nothing. */
    .prt-lib-source > :empty { padding: 0; }
  }
}
@container prtlibsrc (min-width: 600px) { .prt-lib-sources { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@container prtlibsrc (min-width: 900px) { .prt-lib-sources { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.prt-lib-about { margin: 0; }
.prt-lib-status, .prt-lib-help { min-width: 0; }
.prt-lib-help > :is(p, div) { margin: 0; }
/* Several switched-on CraftMyPDF integrations: one row each, in the card's rhythm, the rows together
   the card's status block — each row keeps its own name, problem line and buttons, which act on it. */
.prt-lib-sourcelist { list-style: none; margin: 0; padding: 0; }
.prt-lib-sourcelist > li { display: flex; flex-direction: column; align-items: flex-start; gap: var(--prt-lib-sec); padding: var(--prt-lib-sec) 0; }
.prt-lib-sourcelist > li:first-child { padding-top: 0; }
.prt-lib-sourcelist > li:last-child { padding-bottom: 0; }
.prt-lib-sourcelist > li + li { border-top: 1px solid var(--border); }
.prt-lib-sourcename { min-width: 0; }
.prt-lib-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* A CraftMyPDF layout's page (printcraft.js) puts its short cards side by side, each no wider than what is in it (R-UI-1). */
.prt-lib-why { max-width: 42ch; }
.prt-lib-top > .card { flex: 1 1 320px; max-width: 580px; }
.prt-lib-switch { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; margin: 0 0 6px; }
/* A CraftMyPDF layout's fields as paths (lane craft-simplify): one per line, the path in mono, its type after it. */
.prt-lib-paths { list-style: none; margin: 0 0 10px; padding: 0; }
.prt-lib-paths li { padding: 2px 0; font-size: 13px; }
/* Nested as the layout nests them (lane templates-craft): a group's or a list's fields under it, a thin guide line for the depth. */
.prt-lib-paths .prt-lib-paths { margin: 2px 0 2px 3px; padding-left: 14px; border-left: 1px solid var(--border); }
/* The layout page's masthead once enrolled: Enrolled, then its Available switch, on one line. */
.prt-craft-enrolled { display: inline-flex; align-items: center; gap: 10px; }
.prt-craft-enrolled > .prt-lib-switch { margin: 0; }
/* The integration selector names each one in full, REAL or FAKE LOCAL. */
.prt-craft-browse .fb-f select { max-width: 340px; }
/* CraftMyPDF uses the same full-width catalogue layout and compact rows as HTML layouts. */
.prt-craft-list td:first-child { white-space:normal; min-width:22ch; }
.prt-craft-list td:nth-child(4) .badge { margin-right:4px; }
@media (max-width: 640px) {
  .prt-craft-list :is(.prt-craft-source, .prt-craft-synced) { display:none; }
  .prt-craft-list td:first-child { min-width:0; }
  .prt-craft-list :is(th, td) { padding-left:8px; padding-right:8px; }
}
.prt-lib-attention { list-style: none; margin: 0; padding: 0; }
.prt-lib-attention li + li { margin-top: 4px; }
.prt-lib-sample { max-height: 360px; overflow: auto; font-size: 12px; background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 8px 10px; margin: 0; white-space: pre; }
.prt-more + .prt-more { margin-top: 10px; }
/* On a phone Enrolled Templates is what a template is, whether it is offered, and Manage. */
@media (max-width: 640px) {
  .card.np.prt-lib-enrolled th:is(:nth-child(3), :nth-child(4), :nth-child(5), :nth-child(7)),
  .card.np.prt-lib-enrolled td:is(:nth-child(3), :nth-child(4), :nth-child(5), :nth-child(7)) { display: none; }
  .card.np.prt-lib-enrolled th, .card.np.prt-lib-enrolled td, .card.np.prt-lib-enrolled td:first-child { padding-left: 8px; padding-right: 8px; min-width: 0; }
}

/* The enrolled printer's page (owner, 2026-09-19): Printer details on the left; Supported formats over
   Print Media on the right, the right column exactly as tall as Printer details (`contain: size` keeps
   it from making the row taller) with the Print Media rows scrolling inside their card. The details
   note sits in the Save row. Advanced spans the page. Stacked on a narrow screen, natural heights. */
.prt-pd-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; margin-bottom: 14px; }
.prt-pd-grid > .card, .prt-pd-right > .card { margin-bottom: 0; max-width: none; min-width: 0; }
.prt-pd-right { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.prt-pd-right #prt-formats .prt-checks { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.prt-pd-right #prt-media .prt-pp-list { max-height: 320px; overflow-y: auto; }
.prt-pd-right #prt-media > .prt-note { margin-top:auto; }
.prt-pd-right #prt-media .prt-pp-list > li { display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:center; gap:6px 12px; }
.prt-pd-right #prt-media .prt-media-summary { display:contents; }
.prt-media-summary > b { grid-column:1; grid-row:1; min-width:0; }
.prt-media-badges { grid-column:1 / -1; grid-row:2; display:flex; flex-wrap:wrap; align-items:center; gap:6px; min-width:0; }
.prt-pd-right #prt-media .prt-pp-list > li > .row-gap { grid-column:2; grid-row:1; flex-wrap:nowrap; }
.prt-reported-media-matches { display:flex; flex-wrap:wrap; justify-content:flex-end; gap:6px; min-width:0; }
.prt-pd-right #prt-media .prt-reported-media > li { grid-template-columns:minmax(0,1fr) minmax(0,1fr); }
@media(max-width:640px) { .prt-pd-right #prt-media .prt-pp-list > li { grid-template-columns:minmax(0,1fr); } .prt-pd-right #prt-media .prt-pp-list > li > .row-gap { grid-column:1; grid-row:3; } }
#prt-details .form-actions { justify-content: flex-end; }
#prt-details .form-actions .prt-pd-details-note { flex: 1 1 auto; margin: 0; }
.prt-pd-advanced { max-width: none; margin-bottom: 0; }
.prt-pd-advanced-grid { display:grid; grid-template-columns:minmax(0,1fr); gap:20px; }
.prt-pd-advanced-info, .prt-pd-driver-report { min-width:0; }
.prt-pd-advanced-info > .card-head { padding:0; margin-bottom:12px; }
.prt-pd-advanced-info .prt-pd-notes { max-width:none; margin-bottom:0; }
.prt-pd-advanced:not(:has(.prt-pd-advanced-info > details[open])) .prt-pd-driver-report { display:none; }
.prt-pd-advanced:not(:has(details[open])) { padding:12px 16px; }
.prt-pd-advanced:not(:has(details[open])) .prt-pd-advanced-grid { grid-template-columns:minmax(0,1fr); }
.prt-pd-advanced:not(:has(details[open])) .prt-pd-advanced-info { display:flex; flex-wrap:wrap; align-items:center; gap:6px 20px; }
.prt-pd-advanced:not(:has(details[open])) .card-head { margin:0; }
.prt-pd-delete { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:10px 20px; padding:12px 16px; }
.prt-pd-delete > div { flex:1 1 280px; min-width:0; }
.prt-pd-delete p { margin:2px 0 0; }
.prt-pd-delete > button { flex:none; }
.prt-pd-notes [data-field="notes"] { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:8px 24px; margin-bottom:0; }
.prt-pd-notes [data-field="notes"] > label { grid-column:1 / -1; grid-row:1; }
.prt-pd-notes textarea { grid-column:1; grid-row:2; width:100%; }
.prt-pd-notes .form-actions { grid-column:2; grid-row:2; align-self:end; margin:0; }
.prt-pd-notes .hint { grid-column:1 / -1; grid-row:3; }
.prt-pd-notes .field-error { grid-column:1 / -1; grid-row:4; }
@media (max-width:600px) {
  .prt-pd-notes [data-field="notes"] { grid-template-columns:minmax(0,1fr); }
  .prt-pd-notes .form-actions { grid-column:1; grid-row:5; }
}
.prt-pd-driver-report > p { margin:0 0 12px; }
@media (min-width: 1100px) {
  .prt-pd-advanced-grid { grid-template-columns:repeat(2,minmax(0,1fr)); gap:24px; }
  .prt-pd-driver-report { display:flex; flex-direction:column; contain:size; min-height:320px; }
  .prt-pd-driver-report > .prt-pre { flex:1; height:0; min-height:240px; max-height:none; }
  .prt-pd-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: stretch; grid-auto-rows: minmax(360px, auto); }
  .prt-pd-right { contain: size; overflow: hidden; }
  .prt-pd-right > #prt-formats { flex: none; }
  .prt-pd-right > #prt-media { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
  .prt-pd-right #prt-media > .card-head, .prt-pd-right #prt-media > .prt-note, .prt-pd-right #prt-media > .empty { flex: none; }
  .prt-pd-right #prt-media .prt-pp-list { flex: 1 1 auto; min-height: 0; max-height: none; }
}

/* Print Studio form dialogs (printkit.js formDialog; owner, 2026-09-19): the dialog fits the window,
   its fields scroll, and Cancel / Save stay in a sticky footer — nobody scrolls the page to find Save.
   `.prt-dialog-grid` lays the fields in two columns where there is room (Name | Key, Width | Height),
   full-width fields spanning both; one column on a narrow screen. */
.modal.prt-dialog { --prt-dlg-pad: 24px; max-height: min(90vh, 860px); max-height: min(90dvh, 860px); display: flex; flex-direction: column; padding-left: var(--prt-dlg-pad); padding-right: var(--prt-dlg-pad); padding-bottom: 0; }
.modal.prt-dialog > .modal-title, .modal.prt-dialog > .modal-body { flex: none; }
.modal.prt-dialog > .prt-dialog-scroll { flex:1 1 auto; min-height:0; overflow-y:auto; overflow-x:hidden; margin:0 calc(-1 * var(--prt-dlg-pad)); padding:4px var(--prt-dlg-pad) 0; }
.modal.prt-dialog .prt-dialog-form { margin:0; padding:0; }
.modal.prt-dialog .prt-dialog-form > .form-actions { position: sticky; bottom: 0; z-index: 1; margin: 12px calc(-1 * var(--prt-dlg-pad)) 0; padding: 12px var(--prt-dlg-pad) 16px; background: var(--panel); border-top: 1px solid var(--border); }
.modal.prt-dialog-grid { max-width: 640px; }
.modal.prt-editor-dialog { width: min(94vw, 720px); max-width: 720px; }
.modal.prt-editor-dialog > .prt-dialog-scroll { padding-bottom: 16px; }
.prt-editor-dialog [data-shared-editor] > .card { margin: 0; padding: 0; border: 0; background: transparent; box-shadow: none; }
.prt-editor-dialog .prt-editor-fields { display: grid; gap: 12px; }
.prt-map-check-notes { padding: 0 16px 12px; }
.prt-te-map-rows .prt-map-summary-row { cursor: pointer; }
.prt-te-map-rows .prt-map-summary-row:hover { background: var(--panel); }
.prt-te-map-rows .prt-map-summary-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.prt-editor-fields label:not(.check) { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.prt-editor-fields .row-gap > label { flex: 1 1 180px; }
.prt-editor-fields .check { display: flex; align-items: center; gap: 8px; }
.prt-editor-dialog [data-plan-notice]:empty { display: none; }
.prt-editor-dialog > [data-editor-error] { flex: none; margin: 8px 0 16px; }
.modal.prt-dialog-grid .field { margin-bottom: 12px; gap: 4px; }
.modal.prt-dialog-grid .field .hint { font-size: 12.5px; margin-top: 0; }
.modal.prt-dialog-grid textarea { min-height: 64px; }
.modal.prt-dialog-compact { max-width:560px; padding-top:16px; }
.modal.prt-dialog-compact .field { margin-bottom:10px; gap:4px; }
.modal.prt-dialog-compact .field-error:empty { min-height:0; margin:0; }
.modal.prt-dialog-compact :is(input,textarea) { padding:8px 10px; }
.modal.prt-dialog-compact textarea { min-height:64px; height:76px; resize:vertical; }
.modal.prt-dialog-compact .prt-dialog-form > .form-actions { margin-top:8px; padding-top:10px; padding-bottom:10px; }
@media (min-width: 640px) {
  .modal.prt-dialog-grid .prt-dialog-form { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: 16px; align-content: start; }
  .modal.prt-dialog-grid .prt-dialog-form > :not(.field), .modal.prt-dialog-grid .prt-dialog-form > .span-full { grid-column: 1 / -1; }
}
@media (max-width: 640px) { .modal.prt-dialog { --prt-dlg-pad: 16px; } }
/* The Danger zone a record's own dialog carries (formDialog `extra`; a Print Configuration's Delete,
   owner #98, 2026-09-20): one compact band at the BOTTOM, below Save, in the same red as the
   `.danger-zone` card the product and branch pages use — never a large red button on the editor. */
.modal.prt-dialog .prt-dialog-extra { margin:0 0 16px; padding:10px 0 16px; border-top:1px solid var(--bad-bd); }
.prt-dialog-extra h3 { font-size: 13px; font-weight: 800; margin: 0 0 2px; color: var(--bad); }
.prt-dialog-extra p { margin: 0 0 8px; }
.prt-dialog-extra .row-gap { justify-content: flex-end; }
/* Consistent delete areas across Print Studio pages and configuration dialogs. */
.prt .danger-zone, .modal.prt-dialog .prt-dialog-extra.danger-zone {
  position:relative;
  border:1px solid transparent;
  background:color-mix(in srgb, var(--bad) 4%, var(--panel));
  border-radius:var(--r);
  margin-top:14px;
}
.prt .danger-zone::after, .modal.prt-dialog .prt-dialog-extra.danger-zone::after {
  content:""; position:absolute; inset:-1px; pointer-events:none; border-radius:inherit;
  background:repeating-linear-gradient(90deg,var(--bad-bd) 0 12px,transparent 12px 20px) top / 100% 1px no-repeat,
    repeating-linear-gradient(90deg,var(--bad-bd) 0 12px,transparent 12px 20px) bottom / 100% 1px no-repeat,
    repeating-linear-gradient(0deg,var(--bad-bd) 0 12px,transparent 12px 20px) left / 1px 100% no-repeat,
    repeating-linear-gradient(0deg,var(--bad-bd) 0 12px,transparent 12px 20px) right / 1px 100% no-repeat;
}
.modal.prt-dialog .prt-dialog-extra.danger-zone { margin-inline:0; padding:12px 16px; }
/* Print Studio → the field picker (printfieldpicker.js, lane field-catalogue): Mapping's one way to
   choose QuakeERP data. Search over everything, or Module → Model → Field; the rows are the only
   thing that scrolls. A row a field cannot be chosen from stays readable with its reason beside it. */
.prt-fp { display: flex; flex-direction: column; gap: 8px; min-width: 0; min-height: 0; }
.prt-fp-q { width: 100%; }
.prt-fp-browse[hidden] { display: none; }
.prt-fp-browse { display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.1fr) minmax(0, 1.4fr) auto; gap: 8px; align-items: end; }
.prt-fp-browse label { display: flex; flex-direction: column; gap: 3px; min-width: 0; font-size: 13px; color: var(--muted); }
.prt-fp-browse select { width: 100%; min-width: 0; }
.prt-fp-browse .btn { white-space: nowrap; }
.prt-fp-note { font-size: 13px; color: var(--muted); line-height: 1.5; }
.prt-fp-note:empty, .prt-fp-foot:empty { display: none; }
.prt-fp-now { font-size: 13px; line-height: 1.5; }
.prt-fp-err { color: var(--bad); }
.prt-fp-list { max-height: min(46vh, 420px); border: 1px solid var(--border); border-radius: var(--r); }
.prt-fp-row { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 14em); align-items: center; gap: 2px 10px; padding: 5px 11px; border-bottom: 1px solid var(--border); font-size: 13px; }
.prt-fp-row .chip, .prt-fp-row .badge { padding: 0 8px; }
.prt-fp-row:last-child { border-bottom: none; }
.prt-fp-row.is-off { cursor: default; color: var(--muted); }
.prt-fp-row.is-off:hover { background: none; }
.prt-fp-row.is-sel { background: var(--accent-wash); }
.prt-fp-name { font-weight: 600; min-width: 0; overflow-wrap: anywhere; }
.prt-fp-row.is-off .prt-fp-name { font-weight: 500; }
.prt-fp-facts { display: inline-flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; }
.prt-fp-val { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right; }
.prt-fp-val.is-none { color: var(--muted); }
.prt-fp-why { grid-column: 1 / -1; font-size: 12.5px; color: var(--muted); }
.prt-fp-group { background: var(--panel3); border-bottom: 1px solid var(--border); text-transform: none; letter-spacing: 0; font-size: 12.5px; }
.modal.prt-fp-dialog { max-width: 820px; }
.modal.prt-fp-dialog .prt-fp-host { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.modal.prt-fp-dialog .prt-fp { flex: 1 1 auto; }
.modal.prt-fp-dialog .prt-fp-list { flex: 1 1 auto; min-height: 140px; max-height: none; }
@media (max-width: 720px) {
  .prt-fp-browse { grid-template-columns: minmax(0, 1fr); }
  .prt-fp-row { grid-template-columns: minmax(0, 1fr) auto; }
  .prt-fp-val { grid-column: 1 / -1; text-align: left; }
}

/* Print Configuration → 4 Output (printtpl-output.js, lane output-destinations): one card per destination —
   a Printer (its printers as a list of rows: name · location, integration · formats · state) or the built-in
   Save as PDF — sized to its content (R-UI-1), On/Off and its switch in the card's head (R-UI-2). */
.prt-out-dest { max-width: 720px; border: 1px solid var(--border); border-radius: var(--r); padding: 10px 14px; margin: 10px 0; }
.prt-out-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }
.prt-out-head h3 { margin: 0; }
.prt-out-for { color: var(--muted); font-size: 13px; overflow-wrap: anywhere; }
.prt-out-head > .btn, .prt-out-head > .prt-out-later { margin-left: auto; }
.prt-out-dest > p:last-child { margin-bottom: 0; }
.prt-out-list { display: flex; flex-direction: column; gap: 5px; margin: 2px 0; }
label.check.prt-out-opt { align-items: flex-start; gap: 9px; padding: 6px 10px; border: 1px solid var(--border); border-radius: var(--r); background: var(--panel); cursor: pointer; }
label.check.prt-out-opt:hover { background: var(--panel2); }
label.check.prt-out-opt:has(input:checked) { border-color: var(--accent-deep); background: var(--panel2); }
.prt-out-opt input { margin-top: 3px; flex: none; }
.prt-out-opt-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.prt-out-opt-name { font-weight: 600; overflow-wrap: anywhere; }
.prt-out-opt-facts { color: var(--muted); font-size: 12.5px; overflow-wrap: anywhere; }
.prt-out-opt-facts.bad-text { color: var(--bad); }

/* ============================================================================
   PRINT STUDIO · 3 Mapping → a row's OPTIONS, as a NUMBERED PIPELINE
   (printtpl-transforms.js; owner #102, 2026-09-20 — the owner's mockup)

   1 Field · 2 Fill with · 3 Data Transformation · 4 Formatting ·
   5 Fallback · 6 Preview (the owner's approved reference, owner #104), and the
   transformation chooser ROLLING DOWN inside the body when + Add
   transformation is open — never a second pane, so the dialog keeps ONE width
   and does not move a pixel left or right when it opens.

   UNIFORMITY IS THE POINT (ARCHITECTURE §11), and it is enforced by THREE
   shared measurements, not by each section styling itself:
     --prt-tx-gutter  the step number's column. Every section head and every
                      section body starts after it, so all five sections share
                      ONE left edge and a horizontal line meets the same place.
     --prt-tx-label   the ONE control column. Every labelled line — a section's
                      line, a transform's argument, the format's options — is
                      the same grid, so their controls line up down the dialog.
     --prt-tx-min     the resting height of a control row and of a card, so a
                      section holding one short line is not taller than the one
                      beside it and a card with no arguments matches one with.
   Section actions (+ Add transformation, Always prints) all sit in
   `.prt-tx-sec-aside`, at the right of the section head: the same place on
   every section and on every row. Save / Cancel never scroll (the footer).

   Colour is restrained and structural: the step number carries the pipeline's
   blue → purple → green (source → change → result), nothing else is tinted.
   `--prt-tx-step` is local to this dialog; the rest are house tokens.
   ========================================================================== */
.modal.prt-tx {
  --prt-tx-gutter: 26px;
  --prt-tx-label: 8.5em;
  --prt-tx-min: 30px;
  --prt-tx-step: #6a4fbd; /* the pipeline's middle accent; only the step badge uses it */
  max-width: 760px;
}
:root[data-theme="dark"] .modal.prt-tx { --prt-tx-step: #a893ea; }
/* THE DIALOG HAS ONE WIDTH (owner #104 §3). The chooser opens DOWNWARDS inside
   the body, so there is deliberately no `.prt-tx-open` width here and nothing
   else may add one: the modal is 760px whether the chooser is open or shut, and
   a centred box of an unchanging width cannot shift sideways. `scrollbar-gutter`
   keeps the body's own scrollbar space reserved, so the roll-down growing the
   content does not nudge the controls either. */
.modal.prt-tx select, .modal.prt-tx input[type="text"], .modal.prt-tx input[type="number"], .modal.prt-tx input[type="date"], .modal.prt-tx input[type="datetime-local"], .modal.prt-tx input[type="search"] { width: auto; max-width: 100%; }
.modal.prt-tx .prt-tx-lead { margin-bottom: 4px; }
.modal.prt-tx .prt-tx-body { flex: 1 1 auto; min-width: 0; min-height: 0; overflow-y: auto; overflow-x: hidden; scrollbar-gutter: stable; margin: 0 calc(-1 * var(--prt-dlg-pad)); padding: 2px var(--prt-dlg-pad) 8px; }
.modal.prt-tx .prt-tx-foot { flex: none; display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin: 0 calc(-1 * var(--prt-dlg-pad)); padding: 12px var(--prt-dlg-pad) 16px; border-top: 1px solid var(--border); background: var(--panel); }
.prt-tx-saving { margin-right: auto; }

/* ---- a section: number · heading · its own action, then one body -------- */
.prt-tx-sec { padding: 9px 0; border-bottom: 1px solid var(--border); }
.prt-tx-sec:last-child { border-bottom: 0; }
.prt-tx-sec-h { display: grid; grid-template-columns: var(--prt-tx-gutter) auto minmax(0, 1fr); align-items: center; gap: 0 6px; min-height: 26px; }
.prt-tx-h { margin: 0; font-size: 13px; font-weight: 700; }
.prt-tx-sec-aside { justify-self: end; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 6px; font-size: 13px; }
.prt-tx-sec-b { padding-left: var(--prt-tx-gutter); }
.prt-tx-sec-b:not(:empty) { margin-top: 4px; }
.prt-tx-n { display: inline-flex; align-items: center; justify-content: center; width: 19px; height: 19px; border-radius: var(--r-pill); font-size: 11.5px; font-weight: 700; color: var(--panel); background: var(--muted); font-variant-numeric: tabular-nums; }
/* The pipeline's own story, in the reference's order: the two source steps, the
   change, the result, the fallback, and the preview that closes the loop. */
.prt-tx-n[data-n="1"] { background: var(--info); }
.prt-tx-n[data-n="2"] { background: var(--info); }
.prt-tx-n[data-n="3"] { background: var(--prt-tx-step); }
.prt-tx-n[data-n="4"] { background: var(--ok); }
.prt-tx-n[data-n="5"] { background: var(--muted); }
.prt-tx-n[data-n="6"] { background: var(--info); }

/* ---- 1 Field: the chosen source field, on its own card ------------------ */
.prt-tx-fld { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 12px; align-items: center; padding: 9px 11px; border: 1px solid var(--border-strong); border-radius: var(--r); background: var(--panel); }
.prt-tx-fld-b { min-width: 0; }
.prt-tx-fld-n { overflow-wrap: anywhere; }
.prt-tx-fld-m { margin-top: 2px; overflow-wrap: anywhere; }
.prt-tx-fld-a { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 6px 8px; }

/* ---- the ONE control column -------------------------------------------- */
.prt-tx-line { display: grid; grid-template-columns: var(--prt-tx-label) minmax(0, 1fr); gap: 4px 10px; align-items: start; min-height: var(--prt-tx-min); }
.prt-tx-l { color: var(--muted); font-size: 12.5px; padding-top: 6px; overflow-wrap: anywhere; }
.prt-tx-c { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; min-height: var(--prt-tx-min); }
.prt-tx-c > .prt-tx-hint { flex: 1 0 100%; margin: -2px 0 2px; }
.prt-tx-inline { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; }
.prt-tx-num { width: 6em; }
.prt-tx-short { width: 12em; max-width: 100%; }
.prt-tx-fixed { width: 22em; max-width: 100%; }
.prt-tx-ask { font-size: 13px; }
.prt-tx-chosen { font-size: 12.5px; font-weight: 600; padding: 2px 8px; border-radius: var(--r-pill); background: var(--panel2); }
.prt-tx-children { display: grid; grid-template-columns: repeat(auto-fill, minmax(11em, 1fr)); gap: 2px 12px; width: 100%; }
.prt-tx-adv { margin: 2px 0 0; }
.prt-tx-adv > summary { font-size: 12.5px; color: var(--muted); cursor: pointer; }
.prt-tx-adv > * { margin-top: 4px; }

/* ---- 1 Fill with: the segmented row ------------------------------------- */
.prt-tx-modes { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.prt-tx-mode { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border: 1px solid var(--border-strong); border-radius: var(--r); cursor: pointer; font-size: 13px; background: var(--panel); }
.prt-tx-mode:hover { background: var(--panel2); }
.prt-tx-mode.on { border-color: var(--info); background: var(--info-bg); color: var(--info); font-weight: 600; }

/* ---- 2 Change the value: one card per change, all the same height ------- */
.prt-tx-cards { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 6px; }
.prt-tx-card { display: grid; grid-template-columns: 20px 20px minmax(0, 1fr) auto; gap: 2px 8px; align-items: start; min-height: 46px; padding: 7px 9px; border: 1px solid var(--border); border-left: 3px solid var(--prt-tx-step); border-radius: var(--r); background: var(--panel); }
.prt-tx-card.open { border-style: dashed; border-left-style: solid; }
.prt-tx-card-n { color: var(--muted); font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums; padding-top: 2px; }
.prt-tx-ic { width: 18px; height: 18px; flex: none; margin-top: 1px; color: var(--muted); }
.prt-tx-card-b { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.prt-tx-card-h { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; font-size: 13.5px; }
.prt-tx-card-d { margin: 0; }
.prt-tx-card-d:empty { display: none; }
.prt-tx-gives { white-space: nowrap; }
.prt-tx-card-v { display: inline-flex; gap: 4px; justify-self: end; }
.prt-tx-none { margin: 0; }
/* A change's arguments are the same grid as a section's lines, so they line up with them. */
.prt-tx-args { display: flex; flex-direction: column; gap: 3px; margin-top: 4px; }
.prt-tx-args:empty { display: none; }
.prt-tx-arg { display: grid; grid-template-columns: var(--prt-tx-label) minmax(0, 1fr); gap: 2px 8px; align-items: start; }
.prt-tx-arg-l { color: var(--muted); font-size: 12.5px; padding-top: 6px; overflow-wrap: anywhere; }
.prt-tx-arg-c { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; min-height: 28px; }
.prt-tx-list, .prt-tx-map { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; width: 100%; }
.prt-tx-listrow, .prt-tx-maprow, .prt-tx-operand { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; }

/* ---- 4 / 5 and the issues strip ---------------------------------------- */
.prt-tx-problem { margin: 3px 0; font-size: 13px; }
.prt-tx-issues { padding-left: var(--prt-tx-gutter); }
td .prt-tx-pv { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 6px; font-size: 13px; }
.prt-tx-pv-l { color: var(--muted); font-size: 12px; }

/* ---- 6 Preview: three stages, left to right, no outer panel -------------
   Only the three cards carry a border (owner #104 §4): the section frame is
   the same as every other section's, so the preview does not read as a box
   bolted onto the bottom of the dialog. The grid — not flex — is what keeps
   the three of them equal and the two arrows between them, at any width, with
   `minmax(0, 1fr)` so a long value ellipsises instead of widening the row. */
.prt-tx-flow { display: grid; grid-template-columns: minmax(0, 1fr) 26px minmax(0, 1fr) 26px minmax(0, 1fr); align-items: stretch; width: 100%; }
.prt-tx-pvc { display: flex; flex-direction: column; gap: 3px; min-width: 0; padding: 8px 10px; border: 1px solid var(--border-strong); border-radius: var(--r); background: var(--panel); }
.prt-tx-pvc-l { color: var(--muted); font-size: 11.5px; }
.prt-tx-pvc strong { font-family: var(--mono); font-size: 13px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.prt-tx-arrow { display: grid; place-items: center; color: var(--muted); font-size: 16px; }
.prt-tx-pv-steps { margin: 2px 0 0; padding-left: 18px; font-size: 12.5px; display: flex; flex-direction: column; gap: 2px; }
.prt-tx-pv-ctl { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 6px 10px; align-items: center; margin-top: 9px; padding-top: 9px; border-top: 1px solid var(--border); }
.prt-tx-pv-ctl-l { font-size: 12.5px; font-weight: 600; }
.prt-tx-pv-ctl-c { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; min-width: 0; }
.prt-tx-pv-hint { margin: 6px 0 0; }

/* ---- the transformation chooser, rolled down inside the body ------------
   Full-bleed to the dialog's edges (the body's own padding, negated), tinted
   so it reads as a drawer of this section rather than another card, and shut
   again by its own ×. It is inside `.prt-tx-sec`, which is inside the
   scrolling body — so opening it makes the BODY longer and nothing wider. */
.prt-tx-roll { margin: 8px calc(-1 * var(--prt-dlg-pad)) calc(-1 * 9px); padding: 10px var(--prt-dlg-pad) 13px; background: var(--panel3); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.prt-tx-roll-q { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: center; }
.modal.prt-tx .prt-tx-roll-q input[type="search"] { width: 100%; max-width: none; }
.prt-tx-roll-x { flex: none; }
.prt-tx-roll-note { margin: 6px 0 0; }
.prt-tx-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
.prt-tx-chip { display: inline-flex; align-items: center; gap: 5px; padding: 4px 8px; border: 1px solid var(--border-strong); border-radius: var(--r-sm); background: var(--panel); color: var(--text); font: inherit; font-size: 12.5px; cursor: pointer; }
.prt-tx-chip:hover { background: var(--panel2); }
.prt-tx-chip.on { border-color: var(--info); background: var(--info-bg); color: var(--info); font-weight: 600; }
.prt-tx-chip.on .prt-tx-ic { color: var(--info); }
.prt-tx-chip .prt-tx-ic { width: 15px; height: 15px; margin: 0; }
.prt-tx-chip-n { color: var(--muted); font-variant-numeric: tabular-nums; font-size: 11.5px; }
.prt-tx-chip.on .prt-tx-chip-n { color: inherit; }

/* FOUR CARDS PER ROW at normal desktop width (owner #104 §3). Each cell is
   `minmax(0, 1fr)`, so two cards occupy two cells and do NOT stretch across
   the row: seven cards fall 4 + 3, thirteen fall 4 / 4 / 4 / 1. How many
   there are is the ENGINE's answer for this value, never a number here. */
.prt-tx-pick-cards { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; align-items: start; }
.prt-tx-pick-card { display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: 8px; align-items: start; width: 100%; min-width: 0; min-height: 56px; padding: 8px 9px; border: 1px solid var(--border); border-radius: var(--r); background: var(--panel); color: var(--text); font: inherit; text-align: left; cursor: pointer; }
.prt-tx-pick-card:hover { border-color: var(--border-strong); background: var(--panel2); }
.prt-tx-pick-card .prt-tx-ic { margin-top: 1px; }
.prt-tx-pick-t { display: flex; flex-direction: column; gap: 1px; min-width: 0; font-size: 13px; overflow-wrap: anywhere; }
.prt-tx-pick-t .small { line-height: 1.35; }

/* 4 desktop → 2 medium → 1 mobile. Normal desktop stays four: the dialog is
   760px wide until the viewport is narrower than it, so these only bite once
   the dialog itself has had to shrink. */
@media (max-width: 700px) {
  .prt-tx-pick-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .prt-tx-flow { grid-template-columns: minmax(0, 1fr) 18px minmax(0, 1fr) 18px minmax(0, 1fr); }
  .prt-tx-arrow { font-size: 14px; }
}
@media (max-width: 640px) {
  .modal.prt-tx { --prt-tx-label: 0; --prt-tx-gutter: 0; }
  .prt-tx-line, .prt-tx-arg { grid-template-columns: minmax(0, 1fr); }
  .prt-tx-l, .prt-tx-arg-l { padding-top: 0; }
  .prt-tx-l:empty, .prt-tx-arg-l:empty { display: none; }
  .prt-tx-fld { grid-template-columns: minmax(0, 1fr); }
  .prt-tx-fld-a { justify-content: flex-start; }
  .prt-tx-pv-ctl { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 460px) {
  .prt-tx-pick-cards { grid-template-columns: minmax(0, 1fr); }
}
/* Conditional output summaries: detailed controls are opened only on demand. */
.prt-output-plan { max-width: 1560px; }
.prt-output-plan > .card, .prt-output-plan > .prt-tw-steps { max-width: none; }
.prt-output-plan .page-head { margin-bottom: 18px; }
.prt-output-plan .card-head { flex-wrap: wrap; gap: 10px; align-items: center; }
.prt-output-plan h2 { display: flex; align-items: center; gap: 9px; }
.prt-output-plan .prt-plan-status { margin-top: 12px; }
.prt-plan-section.card.prt-tw-sec { min-width: 0; margin: 0; padding: 14px 16px; }
.prt-plan-section .card-head { padding: 0; }
.prt-plan-section p { margin: 10px 0; }
.prt-plan-section h3 { margin: 12px 0 8px; }
.prt-plan-section .prt-conditional-groups { margin-top: 12px; padding-top: 10px; }
.prt-plan-section .prt-conditional-groups > h3 { margin-top: 0; }
.prt-plan-section .prt-delivery { gap: 8px 16px; }
.prt-plan-section .prt-te-compact { margin-top: 10px; }
.prt-output-plan .prt-plan-section [data-preview-editor] { margin-top: 12px; }
.prt-plan-section [data-preview-editor] label { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.prt-plan-section [data-preview-editor] input, .prt-plan-section [data-preview-editor] select { max-width: 100%; box-sizing: border-box; }
.prt-plan-section .form-actions { flex-wrap: wrap; margin-top: 12px; }
.prt-plan-section .form-actions + .row-gap { margin-top: 8px; }
.prt-plan-section .prt-plan-editor:not(:empty) { margin-top: 12px; }
/* Full-width setup and preview cards use their horizontal space before wrapping. */
.prt-binding-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:24px; }
.prt-binding-grid > * { min-width:0; }
.prt-plan-section { container-type:inline-size; }
.prt-binding-trigger { grid-column:1 / -1; display:flex; flex-wrap:wrap; align-items:baseline; gap:8px 20px; }
.prt-plan-section .prt-binding-trigger p { margin:0; }
@container (min-width:1000px) {
 .prt-binding-grid { grid-template-columns:minmax(170px,.65fr) minmax(0,1.4fr) minmax(0,1fr); gap:20px; }
 .prt-binding-trigger { grid-column:auto; display:block; }
 .prt-plan-section .prt-binding-trigger p { margin-top:8px; }
 .prt-plan-section .prt-binding-grid .prt-binding-delivery { border-left:1px solid var(--border); padding-left:20px; }
 .prt-plan-section .prt-binding-grid .prt-conditional-groups { padding-left:20px; }
}

.prt-plan-section .prt-binding-grid .prt-conditional-groups { border-top:0; border-left:1px solid var(--border); padding:0 0 0 24px; margin:0; }
.prt-plan-section .prt-binding-grid h3 { margin:0 0 8px; }
.prt-plan-section .prt-binding-grid .prt-group-choice { margin-top:0; }
.prt-plan-section .prt-binding-grid p:last-child { margin-bottom:0; }
.prt-preview-controls { display:flex; flex-wrap:wrap; align-items:end; gap:10px 16px; }
.prt-preview-record { flex:1 1 240px; }
.prt-preview-arguments { display:grid; grid-template-columns:repeat(auto-fit,minmax(130px,1fr)); flex:1 1 300px; min-width:0; align-items:end; gap:10px; }
.prt-preview-arguments:empty { display:none; }
.prt-preview-arguments > label { min-width:0; }
.prt-preview-arguments :is(input,select) { width:100%; }
.prt-plan-section .prt-preview-controls .form-actions { margin:0; gap:8px; }
.prt-plan-section .prt-preview-tools { margin-top:12px; align-items:center; gap:8px; }
.prt-preview-tools > a { margin-left:auto; font-size:13px; }
.prt-preview-tools > button { white-space:normal; text-align:left; }
@media(max-width:800px) {
 .prt-binding-grid { grid-template-columns:minmax(0,1fr); gap:14px; }
 .prt-plan-section .prt-binding-grid .prt-conditional-groups { border-left:0; border-top:1px solid var(--border); padding:12px 0 0; }
 .prt-preview-arguments { flex:1 1 100%; }
 .prt-preview-tools > a { margin-left:0; }
}

.prt-outputs-frame { border: 1px solid var(--border); border-radius: var(--r-card, 14px); padding: 14px; margin: 14px 0; background: var(--bg); scroll-margin-top: 16px; }
.prt-outputs-frame > .card-head { padding: 0 0 10px; }
.prt-outputs-frame > .card-head h2 { font-size: 16px; margin: 0; }
.prt-output-sets { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; align-items: stretch; }
.prt-independent { display: flex; min-width: 0; }
.prt-output-branch { display: grid; grid-template-rows: auto repeat(3, 1fr); gap: 12px; width: 100%; min-width: 0; }
.prt-branch-head { padding: 6px 2px 4px; }
.prt-branch-head h3, .prt-output-group > header h3 { font-size: 16px; margin: 0; }
.prt-rule-summary { margin: 10px 0; line-height: 1.5; overflow-wrap: anywhere; }
.prt-output-plan .prt-branch-section { margin: 0; display: flex; flex-direction: column; padding: 12px; min-width: 0; }
.prt-output-plan .prt-branch-section > .card-head, .prt-output-plan .prt-branch-head > .card-head { padding: 0; }
.prt-branch-section .card-head h3 { margin: 0; font-size: 15px; }
.prt-branch-section .prt-te-compact { color: var(--muted); line-height: 1.5; overflow-wrap: anywhere; padding: 6px 0 10px; }
.prt-branch-verbs { margin-top: auto; flex-wrap: wrap; }
.prt-output-group { grid-column: span 2; min-width: 0; border: 1px solid var(--border); border-radius: var(--r-card, 14px); padding: 16px; display: flex; flex-direction: column; }
.prt-output-group > header { border-bottom: 1px solid var(--border); margin-bottom: 14px; padding-bottom: 8px; }
.prt-variant-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); gap: 16px; align-items: stretch; flex: 1; }
.prt-plan-editor:not(:empty) { margin-top: 20px; scroll-margin-top: 16px; }
.prt-plan-editor > .card { margin: 0; }
.prt-plan-editor [data-shared-editor] { min-width: 0; }
.prt-plan-editor label { display: flex; flex-direction: column; gap: 6px; margin: 12px 0; }
.prt-plan-editor .check { flex-direction: row; }
.prt-condition-row { display: grid; gap: 10px; padding: 14px 0; max-width: 780px; }
.prt-condition-field { display: block; width: 100%; text-align: left; white-space: normal; margin-top: 6px; }
.prt-condition-row select[multiple] { min-height: 100px; }
.prt-output-plan [data-preview-editor] { margin-top: 20px; }
.prt-output-plan [data-preview-editor] > .row-gap { flex-wrap: wrap; }
@media (max-width: 1050px) { .prt-output-sets { grid-template-columns: 1fr; } .prt-output-group { grid-column: auto; } }
@media (max-width: 620px) { .prt-outputs-frame { padding: 12px; } .prt-output-group { padding: 12px; } .prt-variant-grid { grid-template-columns: 1fr; } }
.prt-output-plan .page-head .kicker { display: block; margin-top: 8px; }
.prt-output-sets { grid-template-rows: auto 1fr; }
.prt-independent, .prt-output-group { display: grid; grid-row: span 2; grid-template-rows: subgrid; }
.prt-independent { border: 1px solid var(--border); border-radius: var(--r-card, 14px); padding: 16px; }
.prt-independent > header { padding-bottom: 8px; border-bottom: 1px solid var(--border); }
.prt-independent > header h3 { margin: 0; font-size: 16px; }
.prt-output-group > header { margin-bottom: 0; }
.prt-independent > .prt-output-branch, .prt-variant-grid { margin-top: 14px; }
@media (max-width: 1050px) { .prt-output-sets { grid-template-rows: none; } .prt-independent, .prt-output-group { grid-row: auto; grid-template-rows: auto 1fr; } }

/* The configuration keeps its width while local detail editors open. */
.prt-output-plan .prt-output-sets { grid-template-columns: repeat(auto-fit, minmax(min(100%, 310px), 1fr)); grid-template-rows: auto; }
.prt-output-plan .prt-output-sets > .prt-output-branch { border: 1px solid var(--border); border-radius: var(--r-card, 14px); padding: 12px; box-sizing: border-box; gap: 10px; }
.prt-conditional-groups { border-top: 1px solid var(--border); margin-top: 20px; padding-top: 16px; }
.prt-selection-list { display: grid; grid-template-columns: repeat(auto-fit,minmax(min(100%,320px),1fr)); gap: 12px; margin-top: 14px; }
.prt-selection-list > .card { margin: 0; padding: 16px; min-width: 0; }
.prt-output-plan > .prt-selection-frame { padding: 14px; margin: 14px 0; min-width: 0; }
.prt-selection-frame > .prt-selection-heading { margin: 0 0 12px; font-size: 14px; }
.modal.prt-group-dialog { max-width: 720px; }
.prt-group-dialog-body fieldset { min-width: 0; }
.prt-group-dialog-body .row-gap { flex-wrap: wrap; gap: 8px; }
.prt-group-dialog-body label:not(.check) { display: flex; flex-direction: column; gap: 4px; }
.prt-group-dialog-body .check { display: inline-flex; align-items: center; gap: 6px; }
.prt-group-dialog-body .check input { width: auto; }
.prt-group-dialog-body :is(input,select) { max-width: 100%; box-sizing: border-box; }
.prt-group-dialog-body h3 { margin: 6px 0 8px; font-size: 14px; }
.prt-group-dialog-body fieldset { padding: 8px 10px; margin: 4px 0 8px; border: 1px solid var(--border); border-radius: 8px; }
.prt-group-dialog-body .prt-condition-row.is-compact { grid-template-columns: minmax(0, 1.4fr) minmax(0, .7fr) minmax(0, 1.3fr); align-items: start; gap: 6px 10px; padding: 8px 0; }
.prt-group-dialog-body .prt-condition-row.is-compact + .prt-condition-row { border-top: 1px solid var(--border); }
.prt-condition-row.is-compact > .prt-condition-operand { grid-column: 1; grid-row: 1; min-width: 0; }
.prt-condition-row.is-compact > .prt-condition-operator { grid-column: 2; grid-row: 1; min-width: 0; }
.prt-condition-row.is-compact > .prt-condition-value { grid-column: 3; grid-row: 1; min-width: 0; }
.prt-condition-row.is-compact > .prt-condition-tools { grid-column: 1 / -1; grid-row: 2; }
.prt-condition-row.is-compact > .prt-condition-actions { grid-column: 1 / -1; justify-content: flex-end; }
.prt-group-dialog-body .prt-condition-row.is-compact :is(label,.muted.small) { font-size: 12px; }
.prt-group-dialog-body .prt-condition-operand > div { display: flex; flex-direction: column; gap: 4px; }
.prt-group-dialog-body .prt-condition-row.is-compact :is(label,.prt-condition-operand .muted.small) { margin: 0; line-height: 18px; font-weight: 600; color: var(--muted); }
.prt-group-dialog-body .prt-condition-row.is-compact .prt-condition-field { margin-top: 0; }
.prt-group-dialog-body .prt-condition-row.is-compact :is(input,select,.prt-condition-field) { padding: 6px 8px; min-height: 34px; font-size: 13px; }
.prt-group-dialog-body .prt-condition-row.is-compact select[multiple] { min-height: 68px; }
.prt-group-dialog-body .btn.sm { padding: 4px 8px; font-size: 12px; }
.prt-group-dialog-body .prt-condition-row.is-compact :is(input,select:not([multiple]),.prt-condition-field) { min-height: 36px; line-height: 20px; }
.prt-group-dialog-body :is(.prt-condition-actions,.prt-condition-group-actions) { justify-content: flex-end; align-items: center; gap: 8px; }
.prt-group-dialog-body .prt-condition-group-actions { border-top: 1px solid var(--border); padding-top: 8px; margin-top: 4px; }
.prt-group-dialog-body :is(.prt-condition-actions,.prt-condition-group-actions) > .btn { min-height: 30px; margin: 0; }
.prt-group-dialog .prt-dialog-extra.danger-zone > .field-error:empty { display: none; }
.prt-group-dialog .prt-dialog-extra p { margin: 6px 0 8px; }
@media (max-width: 600px) {
  .prt-group-dialog-body .prt-condition-row.is-compact { grid-template-columns: minmax(0, 1fr); }
  .prt-condition-row.is-compact > :is(.prt-condition-operand,.prt-condition-operator,.prt-condition-value,.prt-condition-tools,.prt-condition-actions) { grid-column: 1; grid-row: auto; }
}
/* Guided condition editor: each logic word has a stable, distinct colour. */
.modal.prt-group-dialog { max-width: 800px; }
/* One width for every configurator dialog, including nested pickers; heights fit content.
   The shared page marker scopes this to configuration work, not other screens. */
body:has(.prt-te-v2:not(.prt-dialog-scroll)) {
  --prt-config-dialog-width: 800px;
}
body:has(.prt-te-v2:not(.prt-dialog-scroll)) .modal {
  box-sizing: border-box;
  width: min(calc(100vw - 40px), var(--prt-config-dialog-width));
  max-width: var(--prt-config-dialog-width);
  height: auto;
  max-height: 90dvh;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
body:has(.prt-te-v2:not(.prt-dialog-scroll)) .modal > .modal-body {
  min-height: 0;
  overflow: auto;
}
body:has(.prt-te-v2:not(.prt-dialog-scroll)) .modal > :is(.modal-title,.modal-actions,.modal-head,.modal-foot,.prt-dialog-heading) {
  flex-shrink: 0;
}
/* Muted logic accents blend with the current grey surface in either theme. */
.prt-group-dialog { --logic-and:color-mix(in srgb, #b388ed 35%, var(--text)); --logic-and-bg:color-mix(in srgb, #b388ed 12%, var(--panel)); --logic-and-line:color-mix(in srgb, #b388ed 40%, var(--border)); --logic-or:color-mix(in srgb, #e6b34e 35%, var(--text)); --logic-or-bg:color-mix(in srgb, #e6b34e 12%, var(--panel)); --logic-or-line:color-mix(in srgb, #e6b34e 40%, var(--border)); }
.prt-logic-tag { display:inline-flex; align-items:center; justify-content:center; padding:3px 8px; border:1px solid; border-radius:5px; font-size:11px; line-height:18px; letter-spacing:.35px; font-weight:800; white-space:nowrap; }
.prt-logic-tag.prt-logic-when { color:color-mix(in srgb, #6daef0 35%, var(--text)); background:color-mix(in srgb, #6daef0 12%, var(--panel)); border-color:color-mix(in srgb, #6daef0 30%, var(--border)); }
.prt-logic-tag.prt-logic-if { color:color-mix(in srgb, #d778a8 35%, var(--text)); background:color-mix(in srgb, #d778a8 12%, var(--panel)); border-color:color-mix(in srgb, #d778a8 40%, var(--border)); }
.prt-logic-tag.prt-logic-and { color:var(--logic-and); background:var(--logic-and-bg); border-color:var(--logic-and-line); }
.prt-logic-tag.prt-logic-or { color:var(--logic-or); background:var(--logic-or-bg); border-color:var(--logic-or-line); }
.prt-logic-tag.prt-logic-then { color:color-mix(in srgb, #71c6a5 35%, var(--text)); background:color-mix(in srgb, #71c6a5 12%, var(--panel)); border-color:color-mix(in srgb, #71c6a5 30%, var(--border)); }
.prt-logic-tag.prt-logic-not { color:color-mix(in srgb, #ba7460 35%, var(--text)); background:color-mix(in srgb, #ba7460 12%, var(--panel)); border-color:color-mix(in srgb, #ba7460 40%, var(--border)); }
.prt-logic-summary { display:flex; align-items:center; flex-wrap:wrap; gap:8px 10px; padding:12px; margin-bottom:14px; border:1px solid color-mix(in srgb, #6daef0 24%, var(--border)); border-left:3px solid color-mix(in srgb, #6daef0 40%, var(--border)); border-radius:9px; background:color-mix(in srgb, #6daef0 5%, var(--panel)); font-size:13px; line-height:1.8; }
.prt-logic-summary > span:not(.prt-logic-tag) { flex:1; min-width:160px; overflow-wrap:anywhere; }
.prt-logic-summary > small { color:var(--muted); font-size:10px; }
.prt-logic-summary .prt-logic-field { font-style:italic; font-weight:400; }
.prt-logic-summary .prt-logic-operator { text-decoration:underline; text-underline-offset:3px; font-weight:400; }
.prt-logic-summary .prt-logic-value { font-weight:700; }
.prt-logic-summary .prt-logic-tag:not(.prt-logic-when) { padding:0 4px; font-size:10px; line-height:16px; }
.prt-logic-bracket { display:inline-flex; align-items:center; justify-content:center; min-width:14px; padding:0 3px; margin:0 2px; border:1px solid; border-radius:5px; font-size:12px; line-height:16px; font-weight:800; vertical-align:baseline; }
.prt-logic-summary .prt-logic-bracket.prt-logic-and { color:var(--logic-and); background:var(--logic-and-bg); border-color:var(--logic-and-line); }
.prt-logic-summary .prt-logic-bracket.prt-logic-or { color:var(--logic-or); background:var(--logic-or-bg); border-color:var(--logic-or-line); }
.prt-logic-if-card { display:grid; grid-template-columns:auto minmax(0,1fr); align-items:start; gap:10px; padding:12px; border:1px solid color-mix(in srgb, #d778a8 24%, var(--border)); border-left:3px solid color-mix(in srgb, #d778a8 40%, var(--border)); border-radius:9px; background:color-mix(in srgb, #d778a8 4%, var(--panel)); }
.prt-logic-if-card > .prt-logic-tag { margin-top:5px; }
.prt-logic-if-card > :not(.prt-logic-tag) { grid-column:2; min-width:0; }
.prt-logic-if-card > [data-condition-always] { justify-self:start; }
.prt-logic-group { min-width:0; border:1px solid color-mix(in srgb, #b388ed 24%, var(--border)); border-left:3px solid var(--logic-and-line); background:color-mix(in srgb, #b388ed 5%, var(--panel)); border-radius:8px; padding:10px; }
.prt-logic-group.prt-logic-or { border-color:color-mix(in srgb, #e6b34e 24%, var(--border)); border-left-color:var(--logic-or-line); background:color-mix(in srgb, #e6b34e 5%, var(--panel)); }
.prt-logic-group.prt-logic-negated { border-color:color-mix(in srgb, #ba7460 40%, var(--border)); background:color-mix(in srgb, #ba7460 5%, var(--panel)); }
.prt-logic-group.is-root { border:0; padding:0; background:transparent; }
.prt-logic-group .prt-logic-group { margin-top:8px; }
.prt-logic-connector { display:flex; align-items:center; gap:10px; margin:8px 0; }
.prt-logic-connector::before,.prt-logic-connector::after { content:''; height:1px; flex:1; background:var(--logic-and-line); }
.prt-logic-connector.prt-logic-or::before,.prt-logic-connector.prt-logic-or::after { background:var(--logic-or-line); }
.prt-group-dialog-body .prt-logic-connector select { width:76px; min-height:28px; padding:3px 7px; font-size:11px; font-weight:800; background:var(--logic-and-bg); color:var(--logic-and); border:1px solid var(--logic-and-line); border-radius:5px; }
.prt-group-dialog-body .prt-logic-connector.prt-logic-or select { background:var(--logic-or-bg); color:var(--logic-or); border-color:var(--logic-or-line); }
.prt-group-dialog-body .prt-guided-builder .prt-condition-row.is-compact { padding:0; grid-template-columns:minmax(0,1.2fr) minmax(90px,.65fr) minmax(0,1.3fr); }
.prt-guided-builder .prt-condition-operand .muted.small { display:none; }
.prt-group-dialog-body .prt-guided-builder .prt-condition-row :is(.prt-condition-operator,.prt-condition-value) { font-size:0; line-height:0; gap:0; }
.prt-guided-builder .prt-condition-field { text-align:left; }
.prt-group-dialog-body .prt-guided-builder :is(.prt-condition-actions,.prt-condition-group-actions) { border:0; margin:0; padding-top:6px; gap:6px; }
.prt-group-dialog-body .prt-guided-builder .prt-condition-actions .btn { font-size:11px; min-height:26px; padding:2px 6px; }
.prt-logic-outputs { border:1px solid color-mix(in srgb, #71c6a5 24%, var(--border)); border-left:3px solid color-mix(in srgb, #71c6a5 40%, var(--border)); border-radius:9px; padding:12px; background:color-mix(in srgb, #71c6a5 5%, var(--panel)); }
.prt-group-dialog-body .prt-logic-outputs h3 { display:flex; align-items:center; gap:10px; margin:0 0 12px; font-size:13px; }
.prt-logic-outputs .check { border:1px solid var(--border-strong); border-radius:6px; padding:6px 9px; font-size:12px; }
.prt-logic-outputs .check:has(:checked) { color:color-mix(in srgb, #71c6a5 30%, var(--text)); background:color-mix(in srgb, #71c6a5 12%, var(--panel)); border-color:color-mix(in srgb, #71c6a5 35%, var(--border)); }
.prt-logic-outputs input { accent-color:color-mix(in srgb, #71c6a5 65%, var(--text)); }
@media(max-width:600px) {
 .prt-logic-if-card { grid-template-columns:minmax(0,1fr); }
 .prt-logic-if-card > :not(.prt-logic-tag) { grid-column:1; }
 .prt-logic-if-card > .prt-logic-tag { justify-self:start; }
 .prt-group-dialog-body .prt-guided-builder .prt-condition-row.is-compact { grid-template-columns:minmax(0,1fr); }
 .prt-guided-builder .prt-logic-group { padding:8px; }
}
.prt-selection-frame .prt-selection-list { grid-template-columns: repeat(auto-fit, minmax(min(100%, max(320px, calc((100% - 24px) / 3))), 1fr)); margin-top: 0; }
.prt-selection-frame .prt-selection-list .card-head { padding: 0; }
.prt-selection-frame .prt-selection-list p { overflow-wrap: anywhere; }
.prt-output-plan .row-gap { flex-wrap: wrap; }
.prt-conditional-groups label:not(.check), .prt-binding-editor label { display: flex; flex-direction: column; gap: 6px; }
.prt-delivery { gap: 24px; }
.prt-output-plan input[type=checkbox] { width: auto; }
.prt-selection-editor fieldset { min-width: 0; }

/* Configuration state and output selection are distinct, explicitly named controls. */
.prt-configuration-availability { display:flex; align-items:center; justify-content:space-between; gap:20px; padding:16px 20px; margin:0 0 20px; border:1px solid var(--border); border-radius:12px; }
.prt-configuration-availability p { margin:5px 0 0; }
.prt-document-picker { display:flex; flex-wrap:wrap; align-items:start; }
.prt-document-picker label { display:flex; flex-direction:column; gap:6px; flex:1; min-width:200px; }
.prt-output-plan .prt-group-choice { display:inline-flex; align-items:center; gap:8px; margin:8px 0 0; }
.prt-conditional-groups > .row-gap { align-items:end; flex-wrap:wrap; }

/* Actual PDF pages, fitted to the panel without changing the printed layout. */
.prt-hl-live-pages { flex: 1 1 auto; min-height: 0; overflow: auto; background: var(--panel3); padding: 16px; }
.prt-hl-pdfpages { display: grid; gap: 20px; }
.prt-hl-pdfpage { margin: 0 auto; width: 100%; max-width: 1000px; }
.prt-hl-pdfpage figcaption { color: var(--muted); font-size: 12px; margin-bottom: 8px; }
.prt-hl-pdfpage canvas { display: block; width: 100%; height: auto; background: white; box-shadow: 0 2px 8px #0003; }
/* Printable-area design guide over the live pages (never in the PDF): the PDF guide's red edges and green boundary. */
.prt-hl-pdfsheet { position: relative; }
.prt-hl-guide { position: absolute; inset: 0; pointer-events: none; }
.prt-hl-guide-edge { position: absolute; background: rgb(255 26 26 / .22); }
.prt-hl-guide-line { position: absolute; border: 1px solid rgb(13 128 51); }
.prt-hl-guide-note { margin: 0; color: var(--muted); }

/* Shared Print Studio list and portable-file controls. */
.view:has(> .prt) { padding-bottom:16px; }
/* Final cards/wrappers must not add a second gap after the page's content. */
.view > .prt > :last-child,
.prt :is(#prt-body, #prt-doc, #prt-settings, #prt-sources, [data-print-server-list]:last-child) > :last-child,
.prt .prt-pp-pair [data-print-server-list] > :last-child,
.prt #prt-settings > :last-child > .card:last-child { margin-bottom:0; }
.prt-pagination { display:flex; align-items:center; justify-content:space-between; gap:1rem; min-height:44px; padding:8px 12px; box-sizing:border-box; border-top:1px solid var(--border,#303642); }
.prt-pagination[hidden], .prt-list-region tr[hidden] { display:none; }
.prt-list-region { min-width:0; max-width:100%; overflow-x:auto; }
.prt-transfer-tools { margin:8px 0; justify-content:flex-end; }
.card-head > .prt-transfer-tools { margin:0 0 0 auto; align-items:center; }
.card-head > .prt-layout-search { flex:1 1 240px; width:auto; min-width:160px; max-width:800px; }
.prt-transfer-dialog { width:min(900px,94vw); max-height:90dvh; display:flex; flex-direction:column; }
.prt-transfer-dialog .modal-body { overflow:auto; min-height:0; }
.prt-transfer-dialog .modal-head,.prt-transfer-dialog .modal-foot { padding:16px 24px; display:flex; gap:8px; justify-content:space-between; }
.prt-transfer-item { padding:14px 0; border-bottom:1px solid var(--border,#303642); display:flex; gap:12px; align-items:center; flex-wrap:wrap; }
.prt-transfer-item p { flex-basis:100%; margin:0; }
.prt-transfer-item label { display:flex; align-items:center; gap:8px; }
.prt-transfer-item input:not([type=checkbox]), .prt-transfer-item select { max-width:300px; }

/* Page controls replace the previous nested scrolling bodies. */
[data-print-server-list] .prt-pp-card-body { max-height:none; overflow:visible; }
[data-print-server-list] { min-width:0; }
[data-print-server-list] > .card { overflow-x:auto; }
[data-print-local-list] > [hidden], .prt-iu-grid > [hidden] { display:none; }

[data-print-paged] > [hidden] { display:none; }

/* Keep responsive card layouts while each list owns its pagination. */
.prt-pp-pair.prt-pp-paged-pair { height:auto; grid-template-rows:auto; gap:14px; align-items:stretch; }
.prt-pp-paged-pair > [data-print-server-list], .prt-pp-paged-pair .fb-compact,
.prt-pp-paged-pair .fb-list-card, .prt-pp-paged-pair [data-list-results],
.prt-pp-paged-pair .prt-list-content, .prt-pp-paged-pair .prt-pp-card-body { display:flex; flex-direction:column; flex:1 1 auto; min-width:0; }
.prt-pp-paged-pair .fb-list-card { margin-bottom:0; }
.prt-pp-paged-pair .card-head, .prt-pp-paged-pair table, .prt-pp-paged-pair .prt-pagination { flex:none; }
.prt-pp-paged-pair .prt-pagination { margin-top:auto; }
.prt-family-settings-region { container:printsettings / inline-size; }
.prt-family-settings-grid { display:grid; grid-template-columns:minmax(0,1fr); gap:14px; align-items:stretch; }
.prt-family-settings-grid > .card { min-width:0; max-width:none; margin-bottom:0; }
@container printsettings (min-width:750px) {
  .prt-family-settings-grid { grid-template-columns:repeat(2,minmax(0,1fr)); }
}
@container printsettings (min-width:1150px) {
  .prt-family-settings-grid { grid-template-columns:repeat(3,minmax(0,1fr)); }
}

/* ------------------------------------------------------------------ history
   The History card (0217): every ledger entry about the document, one line
   each — when · who · what — newest first. Sized to a list of one-line
   sentences, like the notes card is sized to prose (R-UI-1). Automatic steps
   (the system, the store) are greyed but shown: a store-imported order's story
   IS mostly automatic, and hiding it makes the card read as empty. */
.history-card { max-width: 900px; }
.history-card .card-head { padding: 0 0 10px; display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.hist-tools { margin-left: auto; display: inline-flex; align-items: center; gap: 12px; font-size: 12.5px; }
.hist-chip { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); cursor: pointer; }
.hist-who-filter { font: inherit; font-size: 12.5px; padding: 2px 6px; border: 1px solid var(--border-strong); border-radius: var(--r-sm); background: var(--panel); color: var(--text); }
.hist-list { list-style: none; margin: 6px 0 0; padding: 0; }
.hist-row { display: grid; grid-template-columns: 128px 1fr; gap: 12px; padding: 7px 0 8px; border-top: 1px solid var(--border); font-size: 13.5px; line-height: 1.5; }
.hist-row:first-child { border-top: none; padding-top: 2px; }
.hist-row.hist-over, .hist-row.hist-filtered { display: none; }
.hist-when { color: var(--muted); font-size: 12.5px; font-variant-numeric: tabular-nums; padding-top: 1px; white-space: nowrap; }
.hist-main { min-width: 0; }
.hist-who { font-weight: 700; margin-right: 6px; }
.hist-row.auto .hist-who { font-weight: 600; }
.hist-row.auto .hist-text { color: var(--muted); }
.hist-text a { font-weight: 600; }
.hist-seq { margin-left: 8px; font-size: 11.5px; color: var(--muted); text-decoration: none; opacity: 0; transition: opacity .12s; }
.hist-row:hover .hist-seq, .hist-row:focus-within .hist-seq { opacity: 1; }
.hist-more { margin-left: 4px; border: none; background: transparent; color: var(--muted); cursor: pointer; font: inherit; font-size: 12px; padding: 0 4px; opacity: 0; transition: opacity .12s; }
.hist-row:hover .hist-more, .hist-row:focus-within .hist-more, .hist-more[aria-expanded="true"] { opacity: 1; }
.hist-detail { list-style: none; margin: 6px 0 2px; padding: 8px 10px; background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-sm); font-size: 12.5px; }
.hist-detail li { padding: 2px 0; }
.hist-detail .hist-raw { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 11.5px; color: var(--muted); }
.hist-foot { margin-top: 10px; }
.hist-none { padding: 6px 0 2px; font-size: 13px; }
/* The sales order's foot gains a row of its own for it, under Payments and Notes. */
.so-history { margin-top: 16px; }
/* The audit ledger page speaks the same sentences; the raw payload waits behind a toggle. */
.ledger-what { max-width: 620px; }
.ledger-raw { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 11.5px; color: var(--muted); margin-top: 4px; }
tr.ledger-hit td { background: var(--warn-bg); }


/* ---- Files on a document (0224) -------------------------------------------
   The twin of the notes card, and it sits beside one at the foot of the same
   documents. A thumbnail grid rather than a list, because the commonest
   attachment here is a photo of something broken and the whole point of it is
   to be looked at. Tiles are a fixed width so a row of them reads as a
   contact sheet; the caption block below each one carries the words. */
.files-card .files-add {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 14px; border-bottom: 1px solid var(--border);
}
.files-card .files-caption { flex: 1 1 220px; min-width: 0; }
.files-card .files-kind { flex: none; }
.files-card .files-status { font-size: 12px; }
.files-card .files-empty { padding: 14px; font-size: 13px; }

.files-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
  gap: 12px; padding: 12px 14px;
}
.file-tile { margin: 0; border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; background: var(--panel); }
.file-thumb {
  display: flex; align-items: center; justify-content: center;
  height: 118px; background: var(--panel2); overflow: hidden;
}
.file-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.file-icon { font-size: 15px; font-weight: 700; letter-spacing: .06em; color: var(--muted); }
.file-tile figcaption { padding: 8px 9px; display: flex; flex-direction: column; gap: 3px; }
.file-name {
  font-size: 12.5px; font-weight: 600; color: var(--text); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.file-name:hover { text-decoration: underline; }
.file-cap { font-size: 12px; color: var(--text); }
.file-tile .file-rm { align-self: flex-start; margin-top: 3px; }

/* ---- Stocktakes (2026-09-23) --------------------------------------------
   The count screen. Namespaced stk- (`.st-*` is the status-colour family).
   The chassis does the rest: .stats tiles, .card.fb for the find bar, .seg for
   the views, .card.np for the lines. */
.stk-prog { min-width: 150px; }
.stk-prog .num { display: inline-block; min-width: 9ch; margin-right: 8px; }
.stk-prog .bar { display: inline-block; width: 64px; height: 6px; vertical-align: middle; }
/* "1,204 of 3,655": the second figure is the BASIS, so it steps down to body
   weight and size rather than competing with the number. */
.stk-of { font-size: 14.5px; font-weight: 600; color: var(--muted); letter-spacing: 0; }
.stk-money { display: flex; flex-wrap: wrap; gap: 0 12px; }
.stk-bar .fb-row { align-items: center; }
.stk-scan { flex: 1 1 280px; min-width: 220px; }
.stk-scanadd { margin: 0; font-size: 13px; white-space: nowrap; }
.stk-scanadd input { width: auto; }
.stk-table table { table-layout: auto; }
.stk-table tr.stk-group td { background: var(--panel3); border-top: 1px solid var(--border); padding-top: 9px; padding-bottom: 7px; font-size: 13px; }
.stk-table tbody:first-of-type tr.stk-group td { border-top: none; }
.stk-table td.stk-count { width: 96px; }
.stk-in { width: 84px; text-align: right; padding: 5px 8px; font-variant-numeric: tabular-nums; font-weight: 600; }
.stk-in::placeholder { color: var(--faint); font-weight: 400; }
.stk-in.stk-saving { border-color: var(--border-strong); background: var(--panel2); }
.stk-table tr.stk-todo td { color: var(--text); }
.stk-table tr.stk-held td:first-child { box-shadow: inset 3px 0 0 var(--bad); }
.stk-hold { color: var(--bad); max-width: 62ch; white-space: normal; }
.stk-who { line-height: 1.35; }
.stk-table tr.stk-saved td { background: var(--ok-bg); transition: background 0.9s ease-out; }
.stk-table tr.stk-hit td { background: var(--accent-wash); }
.stk-new .seg { display: inline-flex; }
.stk-new .stk-chosen { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; min-height: 26px; }
.stk-new .sn-results { position: static; max-height: 240px; margin-top: 6px; }
.stk-sheet { max-width: 560px; max-height: calc(100vh - 40px); overflow-y: auto; }
.stk-sheet input[type="file"] { width: 100%; }
.stk-sheet-cols { display: flex; gap: 10px; flex-wrap: wrap; margin: 4px 0 10px; }
.stk-sheet-cols select { min-width: 180px; }
.stk-sheet .modal-body p { margin: 0 0 6px; }
.stk-sheet-miss { margin-top: 6px; font-size: 13.5px; }
.stk-sheet-miss ul { margin: 4px 0 0; padding-left: 18px; max-height: 160px; overflow-y: auto; }
@media (pointer: coarse) { .stk-in { min-height: 40px; font-size: 16px; } }
@media (max-width: 900px) { .stk-of { display: block; } .stk-table td.stk-who { display: none; } .stk-table th:last-child { display: none; } }

/* ---- THE SIGN-IN STAGE (owner's design, 2026-09-24; sign-in cards 0285) -----
   Every door — sign in, the authenticator code, the new passphrase, setting an
   app up — on the desk and on the floor stands here: near-black with a faint
   red glow, the network (signin.js draws it on .signin-net), the QUAKE mark top
   left, ONE white card in the middle, the language pill bottom left.
   The stage is dark and the card is paper WHATEVER the theme, so the card
   re-declares the light tokens for everything inside it — the enrolment
   wizard's QR and fields included. */
#boot-block.signin-stage {
  background:
    radial-gradient(circle at 18% 24%, rgba(170, 0, 10, 0.05), transparent 24%),
    radial-gradient(circle at 80% 76%, rgba(170, 0, 10, 0.035), transparent 26%),
    linear-gradient(#090a0d, #050609);
  color-scheme: dark;
  overflow: auto;
  padding: 24px 18px;
}
.signin-net { position: fixed; inset: 0; width: 100%; height: 100%; display: block; z-index: 0; pointer-events: none; }
.signin-shade { position: fixed; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(circle at center, transparent 30%, rgba(0, 0, 0, 0.1) 64%, rgba(0, 0, 0, 0.4) 100%); }
.signin-brand { position: fixed; top: 70px; left: 36px; z-index: 3; color: #eceef1; opacity: 0.94;
  pointer-events: none; user-select: none; -webkit-user-select: none; }
.signin-brand .quake-mark { width: 166px; height: auto; display: block; filter: drop-shadow(0 0 18px rgba(239, 27, 36, 0.08)); }

.signin-stage > .boot-block-box {
  --bg: #f4f6fa; --panel: #ffffff; --panel2: #eef1f6; --panel3: #f7f9fc; --chip: #eef1f6;
  --border: #dfe3ea; --border-strong: #bcc3ce;
  --text: #121419; --muted: #7b8390; --faint: #99a1b1;
  --accent: #ef1b24; --accent-hover: #b90711; --accent-deep: #b90711; --accent-ink: #ffffff;
  --accent-wash: #fdeced; --accent-wash-bd: #f6d3d5;
  --ok: #1c7d43; --ok-bg: #e6f6ec; --ok-bd: #b7e2c5;
  --warn: #8a6100; --warn-bg: #fbf0d5; --warn-bd: #eed699;
  --bad: #d41b22; --bad-bg: #fdeaea; --bad-bd: #f3c8c8; --bad-solid: #d32f2f;
  --ring: 0 0 0 3px rgba(239, 27, 36, 0.12);
  color-scheme: light;
  position: relative; z-index: 4;
  width: min(360px, calc(100vw - 36px)); max-width: none;
  padding: 22px 25px 24px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.975);
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: 0 28px 80px rgba(0, 0, 0, 0.42), 0 2px 9px rgba(0, 0, 0, 0.18);
  color: var(--text);
}
.signin-stage > .boot-block-box h1 { font-size: 23px; letter-spacing: -0.025em; margin: 0 0 4px; }
.signin-stage .signin-sub { font-size: 13px; color: var(--muted); margin: 0 0 18px; }
.signin-stage .field { gap: 7px; margin-bottom: 15px; }
.signin-stage .field > label { color: #596170; font-size: 12px; font-weight: 650; }
.signin-stage .field > input[type="text"],
.signin-stage .field > input[type="password"] { height: 42px; padding: 0 12px; border: 1px solid var(--border); border-radius: 8px; background: #fff; font-size: 14px; }
.signin-stage .field > input:focus { border-color: var(--border-strong); box-shadow: 0 0 0 3px rgba(239, 27, 36, 0.075); }
.signin-stage .field .hint { font-size: 12px; line-height: 1.45; color: var(--muted); }
.signin-stage .field-error { margin: 0 0 11px; color: var(--bad); font-size: 12px; line-height: 1.4; }
.signin-stage .field-error:empty { display: none; }
.signin-stage > .boot-block-box .btn { width: 100%; height: 42px; justify-content: center; border-radius: 8px; margin-top: 0; }
.signin-stage > .boot-block-box .btn.primary { border: 0; background: linear-gradient(180deg, #ef1b24, #b90711); color: #fff;
  font-weight: 750; box-shadow: 0 7px 18px rgba(239, 27, 36, 0.18); }
.signin-stage > .boot-block-box .btn.primary:disabled { opacity: 0.7; cursor: progress; }
.signin-stage > .boot-block-box .btn + .btn { margin-top: 8px; }

/* The two ways in: one control, two halves — the half you are on is the card.
   Each half is as wide as its words plus a share of the rest, so "Username &
   password" is never cut off beside the shorter "Scan a code", phone or not. */
.signin-ways { display: flex; gap: 3px; padding: 3px; margin: 0 0 18px; background: #e7eaf0; border-radius: 9px; }
.signin-ways [role="tab"] { flex: 1 1 auto; min-width: 0; height: 32px; border: 0; border-radius: 7px; background: transparent;
  color: #7b8390; font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; padding: 0 8px; transition: background 0.12s, color 0.12s; }
.signin-ways [role="tab"]:hover { color: var(--text); }
.signin-ways [role="tab"][aria-selected="true"] { background: #fff; color: var(--text); font-weight: 700;
  box-shadow: 0 1px 3px rgba(18, 20, 25, 0.16), 0 0 0 1px rgba(18, 20, 25, 0.05); }
.signin-ways [role="tab"]:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(239, 27, 36, 0.16); }
/* The phone's camera, where the browser can read a QR itself. */
.signin-camera { margin: 0 0 12px; border-radius: 8px; overflow: hidden; background: #000; }
.signin-camera video { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; }

.signin-stage > .boot-lang { z-index: 5; }
.signin-stage > .boot-lang .lang-flip { background: rgba(255, 255, 255, 0.02); border: 1px solid rgba(255, 255, 255, 0.08);
  color: #aeb3bb; box-shadow: none; font-size: 11.5px; padding: 6px 11px; }
.signin-stage > .boot-lang .lang-flip:hover { color: #fff; background: rgba(255, 255, 255, 0.06); }

/* A phone, a tablet held upright, a narrow window: the mark cannot sit beside
   the card, so it stands above it, smaller, and the language pill below — one
   column, centred when it fits and scrolling when it does not (the auto
   margins, not justify-content, so the top is never cut off). The fields go
   to 16px because iOS zooms into anything smaller. */
@media (max-width: 859px) {
  #boot-block.signin-stage { display: flex; flex-direction: column; align-items: center; gap: 22px;
    padding: calc(24px + env(safe-area-inset-top)) 18px calc(20px + env(safe-area-inset-bottom)); }
  .signin-brand { position: relative; top: auto; left: auto; margin-top: auto; }
  .signin-brand .quake-mark { width: 84px; }
  .signin-stage > .boot-lang { position: relative; left: auto; bottom: auto; margin-bottom: auto; }
  .signin-stage .field > input[type="text"],
  .signin-stage .field > input[type="password"] { font-size: 16px; }
}

/* ============================================================================
   FEEDBACK (0297) — the Feedback row under Home, the F7 window, the list, and
   the picture viewer. Every class is fbk- (fb- belongs to the filter bar).
   ========================================================================== */

/* The sidebar row: the count is for the people who answer; the key is for
   everybody, so the shortcut is learned by looking at the sidebar. */
#nav a.nav-feedback .nav-count {
  font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums;
  background: var(--bad-bg); border: 1px solid var(--bad-bd); color: var(--bad);
  border-radius: var(--r-pill); padding: 0 7px; line-height: 17px;
}
#nav a.nav-feedback .nav-count[hidden] { display: none; }
/* The same key cap as the Escape palette's. */
.nav-kbd, .fbk-kbd {
  font-family: var(--mono); font-size: 10.5px; font-weight: 600; color: var(--muted);
  background: var(--panel); border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 5px;
  padding: 1px 5px; line-height: 1.4; flex: none;
}
#nav a.nav-feedback .nav-kbd { color: var(--faint); }
#nav a.nav-feedback:hover .nav-kbd, #nav a.nav-feedback.active .nav-kbd { color: var(--muted); }
.btn.primary .fbk-kbd { color: var(--accent-ink); background: transparent; border-color: currentColor; opacity: .8; }

/* ---- The window ---- */
.modal.fbk-modal { max-width: 600px; }
.fbk-head { display: flex; align-items: center; gap: 10px; }
.fbk-head .modal-title { margin: 0; }
.fbk-where { font-size: 13px; color: var(--muted); margin: 4px 0 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fbk-where b { color: var(--text); font-weight: 600; }
.fbk-modal textarea { display: block; resize: vertical; min-height: 116px; line-height: 1.5; }
.fbk-row { display: flex; align-items: center; justify-content: space-between; gap: 10px 16px; flex-wrap: wrap; margin: 10px 0 12px; }
.fbk-pics { display: flex; flex-wrap: wrap; gap: 8px; }
.fbk-pic {
  position: relative; width: 132px; height: 86px; margin: 0; padding: 6px; overflow: hidden;
  display: flex; align-items: center; justify-content: center; text-align: center;
  border: 1px solid var(--border); border-radius: var(--r); background: var(--panel2);
  font: inherit; font-size: 12px; color: var(--muted);
}
.fbk-pic:has(img) { padding: 0; }
.fbk-pic img { width: 100%; height: 100%; object-fit: cover; object-position: top left; display: block; }
.fbk-pic figcaption {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 1px 7px; text-align: left;
  font-size: 11.5px; font-weight: 600; color: var(--text); background: var(--panel); border-top: 1px solid var(--border);
}
.fbk-pic-x {
  position: absolute; top: 4px; right: 4px; width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center; padding: 0; cursor: pointer; font-size: 15px; line-height: 1;
  color: var(--text); background: var(--panel); border: 1px solid var(--border-strong); box-shadow: var(--shadow-sm);
}
.fbk-pic-x:hover { color: var(--bad); border-color: var(--bad-bd); }
.fbk-pic-busy, .fbk-pic-add { border-style: dashed; border-color: var(--border-strong); }
.fbk-pic-add { cursor: pointer; background: var(--panel); font-weight: 600; }
.fbk-pic-add:hover { color: var(--text); background: var(--panel2); }
.fbk-pic-add:focus-visible { outline: none; box-shadow: var(--ring); }
.fbk-pic-note { flex-basis: 100%; font-size: 13px; }
.fbk-hint { margin-top: 10px; font-size: 13px; color: var(--muted); }
.fbk-modal .field-error:empty { display: none; }
.fbk-modal .field-error { margin-top: 8px; }
.fbk-modal .modal-actions { margin-top: 14px; }
.fbk-mine { font-size: 13px; }
.modal.fbk-modal.is-drop { border-color: var(--accent); box-shadow: var(--ring), var(--shadow-lg); }

/* ---- The list ---- */
.fbk-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 16px; }
.fbk-bar select { width: auto; max-width: 260px; padding: 5px 10px; }
.fbk-bar input[type="search"] { width: auto; flex: 0 1 300px; min-width: 200px; padding: 5px 12px; }
.fbk-n { font-variant-numeric: tabular-nums; opacity: .72; margin-left: 2px; }
/* R-UI-1: a feed of messages is read, not scanned across — it stops at a
   reading width, and the picture sits beside the words. */
.fbk-list { max-width: 1120px; }
.fbk-day { font-size: 13px; font-weight: 600; color: var(--muted); margin: 22px 0 8px; }
.fbk-list > .fbk-day:first-child { margin-top: 0; }
.card.fbk-card { display: flex; gap: 18px; padding: 16px 18px; margin-bottom: 10px; }
.fbk-card.is-saving { opacity: .55; pointer-events: none; }
.fbk-card-main { flex: 1 1 auto; min-width: 0; }
.fbk-card-head { display: flex; align-items: flex-start; gap: 10px; }
.fbk-who { flex: 1 1 auto; min-width: 0; line-height: 1.4; }
.fbk-who .sub { margin-top: 0; }
.fbk-tags { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.fbk-page { display: inline-block; margin-top: 10px; font-size: 13px; color: var(--muted); text-decoration: none; overflow-wrap: anywhere; }
.fbk-page b { color: var(--text); font-weight: 600; }
.fbk-page:hover, .fbk-page:hover b { color: var(--accent); }
.fbk-text { margin-top: 6px; white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.55; }
.fbk-reply { margin-top: 10px; padding: 9px 12px; background: var(--panel3); border: 1px solid var(--border); border-radius: var(--r); }
.fbk-reply .sub { margin-top: 0; }
.fbk-reply-text { white-space: pre-wrap; overflow-wrap: anywhere; margin-top: 2px; }
.fbk-details { margin-top: 10px; font-size: 13px; }
.fbk-details summary { cursor: pointer; color: var(--muted); font-weight: 600; width: fit-content; }
.fbk-details summary:hover { color: var(--text); }
.fbk-details dl { display: grid; grid-template-columns: 104px 1fr; gap: 3px 12px; margin: 8px 0 0; }
.fbk-details dt { color: var(--muted); }
.fbk-details dd { margin: 0; overflow-wrap: anywhere; }
.fbk-trouble-n { color: var(--bad); margin-left: 6px; }
.fbk-trouble-head { margin: 10px 0 4px; color: var(--muted); font-weight: 600; }
.fbk-trouble { margin: 0; padding-left: 20px; }
.fbk-trouble li { margin: 2px 0; overflow-wrap: anywhere; }
.fbk-card-foot { display: flex; align-items: center; gap: 8px 10px; flex-wrap: wrap; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
.fbk-card-foot .sub { margin-top: 0; }
.fbk-answer { margin-top: 12px; }
.fbk-answer textarea { display: block; margin-bottom: 8px; resize: vertical; }
/* The first picture large, any more as a row of small ones under it. */
.fbk-shots { flex: 0 0 240px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; align-content: start; }
.fbk-shot { display: block; aspect-ratio: 16 / 10; overflow: hidden; border: 1px solid var(--border); border-radius: var(--r); background: var(--panel2); cursor: zoom-in; }
.fbk-shot:first-child { grid-column: 1 / -1; }
.fbk-shot:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }
.fbk-shot img { width: 100%; height: 100%; object-fit: cover; object-position: top left; display: block; }
.card.fbk-empty { max-width: 560px; color: var(--muted); }
@media (max-width: 760px) {
  .card.fbk-card { flex-direction: column; }
  .fbk-shots { flex-basis: auto; }
  .fbk-card-head { flex-wrap: wrap; }
}

/* ---- The picture viewer ----
   Dark in both themes, because a screenshot is looked at, and a light frame
   around a light screenshot loses its edges — so its words are always light. */
.fbk-lightbox {
  position: fixed; inset: 0; z-index: 110; padding: 24px; gap: 12px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: rgba(8, 10, 16, 0.88); cursor: zoom-out;
}
.fbk-lightbox img { max-width: 100%; max-height: calc(100vh - 110px); object-fit: contain; border-radius: var(--r); box-shadow: var(--shadow-lg); background: var(--panel); }
.fbk-lb-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; width: min(100%, 1100px); color: #eef1f6; font-size: 13px; cursor: default; }
/* Shared named document controls in Studio and People / Positions. */
.prt-delivery-heading { display:flex; align-items:center; justify-content:space-between; gap:16px; }
.prt-delivery-heading h2 { margin:0; }
.prt-delivery-heading p { margin:6px 0; }
.prt-delivery-category { margin-top:22px; }
.prt-delivery-category h3 { font-size:13px; margin:0 0 8px; color:var(--muted); }
.prt-delivery-scroll { overflow-x:auto; }
.prt-delivery-table { width:100%; border-collapse:collapse; }
.prt-delivery-table th { text-align:left; font-size:11px; text-transform:uppercase; color:var(--muted); }
.prt-delivery-table th,.prt-delivery-table td { padding:12px; border-bottom:1px solid var(--border); vertical-align:middle; }
.prt-delivery-table select { min-width:150px; width:100%; }
.prt-methods { display:flex; gap:18px; flex-wrap:wrap; }
.prt-method { display:inline-flex; gap:7px; align-items:center; white-space:nowrap; font-size:13px; }
.prt-method input { margin:0; }
.prt-method.unavailable { color:var(--muted); opacity:.6; }
/* People / Positions: the Print Studio group is one more permission entry. Its
   heading IS a .perm (checkbox column, .pl title, .pd description); the subgrid
   hands that entry's checkbox column to the group, so each document row starts
   exactly where every entry's title starts. Rows reuse .pl and .pk. */
.print-permission-group { grid-column:1 / -1; display:grid; grid-template-columns:auto 1fr; column-gap:8px; }
.print-permission-group > .perm { grid-column:1 / -1; grid-template-columns:subgrid; }
.print-permission-docs { grid-column:2; min-width:0; padding:0 7px 5px 0; }
.print-permission-row { display:grid; grid-template-columns:minmax(0, 200px) 1fr; gap:1px 14px; align-items:center; padding:4px 0; }
.print-permission-row .prt-methods { gap:4px 18px; }
/* An entry's id inherits the label weight (600); a row is not a label, so say it. */
.print-permission-row .pk { grid-column:2; font-weight:600; overflow-wrap:anywhere; }
@media(max-width:700px) { .prt-delivery-heading { align-items:flex-start; } .prt-delivery-table { min-width:710px; }
  .print-permission-row { grid-template-columns:1fr; } .print-permission-row .pk { grid-column:1; } }
.prt-output-printer-choices { display:grid; gap:8px; margin:12px 0; max-height:260px; overflow:auto; }
.prt-output-default { display:grid; gap:5px; min-width:220px; padding:8px 0; }
.prt-output-default + .prt-output-default { border-top:1px solid var(--border, #303744); }
.prt-output-default select { width:100%; }
.prt-output-default .field-error:empty { display:none; }

/* ---- The asset register (0311): System → Assets ----
   Everything here is prefixed `as-` so it cannot reach another screen. The
   showroom plan reuses the floor plan's `.pf-st` / `.pf-head` boxes and adds
   only the spot cell, which draws a machine rather than a task. */
.as-views { display: flex; flex-wrap: wrap; gap: 4px; }
.as-view.on { background: var(--accent-wash); border-color: var(--accent); color: var(--accent-hover); }
.as-view .count { margin-left: 6px; font-family: var(--mono); font-size: 11px; color: var(--muted); }
.as-view.on .count { color: var(--accent-hover); }
.as-facts { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 18px; font-size: 13.5px; }
.as-facts > div { display: grid; gap: 1px; }
.as-facts > div.wide { grid-column: 1 / -1; }
.as-facts .k { font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); font-family: var(--mono); }
.as-pick { border: 1px solid var(--border); border-radius: var(--r); background: var(--panel); max-height: 220px; overflow: auto; margin-top: 4px; }
.as-pick-row { display: grid; grid-template-columns: auto 1fr auto; gap: 10px; width: 100%; text-align: left; font: inherit; font-size: 13px;
  padding: 7px 10px; border: none; border-bottom: 1px solid var(--border); background: none; color: var(--text); cursor: pointer; }
.as-pick-row:last-child { border-bottom: none; }
.as-pick-row:hover { background: var(--panel2); }
.as-pick-row b { font-family: var(--mono); }
.as-pick-row.away { opacity: .6; }
.as-plan { min-width: 600px; }
.as-slots { display: grid; grid-template-columns: repeat(var(--cols, 3), 1fr); gap: 4px; }
.as-slot { border: 1px solid var(--border); border-radius: 5px; background: var(--panel); padding: 4px 6px; min-height: 34px; cursor: pointer; line-height: 1.25; overflow: hidden; }
.as-slot:hover { border-color: var(--accent); }
.as-slot b { display: block; font-family: var(--mono); font-size: 11.5px; }
.as-slot span { display: block; font-size: 10.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.as-slot.empty { border-style: dashed; color: var(--faint); font-size: 11px; font-style: italic; display: flex; align-items: center; }
.as-slot.was { background: var(--warn-bg); border-color: var(--warn-bd); }
.as-slot.was b { color: var(--warn); }
.as-slot.old b::after, .as-dot { content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--warn); margin-left: 5px; vertical-align: middle; }
.as-lines { width: 100%; font-size: 13px; }
.as-lines th, .as-lines td { padding: 6px 8px; border-bottom: 1px solid var(--border); text-align: left; }
.as-lines select { width: 100%; }
.tabs.as-mini { margin-bottom: 12px; }
.tabs.as-mini .tab { font-size: 13px; padding: 6px 10px; }

/* Returns settings (0321): each list beside the picker it draws, and the
   switches that run the bay. */
.rst-cols { align-items: flex-start; }
.cols > .card.rst-list { flex: 1 1 560px; max-width: 900px; }
.cols > .card.rst-side { flex: 0 1 360px; max-width: 400px; }
.cols > .card.rst-form { flex: 1 1 360px; max-width: 520px; }
.rst-list > .card-head, .card.np > .rst-card-head { padding-bottom: 12px; }
.card:not(.np) > .rst-card-head { padding: 0 0 10px; }
.rst-table td { vertical-align: top; }
.rst-grip { width: 22px; padding-right: 0 !important; }
.rst-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.rst-row.dragging { opacity: 0.45; }
.rst-row.retired td { color: var(--muted); }
.rst-sep td { font-size: 12px; font-weight: 600; color: var(--muted); background: var(--panel2); padding-top: 7px; padding-bottom: 7px; }
.rst-side .choice-list { margin-top: 10px; }
.rst-says { margin-top: 12px; font-size: 13px; line-height: 1.5; color: var(--text); }
.rst-says > div + div { margin-top: 3px; }
.rst-gap { margin-top: 16px; }
.rst-fake { pointer-events: none; }
.rst-num { max-width: 16ch; }
.rst-lock { color: var(--warn); }
.modal.rst-modal { max-width: 620px; max-height: calc(100vh - 40px); overflow-y: auto; }
.rst-reads { margin: 4px 0 14px; }
.rst-reads .choice { margin-top: 4px; }
.choice[disabled] { opacity: 0.5; cursor: not-allowed; }

/* ---- Tear-downs (0322): the route a part takes, in the route's own colour ---- */
.td-routes .seg-btn.on.td-new { background: var(--ok); color: #fff; }
.td-routes .seg-btn.on.td-clearance { background: var(--warn); color: #fff; }
.td-routes .seg-btn.on.td-rma { background: var(--info); color: #fff; }
.td-routes .seg-btn.on.td-scrap { background: var(--bad); color: #fff; }
.td-stepper .step.now { color: var(--accent); }

/* ---- Tear-downs: one row per part (owner, 2026-09-27: the routing table grew
   wider than the screen and its scroll bar hid every option). What the part is on
   the left, its choices on the right, the chosen route's fields underneath. It
   wraps; it never scrolls sideways. ---- */
.td-parts { max-width: 980px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r); box-shadow: var(--shadow-sm); margin-bottom: 16px; }
.td-parts-head { display: flex; justify-content: space-between; align-items: center; gap: 10px 16px; flex-wrap: wrap; padding: 12px 16px; border-bottom: 1px solid var(--border); }
.td-part { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 10px 16px; align-items: center; padding: 12px 16px 12px 13px; border-bottom: 1px solid var(--border); border-left: 3px solid transparent; }
.td-part:last-child { border-bottom: 0; }
.td-part .nm { font-weight: 600; overflow-wrap: anywhere; }
.td-part .nm .muted { font-weight: 400; }
.td-part .sub { font-size: 12.5px; color: var(--muted); margin-top: 3px; overflow-wrap: anywhere; }
.td-part-act { justify-self: end; }
.td-part-detail { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: flex-end; }
.td-part-detail label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; font-weight: 600; color: var(--muted); }
.td-part-detail label.grow { flex: 1 1 320px; }
.td-part-detail label.grow input { width: 100%; }
.td-part-detail select { min-width: 220px; }
.td-part-detail .hint { flex-basis: 100%; margin: 0; }
.td-routes .seg-btn, .td-ticks .seg-btn { padding: 7px 14px; font-weight: 600; }
.td-ticks .seg-btn.on.td-removed { background: var(--ok); color: #fff; }
.td-ticks .seg-btn.on.td-missing { background: var(--bad); color: #fff; }
.td-part.is-removed, .td-part.route-new { border-left-color: var(--ok); }
.td-part.is-missing, .td-part.route-scrap { border-left-color: var(--bad); }
.td-part.route-clearance { border-left-color: var(--warn); }
.td-part.route-rma { border-left-color: var(--info); }
@media (max-width: 700px) {
  .td-part { grid-template-columns: 1fr; }
  .td-part-act { justify-self: start; }
}

/* ---- The Explorer, the day book, the board, the scorecard (INS-4, 0326) ----
   Inside `.ins`, so the kind-of-money hues are the Insights palette. Cards are
   the width of what they hold (R-UI-1): the question is a form and is capped;
   the answer's table is as wide as its columns; the day's cards sit two or
   three to a row. */
.xp-q { max-width: 1080px; padding: 14px 18px 12px; }
.xp-line { display: grid; grid-template-columns: 116px 1fr; gap: 12px; align-items: start; padding: 7px 0; }
.xp-line + .xp-line { border-top: 1px solid var(--border); }
.xp-lbl { font-size: 13px; font-weight: 600; color: var(--muted); padding-top: 6px; }
.xp-ctl { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; min-width: 0; }
.xp-ctl > .hint { flex-basis: 100%; margin: 0; }
.xp-ctl select, .xp-ctl input[type="date"] { width: auto; max-width: 100%; }
.xp-ctl .ins-window { margin: 0; }
.xp-chips { gap: 6px; }
.xp-chip { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--border-strong); border-radius: var(--r-pill); padding: 3px 11px 3px 8px; font-size: 13px; cursor: pointer; color: var(--muted); background: var(--panel); }
.xp-chip input { width: auto; margin: 0; }
.xp-chip.on { color: var(--text); border-color: var(--accent-wash-bd); background: var(--accent-wash); }
.xp-filter { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; flex-basis: 100%; }
.xp-flabel { font-size: 13px; font-weight: 600; }
.xp-fvals { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 5px; }
.xp-find { position: relative; display: inline-block; }
.xp-find input { width: 190px; }
.xp-pop { position: absolute; z-index: 30; top: calc(100% + 4px); left: 0; min-width: 260px; max-height: 300px; overflow: auto; background: var(--panel); border: 1px solid var(--border-strong); border-radius: var(--r); box-shadow: var(--shadow-md); padding: 4px; }
.xp-pop button { display: flex; justify-content: space-between; gap: 12px; width: 100%; text-align: left; font: inherit; font-size: 13.5px; background: none; border: 0; border-radius: var(--r-sm); padding: 6px 8px; cursor: pointer; color: var(--text); }
.xp-pop button:hover { background: var(--panel2); }
.xp-popnone { padding: 8px; font-size: 13px; }
.xp-chart { position: relative; max-width: 1080px; margin-bottom: 14px; }
.xp-chart .ins-chart { margin-bottom: 0; }
.xp-chart-ctl { position: absolute; top: 10px; right: 14px; z-index: 2; }
.xp-hit { cursor: pointer; }
.xp-hit:not([data-href]) { cursor: default; }
.xp-cap { fill: var(--muted); font-size: 10.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.xp-base { stroke: var(--border-strong); stroke-width: 1; }
.xp-more { margin-top: 8px; text-align: left; max-width: none; }
.xp-table { width: fit-content; min-width: min(100%, 560px); max-width: 100%; overflow-x: auto; }
.xp-table table { width: auto; min-width: 100%; }
.xp-table tfoot td { border-top: 1px solid var(--border-strong); }
.xp-narrow { max-width: 860px; }
.xp-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 14px; align-items: start; margin-bottom: 8px; }
.xp-cards > div > .card, .xp-cards > .card { margin-bottom: 0; }
.xp-area { font-size: 15px; font-weight: 700; margin: 18px 0 10px; }
.xp-mini { padding: 0 0 6px; }
.xp-mini .card-head { padding: 12px 14px 6px; }
.xp-mini .card-head h2 { font-size: 14.5px; }
.xp-mini-body { padding: 4px 14px 10px; }
.xp-mini.xp-quiet .card-head h2 { color: var(--muted); font-weight: 600; }
.xp-mini-t { width: 100%; }
.xp-mini-t td, .xp-mini-t th { padding: 5px 14px; }
.xp-mini-figs { display: flex; flex-wrap: wrap; gap: 10px 28px; padding: 4px 14px 10px; }
.xp-mini-num { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; }
.xp-score { width: fit-content; max-width: 100%; overflow-x: auto; }
.xp-score .badge { font-variant-numeric: tabular-nums; }
.xp-radio { display: flex; align-items: center; gap: 8px; font-size: 14px; margin-top: 10px; }
.xp-radio input[type="radio"] { width: auto; margin: 0; }
.xp-num { width: 110px; }
@media (max-width: 720px) {
  .xp-line { grid-template-columns: 1fr; gap: 4px; }
  .xp-lbl { padding-top: 0; }
  .xp-cards { grid-template-columns: 1fr; }
}
.ins-sub { font-size: 14px; font-weight: 700; margin: 16px 0 8px; }
