/* KC Interfoods ERP — Design System v1 (Phase 1, 2026-07-10)
   Personality: warm & human, calm, smooth (benchmark: FlowAccount). Light theme now;
   tokens are structured so a future dark theme only needs a second :root override block.
   Home: app shell (base.html), owner dashboard (cockpit.html), one sales list page (proof).
   Everything else keeps its current per-page <style> until Phase 2 rollout (Owner look-approval gate). */

/* ---------------------------------------------------------------- fonts (self-hosted, no CDN)
   All 3 Owner-shortlisted candidates are declared below (each self-hosted, thai+latin subsets).
   @font-face is lazy: a browser only fetches a family it's actually asked to render, so having
   all 3 declared costs nothing until swapped. THE ACTIVE PICK is the single --font-family-thai
   line inside :root right below — change that ONE line + redeploy to swap the whole app. */
@font-face{ font-family:"Sarabun"; font-style:normal; font-weight:400; font-display:swap; unicode-range:U+0E01-0E5B,U+200C-200D,U+25CC; src:url("/static/fonts/sarabun-thai-400.woff2") format("woff2"); }
@font-face{ font-family:"Sarabun"; font-style:normal; font-weight:500; font-display:swap; unicode-range:U+0E01-0E5B,U+200C-200D,U+25CC; src:url("/static/fonts/sarabun-thai-500.woff2") format("woff2"); }
@font-face{ font-family:"Sarabun"; font-style:normal; font-weight:600; font-display:swap; unicode-range:U+0E01-0E5B,U+200C-200D,U+25CC; src:url("/static/fonts/sarabun-thai-600.woff2") format("woff2"); }
@font-face{ font-family:"Sarabun"; font-style:normal; font-weight:700; font-display:swap; unicode-range:U+0E01-0E5B,U+200C-200D,U+25CC; src:url("/static/fonts/sarabun-thai-700.woff2") format("woff2"); }
@font-face{ font-family:"Sarabun"; font-style:normal; font-weight:400; font-display:swap; unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; src:url("/static/fonts/sarabun-latin-400.woff2") format("woff2"); }
@font-face{ font-family:"Sarabun"; font-style:normal; font-weight:500; font-display:swap; unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; src:url("/static/fonts/sarabun-latin-500.woff2") format("woff2"); }
@font-face{ font-family:"Sarabun"; font-style:normal; font-weight:600; font-display:swap; unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; src:url("/static/fonts/sarabun-latin-600.woff2") format("woff2"); }
@font-face{ font-family:"Sarabun"; font-style:normal; font-weight:700; font-display:swap; unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; src:url("/static/fonts/sarabun-latin-700.woff2") format("woff2"); }

@font-face{ font-family:"Anuphan"; font-style:normal; font-weight:400; font-display:swap; unicode-range:U+0E01-0E5B,U+200C-200D,U+25CC; src:url("/static/fonts/anuphan-thai-400.woff2") format("woff2"); }
@font-face{ font-family:"Anuphan"; font-style:normal; font-weight:500; font-display:swap; unicode-range:U+0E01-0E5B,U+200C-200D,U+25CC; src:url("/static/fonts/anuphan-thai-500.woff2") format("woff2"); }
@font-face{ font-family:"Anuphan"; font-style:normal; font-weight:600; font-display:swap; unicode-range:U+0E01-0E5B,U+200C-200D,U+25CC; src:url("/static/fonts/anuphan-thai-600.woff2") format("woff2"); }
@font-face{ font-family:"Anuphan"; font-style:normal; font-weight:700; font-display:swap; unicode-range:U+0E01-0E5B,U+200C-200D,U+25CC; src:url("/static/fonts/anuphan-thai-700.woff2") format("woff2"); }
@font-face{ font-family:"Anuphan"; font-style:normal; font-weight:400; font-display:swap; unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; src:url("/static/fonts/anuphan-latin-400.woff2") format("woff2"); }
@font-face{ font-family:"Anuphan"; font-style:normal; font-weight:500; font-display:swap; unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; src:url("/static/fonts/anuphan-latin-500.woff2") format("woff2"); }
@font-face{ font-family:"Anuphan"; font-style:normal; font-weight:600; font-display:swap; unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; src:url("/static/fonts/anuphan-latin-600.woff2") format("woff2"); }
@font-face{ font-family:"Anuphan"; font-style:normal; font-weight:700; font-display:swap; unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; src:url("/static/fonts/anuphan-latin-700.woff2") format("woff2"); }

@font-face{ font-family:"Prompt"; font-style:normal; font-weight:400; font-display:swap; unicode-range:U+0E01-0E5B,U+200C-200D,U+25CC; src:url("/static/fonts/prompt-thai-400.woff2") format("woff2"); }
@font-face{ font-family:"Prompt"; font-style:normal; font-weight:500; font-display:swap; unicode-range:U+0E01-0E5B,U+200C-200D,U+25CC; src:url("/static/fonts/prompt-thai-500.woff2") format("woff2"); }
@font-face{ font-family:"Prompt"; font-style:normal; font-weight:600; font-display:swap; unicode-range:U+0E01-0E5B,U+200C-200D,U+25CC; src:url("/static/fonts/prompt-thai-600.woff2") format("woff2"); }
@font-face{ font-family:"Prompt"; font-style:normal; font-weight:700; font-display:swap; unicode-range:U+0E01-0E5B,U+200C-200D,U+25CC; src:url("/static/fonts/prompt-thai-700.woff2") format("woff2"); }
@font-face{ font-family:"Prompt"; font-style:normal; font-weight:400; font-display:swap; unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; src:url("/static/fonts/prompt-latin-400.woff2") format("woff2"); }
@font-face{ font-family:"Prompt"; font-style:normal; font-weight:500; font-display:swap; unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; src:url("/static/fonts/prompt-latin-500.woff2") format("woff2"); }
@font-face{ font-family:"Prompt"; font-style:normal; font-weight:600; font-display:swap; unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; src:url("/static/fonts/prompt-latin-600.woff2") format("woff2"); }
@font-face{ font-family:"Prompt"; font-style:normal; font-weight:700; font-display:swap; unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; src:url("/static/fonts/prompt-latin-700.woff2") format("woff2"); }

/* IBM Plex Sans Thai kept self-hosted too (the original Phase-1 pick, Owner said "bad choice" —
   retained only so re-comparing costs nothing; not referenced by --font-family-thai below). */
@font-face{ font-family:"IBM Plex Sans Thai"; font-style:normal; font-weight:400; font-display:swap; src:url("/static/fonts/IBMPlexSansThai-Regular.woff2") format("woff2"); }
@font-face{ font-family:"IBM Plex Sans Thai"; font-style:normal; font-weight:500; font-display:swap; src:url("/static/fonts/IBMPlexSansThai-Medium.woff2") format("woff2"); }
@font-face{ font-family:"IBM Plex Sans Thai"; font-style:normal; font-weight:600; font-display:swap; src:url("/static/fonts/IBMPlexSansThai-SemiBold.woff2") format("woff2"); }
@font-face{ font-family:"IBM Plex Sans Thai"; font-style:normal; font-weight:700; font-display:swap; src:url("/static/fonts/IBMPlexSansThai-Bold.woff2") format("woff2"); }

/* ---------------------------------------------------------------- tokens */
:root{
  /* -- color: warm neutrals -- */
  --ink:#1b1e24; --muted:#586068; --line:#e9ebef; --line2:#f1f2f5;
  --bg:#f7f6f4; --surface:#ffffff;

  /* -- color: accent (amber, used sparingly — primary buttons / active nav / highlights) -- */
  --accent:#e07b16; --accent-soft:#fdf0e2; --accent-ink:#8a4c0e;
  --teal:#1899b5; --dark:#23262c; --dark2:#34373e;
  --navy2:#23262c; /* legacy names kept so other (not-yet-migrated) templates inherit */

  /* -- color: brand-facing additions for marketing/auth surfaces (D-228 / OP-35 login restyle,
     2026-07-16). --accent-amber is a brand-facing ALIAS of --accent (same #e07b16 amber hue) so
     a marketing/auth page (login, any future landing page) reads as ONE consistent brand accent
     with the rest of the app -- never a second, unreviewed amber. --accent-amber-ring is a
     token-driven low-alpha ring (color-mix, so no hardcoded rgba literal is needed for a focus
     ring). --brand-navy-* are the dramatic dark gradient stops used behind the login card / hero
     panel (previously inline hex inside login.html's own <style> block) -- tokenized so a future
     hue swap is a one-line edit here instead of a hunt through page-level CSS. --on-navy is the
     text/logo tint for content placed on that dark navy surface. -- */
  --accent-amber:var(--accent);
  --accent-amber-ring:color-mix(in srgb, var(--accent-amber) 16%, transparent);
  --brand-navy-1:#25406a; --brand-navy-2:#16263d; --brand-navy-3:#0f1c2e;
  --on-navy:#ffffff;

  /* -- color: semantic status (ok/warn/bad/info) — NEVER the accent hue, one canonical value each -- */
  --ok:#15803d; --ok-soft:#e7f5ec; --ok-ink:#0f6e56;
  --warn:#92720c; --warn-soft:#f7f0da; --warn-ink:#7a5a0c;
  --bad:#c0392b; --bad-soft:#fbe9e8; --bad-ink:#96271c;
  --info:#1899b5; --info-soft:#e6f4f7; --info-ink:#0f6b7d;
  --neutral:#878d95; --neutral-soft:#f1f2f5; --neutral-ink:#586068;
  --faint:#767c84; /* darkened from #878d95 (2026-07-10 review) — original failed ~4:1 text contrast on white/surface */
  --green:#15803d; /* legacy alias -> --ok, kept so other templates inherit */

  --on-color:var(--surface); /* not on raw --accent alone -- 2.99:1 fails AA */

  /* -- type: ONE swap point for the whole app (Owner picks Sarabun / Anuphan / Prompt) --
     change ONLY this line to re-skin every page — every @font-face candidate above is already
     self-hosted + declared, so a swap needs no new download, no CDN, no other file touched. */
  --font-family-thai:"Anuphan";
  --font-ui:var(--font-family-thai),"Segoe UI",Tahoma,sans-serif;
  --fs-xs:11px; --fs-sm:12.5px; --fs-table:13.5px; --fs-base:14.5px; --fs-md:15px;
  --fs-lg:17px; --fs-xl:20px; --fs-2xl:24px;
  --lh-tight:1.25; --lh-normal:1.5; --lh-loose:1.7;

  /* -- spacing scale (4px unit) -- */
  --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px; --sp-6:24px; --sp-8:32px;

  /* -- shape -- */
  --radius-sm:8px; --radius-md:10px; --radius-lg:12px; --radius-pill:999px;

  /* -- elevation (gentle — not flat, not heavy) -- */
  --shadow-1:0 1px 2px rgba(27,30,36,.05), 0 1px 1px rgba(27,30,36,.03);
  --shadow-2:0 2px 8px rgba(27,30,36,.07), 0 1px 2px rgba(27,30,36,.04);
  --shadow-3:0 10px 24px rgba(27,30,36,.09), 0 2px 6px rgba(27,30,36,.05);
}

*{ box-sizing:border-box; }
html,body{ margin:0; height:100%; }
body{ font-family:var(--font-ui);
      font-size:var(--fs-md); line-height:var(--lh-loose); color:var(--ink); background:var(--bg);
      display:grid; grid-template-columns:266px 1fr; -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility; }
table, .n, td.n, th.n{ font-variant-numeric:tabular-nums; }

/* ---------------------------------------------------------------- app shell: sidebar */
.side{ background:var(--surface); border-right:1px solid var(--line); height:100vh; position:sticky; top:0; overflow:auto; padding-bottom:28px; }
.side .logo{ padding:18px 20px 16px; border-bottom:1px solid var(--line); }
.side .logo .brand{ font-size:var(--fs-lg); font-weight:600; letter-spacing:.2px; color:var(--ink); }
.side .logo .sub{ display:block; color:var(--faint); font-size:var(--fs-xs); margin-top:2px; letter-spacing:.3px; }
.grp{ padding:20px 20px 7px; color:#353b42; font-size:var(--fs-base); font-weight:700; letter-spacing:.2px; }
.side a, .side form>button{ display:flex; align-items:center; gap:11px; padding:10px 20px; color:#2b3038; text-decoration:none; font-size:var(--fs-md); border-left:3px solid transparent; transition:background .15s ease; }
.side form{ margin:0; } .side form>button{ width:100%; box-sizing:border-box; background:none; border:0; border-left:3px solid transparent; cursor:pointer; text-align:left; font-family:inherit; }
.side a:hover, .side form>button:hover{ background:var(--line2); color:var(--ink); }
.side a.active, .side form>button.active{ background:var(--accent-soft); color:var(--accent-ink); border-left-color:var(--accent); font-weight:600; }
.side a.soon{ color:var(--faint); cursor:default; }
.side a.soon:hover{ background:none; }
.side a .ic{ width:21px; text-align:center; font-size:18.5px; opacity:.9; line-height:1; }
.side a.active .ic{ opacity:1; }
.side a .tag{ margin-left:auto; font-size:10.5px; background:var(--line2); color:var(--muted); padding:1px 7px; border-radius:var(--radius-pill); }
.side a.active .tag{ background:#f6e2cd; color:var(--accent-ink); }
/* nav badge tones — the ONE semantic set every "new/live/edit/pilot/sandbox/..." nav tag maps to
   (was 8+ one-off inline hex pairs across the sidebar; tamed to ok/warn/info/neutral, matching the
   same tokens as .status-pill so a tag and a KPI tone always mean the same thing app-wide). */
.side a .tag.tag-ok{ background:var(--ok-soft); color:var(--ok-ink); }
.side a .tag.tag-warn{ background:var(--warn-soft); color:var(--warn-ink); }
.side a .tag.tag-bad{ background:var(--bad-soft); color:var(--bad-ink); }
.side a .tag.tag-info{ background:var(--info-soft); color:var(--info-ink); }
.side a .tag.tag-neutral{ background:var(--neutral-soft); color:var(--neutral-ink); }

/* ---------------------------------------------------------------- app shell: main / topbar */
.main{ height:100vh; overflow:auto; }
.topbar{ background:rgba(255,255,255,.86); backdrop-filter:saturate(1.4) blur(6px); border-bottom:1px solid var(--line); padding:14px 26px; display:flex; align-items:center; gap:12px; position:sticky; top:0; z-index:5; }
.topbar h1{ font-size:var(--fs-lg); font-weight:600; margin:0; letter-spacing:.1px; }
.topbar .crumb{ color:var(--muted); font-size:var(--fs-sm); }
.content{ padding:24px 26px; max-width:1120px; }

/* ---------------------------------------------------------------- components */
.card{ background:var(--surface); border:1px solid var(--line); border-radius:var(--radius-lg); box-shadow:var(--shadow-1); padding:18px 20px; margin-bottom:16px; transition:box-shadow .15s ease; }

/* KPI tile — a .card specialization for a single headline metric (see partials/_ui.html kpi_tile()) */
.kpi-tile{ padding:16px 18px; position:relative; }
.kpi-tile .lbl{ color:var(--muted); font-size:var(--fs-sm); display:flex; align-items:center; gap:6px; }
.kpi-tile .val{ font-size:var(--fs-2xl); font-weight:700; margin-top:4px; line-height:var(--lh-tight); }
.kpi-tile .val.ok{ color:var(--ok); } .kpi-tile .val.warn{ color:var(--warn); }
.kpi-tile .val.bad{ color:var(--bad); } .kpi-tile .val.neutral{ color:var(--ink); }
.kpi-tile .val.gap{ color:var(--faint); font-size:var(--fs-lg); font-weight:600; }
.kpi-tile .sub{ color:var(--faint); font-size:var(--fs-xs); margin-top:5px; line-height:1.45; }
.kpi-tile .src{ color:var(--faint); font-size:10px; margin-top:6px; font-variant-numeric:tabular-nums; }

/* section header — a titled divider above a group of cards (see partials/_ui.html section_header()) */
.sect{ font-weight:600; background:#f3f4f0; color:var(--ink); padding:8px 13px; border-left:3px solid var(--accent); border-radius:var(--radius-sm); margin:20px 0 9px; }

/* status pill — the ONE badge component for ok/warn/bad/info/neutral (see partials/_ui.html status_pill()) */
.status-pill{ display:inline-flex; align-items:center; gap:6px; padding:3px 11px; border-radius:var(--radius-pill); font-size:var(--fs-xs); font-weight:600; white-space:nowrap; }
.status-pill .dot{ width:7px; height:7px; border-radius:50%; flex:0 0 auto; }
.status-pill.ok{ background:var(--ok-soft); color:var(--ok-ink); } .status-pill.ok .dot{ background:var(--ok); }
.status-pill.warn{ background:var(--warn-soft); color:var(--warn-ink); } .status-pill.warn .dot{ background:var(--warn); }
.status-pill.bad{ background:var(--bad-soft); color:var(--bad-ink); } .status-pill.bad .dot{ background:var(--bad); }
.status-pill.info{ background:var(--info-soft); color:var(--info-ink); } .status-pill.info .dot{ background:var(--info); }
.status-pill.neutral{ background:var(--neutral-soft); color:var(--neutral-ink); } .status-pill.neutral .dot{ background:var(--faint); }

/* "live" data feel — a restrained pulse for a dot marking freshly-updated / live data. Used sparingly. */
@keyframes kc-live-pulse{
  0%{ box-shadow:0 0 0 0 rgba(21,128,61,.35); }
  70%{ box-shadow:0 0 0 6px rgba(21,128,61,0); }
  100%{ box-shadow:0 0 0 0 rgba(21,128,61,0); }
}
.live-pulse{ animation:kc-live-pulse 2.4s ease-out infinite; }
@media (prefers-reduced-motion: reduce){ .live-pulse{ animation:none; } .state-block.loading .ic{ animation:none; } }

/* forms */
input[type=text],input[type=number],select{ width:100%; padding:10px 13px; font-size:var(--fs-base); color:var(--ink); border:1px solid var(--line); border-radius:var(--radius-sm); font-family:inherit; background:#fff; outline:none; transition:border .15s ease, box-shadow .15s ease; }
input[type=text]:focus,input[type=number]:focus,select:focus{ border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft); }
.stock-movement-form :is(input,select,button){ min-height:44px; }
.stock-movement-form .row-issue-table{ overflow-x:auto; }

/* keyboard-focus visibility (a11y) — generic ring for any focusable element (nav links, collapsible
   .grp headers, buttons) that doesn't already have a dedicated focus style above. */
:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }

/* tables */
table{ border-collapse:collapse; width:100%; font-size:var(--fs-table); }
th,td{ padding:9px 12px; text-align:left; vertical-align:middle; border-bottom:1px solid var(--line2); }
th{ background:#fafafb; font-weight:600; color:#525862; border-bottom:1px solid var(--line); white-space:nowrap; }
tbody tr:hover{ background:#fafbfc; }
td.n,th.n{ text-align:right; }

/* buttons */
.btn{ display:inline-flex; align-items:center; gap:6px; background:var(--dark); color:#fff; padding:9px 16px; border-radius:var(--radius-sm); text-decoration:none; font-size:var(--fs-table); font-weight:500; border:none; cursor:pointer; font-family:inherit; transition:background .15s ease, transform .05s ease, box-shadow .15s ease; }
.btn:hover{ background:var(--dark2); box-shadow:var(--shadow-1); } .btn:active{ transform:scale(.98); }
.btn.alt{ background:var(--ok); } .btn.alt:hover{ background:#0f6e56; }
.btn.ghost{ background:#fff; color:var(--ink); border:1px solid var(--line); } .btn.ghost:hover{ background:var(--line2); }
.muted{ color:var(--muted); }
.pc-authority .muted{margin-top:4px}
.fin-note{font-size:var(--fs-base)}
.fin-note-sm{font-size:var(--fs-sm)}
.mrp-exq{border-left:4px solid var(--warn);background:var(--warn-soft)}
.chip{ display:inline-block; padding:6px 13px; margin:0 7px 7px 0; border:1px solid var(--line); border-radius:var(--radius-pill); font-size:13px; text-decoration:none; color:#41464e; background:#fff; transition:all .15s ease; }
.chip:hover{ border-color:#cfd3da; }
.chip.on{ background:var(--accent-soft); color:var(--accent-ink); border-color:var(--accent); font-weight:500; }

/* empty / loading state — a calm, consistent "nothing to show yet" block (see partials/_ui.html empty_state()) */
.state-block{ display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px; padding:36px 20px; color:var(--faint); text-align:center; }
.state-block .ic{ font-size:26px; opacity:.6; }
.state-block .msg{ font-size:var(--fs-base); }
.state-block.loading .ic{ animation:kc-spin 1s linear infinite; }
@keyframes kc-spin{ to{ transform:rotate(360deg); } }

/* ---------------------------------------------------------------- LAW-3 shared components (DA-3 WO-2)
   note() tag() data_table() filter_bar() form_field()/.form-grid lookup() ai_card() -- see
   partials/_ui.html for the macros, DA2-LAW-EXAMPLE.css for the pixel reference this ports from. */

/* note() -- LAW-3.1, the ONE callout/banner.
   T366 ROOT FIX -- A BANNER IS NEVER A FLEX CONTAINER. It used to be, and flex makes EVERY
   direct child a column: not just the tags, but every run of bare text between them becomes
   an anonymous flex item of its own. So the moment a banner's sentence carried a <b>, an <a>
   or a <code> as a DIRECT child, the sentence was cut into that many narrow columns and each
   column wrapped inside itself. Measured on origin/master 12648c27: 7 of the app's 33 banners,
   at 3 to 20 columns each -- worst are sales_so_form (20), quote/Margin Advisor (13) and
   sales_quo_form (9), which is the unreadable sliver stack the Owner met on /sales/quotation/1
   (2026-08-10). Normal block flow is the cure and it is
   the cure for EVERY shape this app writes, which is why the fix lives here and not in one
   template: (1) icon-glyph + inline sentence -> one wrapped paragraph; (2) leading .ti icon +
   a single <div> body -> the icon floats, the body sits beside it; (3) a <b> headline over a
   <div> body -> headline on its own line, body under it (flex used to put them side by side,
   which was the same defect wearing a different mask); (4) bare text -> unchanged.
   flow-root is what keeps the floated icon inside the padded box. Do not restore display:flex
   here -- tests/t366_operator_language_harness.py fails the build if anyone does. */
.note{ display:flow-root; padding:10px 14px; border:1px solid; border-radius:var(--radius-sm); font-size:var(--fs-base); margin-bottom:12px; }
.note > i:first-child{ float:left; margin-right:8px; }
/* a link inside a banner inherits the banner's own contrast-tested ink and is underlined, so it
   reads as a link instead of as greyed-out disabled text on a tinted background (T366). */
.note a{ color:inherit; text-decoration:underline; }
.note.info{ background:var(--info-soft); border-color:#cfe4ea; color:var(--info-ink); }
.note.warn{ background:var(--warn-soft); border-color:#e8dcb4; color:var(--warn-ink); }
.note.ok{ background:var(--ok-soft); border-color:#cde8d6; color:var(--ok-ink); }
.note.bad{ background:var(--bad-soft); border-color:#f0d2cf; color:var(--bad-ink); }

/* tag() -- LAW-3.2, general-purpose (the .side a .tag rules above stay -- same tone classes,
   nav-specific layout); this is the standalone version for topbars/cards/table cells. */
.tag{ display:inline-block; font-size:10.5px; font-weight:600; background:var(--neutral-soft); color:var(--neutral-ink); padding:2px 8px; border-radius:var(--radius-pill); white-space:nowrap; }
.tag.tag-ok{ background:var(--ok-soft); color:var(--ok-ink); }
.tag.tag-warn{ background:var(--warn-soft); color:var(--warn-ink); }
.tag.tag-bad{ background:var(--bad-soft); color:var(--bad-ink); }
.tag.tag-info{ background:var(--info-soft); color:var(--info-ink); }
.tag.tag-neutral{ background:var(--neutral-soft); color:var(--neutral-ink); }

/* filter_bar() -- LAW-3.4 (.chip is already defined above, reused as-is) */
.filter-bar{ display:flex; gap:8px; align-items:center; margin-bottom:12px; flex-wrap:wrap; }
.filter-bar input[type=text]{ width:280px; padding:9px 13px; font-size:var(--fs-base); border:1px solid var(--line); border-radius:var(--radius-sm); font-family:inherit; outline:none; background:#fff; }
.filter-bar .count{ margin-left:auto; color:var(--muted); font-size:var(--fs-sm); white-space:nowrap; }

/* data_table() -- LAW-3.3: scroll container + sticky thead + numeric alignment + honest counter */
.tablewrap{ max-height:56vh; overflow:auto; border:1px solid var(--line); border-radius:var(--radius-sm); }
.tablewrap table{ margin:0; }
.tablewrap thead th{ position:sticky; top:0; z-index:2; }
.tablefoot{ display:flex; align-items:center; gap:12px; padding:10px 4px 0; color:var(--muted); font-size:var(--fs-sm); }
.tablefoot .pager{ margin-left:auto; display:flex; gap:6px; }
.tablefoot .pager a,.tablefoot .pager button{ display:inline-block; min-width:24px; text-align:center; padding:4px 8px; border:1px solid var(--line); border-radius:var(--radius-sm); color:var(--ink); text-decoration:none; background:#fff; font-size:var(--fs-sm); font-family:inherit; cursor:pointer; }
.tablefoot .pager a.cur,.tablefoot .pager button.cur{ background:var(--accent-soft); color:var(--accent-ink); border-color:var(--accent); font-weight:600; }
.tablefoot .pager button:disabled{ opacity:.4; cursor:default; }

/* T380 -- WIDE CONTENT SCROLLS INSIDE ITS CARD, IT NEVER PAINTS OUTSIDE IT.
   A .card is overflow:visible, so anything wider than the card -- a table carrying one column per
   department, a long unbroken code -- is drawn straight through the card's right border and out
   over the work pane. The Owner watched the /users role matrix do exactly that: 1283px of table
   inside a 991px card, 292px of it outside the wrapper, at every window width (the work pane is
   capped at 1120px by .content, so a wider window does not cure it).
   Wrap the wide thing in .xscroll and the card edge holds. Add .xscroll-sticky1 when the first
   column names the row and has to stay readable while the rest scrolls sideways. That frozen
   column's divider is a box-shadow because border-collapse discards a sticky cell's own border,
   and only the body cell needs a background repainted -- th already carries one. */
.xscroll{ overflow-x:auto; max-width:100%; }
.xscroll > table{ margin:0; }
.xscroll-sticky1 th:first-child,
.xscroll-sticky1 td:first-child{ position:sticky; left:0; z-index:1; box-shadow:1px 0 0 var(--line); }
.xscroll-sticky1 tbody td:first-child{ background:var(--surface); }
.xscroll-sticky1 thead th:first-child{ z-index:2; }

/* .aq-row/.aq-cat -- action-queue list item (WO-3) */
.aq-row{ display:flex; align-items:flex-start; gap:11px; padding:11px 8px; border-bottom:1px solid var(--line2); text-decoration:none; color:var(--ink); }
.aq-cat{ flex:none; margin-top:1px; }

/* form_field()/.form-grid -- LAW-3.5 */
.form-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:14px 18px; }
.form-field label{ display:block; font-size:var(--fs-sm); color:var(--muted); margin-bottom:5px; font-weight:500; }
.form-field .help{ font-size:var(--fs-xs); color:var(--faint); margin-top:4px; }
.form-field.err input,.form-field.err select{ border-color:var(--bad); }
.form-field.err .help{ color:var(--bad-ink); font-weight:500; }

/* audit_trail() -- T185: the ONE "who changed what, and when" panel (partials/_ui.html).
   Same size/spacing as the finance documents' own trail line, which it replaces. */
.audit-trail{ font-size:var(--fs-xs); margin-top:10px; }
.audit-trail .audit-note{ color:var(--faint); }

/* import_bar()/import_summary()/import_flag() -- T185: the ONE spreadsheet import flow
   (upload -> preview -> confirm), partials/_ui.html. Ports hr_import.html's own inline styles. */
.import-bar{ display:flex; gap:14px; flex-wrap:wrap; align-items:flex-end; }
.import-confirm{ font-size:var(--fs-sm); }
.import-ok{ color:var(--ok); }
.import-bad{ color:var(--bad); }
.import-unmapped{ font-size:var(--fs-sm); color:var(--warn); }
.import-flagged{ background:var(--bad-soft); }

/* T185 shared status, issue-table and reversal blocks. */
.control-status{ display:flex; align-items:center; gap:12px; flex-wrap:wrap; padding:10px 12px; margin-bottom:12px; border:1px solid var(--line); border-radius:var(--radius-md); background:var(--surface); }
.control-status-main{ display:flex; align-items:baseline; gap:10px; }
.control-status-title{ color:var(--muted); font-size:var(--fs-sm); }
.control-status strong{ font-size:var(--fs-lg); }
.control-status-detail{ color:var(--muted); font-size:var(--fs-xs); }
.control-status-link{ margin-left:auto; font-size:var(--fs-sm); }
.control-status-ok{ border-color:var(--ok); background:var(--ok-soft); }
.control-status-warn{ border-color:var(--warn); background:var(--warn-soft); }
.row-issue-table{ overflow-x:auto; margin-top:10px; }
.row-issue-table table{ margin:0; }
.row-issue-empty{ padding:8px 0; }
.reversal-block{ margin-top:12px; padding:10px 12px; border:1px solid var(--line); border-radius:var(--radius-md); background:var(--surface); }
.reversal-block-title{ font-weight:600; margin-bottom:6px; }
.reversal-block-history{ font-size:var(--fs-sm); }
.reversal-block-form{ display:flex; align-items:end; gap:8px; flex-wrap:wrap; }
.reversal-block-form label{ display:block; width:100%; color:var(--muted); font-size:var(--fs-sm); }
.reversal-block-form input{ flex:1; min-width:220px; }

/* ai_card() -- LAW-3.7 */
.ai-card{ border:1px solid #e8d9c4; background:linear-gradient(180deg,#fffdf9,#fdf6ec); border-radius:var(--radius-lg); padding:16px 18px; margin-bottom:16px; box-shadow:var(--shadow-1); }
.ai-card .head{ font-weight:600; font-size:var(--fs-base); margin-bottom:8px; }
.ai-card .rec{ font-size:var(--fs-md); font-weight:600; margin-bottom:4px; }
.ai-card .impact{ color:var(--ok-ink); font-size:var(--fs-sm); margin-bottom:8px; }
.ai-card ul{ margin:0 0 12px; padding-left:20px; font-size:var(--fs-sm); color:var(--muted); }
.ai-card ul li{ margin-bottom:3px; }
.ai-card .foot{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.ai-card .audit{ color:var(--faint); font-size:10.5px; margin-left:auto; }

/* Shared RV/RD/AP/PV print classes; findoc-/rvd- avoid the unrelated doc-center namespace. */
.rvd-print-bar{ display:flex; justify-content:flex-end; margin-bottom:10px; }
.rvd-btn.print{ background:#42506b; color:#fff; text-decoration:none; display:inline-flex; align-items:center; gap:6px; }

.findoc-toolbar{ display:flex; gap:8px; align-items:center; }
.findoc-status{ margin-left:auto; }
.findoc-sheet{ max-width:680px; margin:0 auto; }
.findoc-sheet.wide{ max-width:720px; }
.findoc-letterhead{ text-align:center; border-bottom:2px solid var(--ink); padding-bottom:8px; margin-bottom:14px; }
.findoc-name{ font-size:18px; font-weight:700; }
.findoc-sub{ font-size:12px; }
.findoc-title{ font-size:16px; font-weight:700; margin-top:6px; color:var(--accent); }
.findoc-parties{ display:flex; gap:24px; justify-content:space-between; flex-wrap:wrap; font-size:13px; }
.findoc-party-right{ text-align:right; }
.findoc-words{ background:#fafbfc; margin-top:12px; text-align:center; font-weight:600; }
.findoc-subtable{ margin-top:8px; font-size:12px; }
.findoc-meta{ font-size:11px; margin-top:10px; }
.findoc-table{ margin-top:14px; }

/* Shared finance-document heading icon. */
.ti-heading{ vertical-align:-2px; margin-right:5px; color:var(--accent); }

/* Three-way-match variance form; replaces five inline style attributes. */
.variance-accept{background:var(--warn-soft);border-color:var(--warn);margin:10px 0 0;display:flex;gap:10px;flex-wrap:wrap;align-items:end}
.variance-accept>.va-field{flex:1;min-width:260px}
.variance-accept label{display:block}
.variance-accept input[type=text]{width:100%}
.variance-accept .btn{background:var(--warn);border-color:var(--warn)}

/* ---------------------------------------------------------- printed document skeleton (T185)
   THE ONE page setup every printed document extends — partials/_doc.html renders the markup,
   this block owns how it comes out on paper. Moved here from two byte-identical <style> blocks
   inside doc_coa.html and doc_spec.html: a per-page print stylesheet is conflict C9, and paper
   that is wrong should be wrong in one place, not in twenty. Print overrides live in the ONE
   @media print block below, never in a second one. */
.doc-bar{ display:flex; gap:8px; margin-bottom:14px; }
.doc-sheet{ padding:24px 28px; }
.doc-head{ display:flex; justify-content:space-between; align-items:flex-start; gap:16px;
           border-bottom:2px solid var(--ink); padding-bottom:12px; margin-bottom:14px; }
.doc-title{ font-size:17px; font-weight:600; }
.doc-sub{ color:var(--muted); font-size:12.5px; margin-top:2px; }
.doc-code{ text-align:right; }
.doc-code-v{ font-size:20px; font-weight:600; }
.doc-kv{ width:100%; border-collapse:collapse; margin-bottom:8px; }
.doc-kv td{ border:none; border-bottom:1px solid var(--line2); padding:7px 6px; vertical-align:top; }
.doc-kv td.k{ width:280px; color:var(--muted); font-weight:600; font-size:13px; }
.doc-foot{ display:flex; gap:30px; margin-top:34px; }
.doc-foot .sig{ flex:1; text-align:center; font-size:12.5px; color:var(--muted); }
.doc-foot .sig-line{ border-top:1px solid var(--line); margin:36px 8px 6px; }

/* ---------------------------------------------------------- statutory document sheets (T75)
   The Thai tax invoice / credit note / debit note. ONE .kcdoc-sheet per physical A4 page: the split
   is decided server-side (calc/doc_paging.py) so every page repeats the letterhead and the column
   titles and carries its own "หน้า X / Y". The width is the A4 printable width (210mm paper less the
   12mm margins declared by @page below), not an arbitrary pixel box that the printer then shrinks. */
.kcdoc-sheet{ width:186mm; max-width:100%; margin:0 auto 16px; position:relative; }
.kcdoc-head{ text-align:center; border-bottom:2px solid var(--ink); padding-bottom:8px; margin-bottom:14px;
             position:relative; z-index:6; }
.kcdoc-seller{ font-size:18px; font-weight:700; }
.kcdoc-line{ font-size:12px; }
.kcdoc-title{ font-size:16px; font-weight:700; margin-top:6px; color:var(--accent); }
.kcdoc-copy-label{ display:inline-block; margin-top:6px; padding:2px 10px; border:1px solid var(--ink);
                   border-radius:var(--radius-sm); font-size:12px; font-weight:700; letter-spacing:.2px; }
.kcdoc-parties{ display:flex; gap:24px; justify-content:space-between; flex-wrap:wrap; font-size:13px;
                position:relative; z-index:6; }
.kcdoc-party-right{ text-align:right; }
.kcdoc-table{ margin-top:14px; position:relative; z-index:6; }
.kcdoc-totals{ margin-top:12px; position:relative; z-index:6; }
.kcdoc-totals table{ width:100%; }
.kcdoc-totals td{ border:none; border-bottom:1px solid var(--line2); }
.kcdoc-amt{ width:170px; }
.kcdoc-words{ background:var(--line2); text-align:center; font-weight:600; padding:8px; margin-top:8px;
              border-radius:var(--radius-sm); }
.kcdoc-foot{ display:flex; justify-content:space-between; align-items:flex-end; gap:16px; font-size:11px;
             color:var(--muted); margin-top:12px; border-top:1px solid var(--line2); padding-top:6px;
             position:relative; z-index:6; }
.kcdoc-pageno{ white-space:nowrap; font-weight:600; color:var(--ink); }
.kcdoc-wm{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
           pointer-events:none; z-index:5; }
.kcdoc-wm span{ transform:rotate(-24deg); font-size:46px; font-weight:800; letter-spacing:2px;
                color:rgba(180,40,40,0.18); border:4px solid rgba(180,40,40,0.18); padding:10px 28px;
                border-radius:8px; text-align:center; line-height:1.2; }
.kcdoc-substitute{ position:relative; z-index:6; margin:0 auto 12px; padding:7px 12px;
                   border:2px solid var(--warn); border-radius:var(--radius-sm); text-align:center;
                   color:var(--warn-ink); background:var(--warn-soft); }
.kcdoc-substitute strong,.kcdoc-substitute span{ display:block; }
.kcdoc-substitute span{ margin-top:3px; font-size:var(--fs-table); }

@page{ size:A4 portrait; margin:12mm; }
@media print{
   body{ display:block; background:#fff; -webkit-print-color-adjust:exact; print-color-adjust:exact; }
   .side,.topbar,.noprint{ display:none !important; }
   /* LAW-6.13 SCREEN-ONLY REGIONS. Three of the eleven regions exist to help the person at the
      screen and have no business on paper: [C] the action row (buttons nobody can press on a
      printout), [D] the message region (advice about what to do next), [E] the assist strip.
      Before T369 each page hid its own with a per-page `noprint` wrapper, and the moment a
      component moved into its mandated region that wrapper was left behind — measured on
      quote.html, where the "create a quotation" button and its hint started printing onto the
      paper a salesperson hands a customer. Declaring it HERE makes it true of all 274 screens at
      once, which is the whole point of a shell: a builder cannot forget a rule they never have to
      remember. A page that genuinely must print one of these marks that element `.printable`. */
   .page-actions,.msg-region,.assist-strip{ display:none !important; }
   .page-actions.printable,.msg-region.printable,.assist-strip.printable{ display:block !important; }
   .content{ padding:0; max-width:none; } .card{ border:none; padding:0; box-shadow:none; }
   /* THE APP SHELL MUST NOT CLIP THE PAPER (T75 root cause). On screen .main is a fixed-height
      scroller (height:100vh; overflow:auto). Left alone when printing, the print engine clips every
      document to ONE viewport-high page and silently throws the rest away — so no page-break rule
      anywhere else in this file could ever have taken effect. */
   .main{ height:auto; overflow:visible; }
   .sect{ background:#f3f4f0 !important; color:var(--ink) !important; }
   th{ background:#fafafb !important; }
   .live-pulse{ animation:none; }
   /* printed document skeleton — partials/_doc.html */
   .doc-sheet{ padding:0; }
   .doc-kv td.k{ width:240px; }
   /* statutory document sheets (T75) — one sheet, one physical page, nothing split.
      break-after on every sheet but the last; the last would otherwise eject a blank final page. */
   .kcdoc-sheet{ width:auto; max-width:none; margin:0; padding:0; border:none; box-shadow:none;
                 break-after:page; page-break-after:always;
                 break-inside:avoid; page-break-inside:avoid; }
   .kcdoc-sheet.kcdoc-last{ break-after:auto; page-break-after:auto; }
   /* Paper typography is stated in POINTS, not screen pixels. The screen row is ~41px tall (13.5px
      text on a 1.7 line-height with 9px cell padding) = ~10.8mm, so a page of rows would run off an
      A4 sheet on its own. These sizes make the printed row ~6.3mm, which is what the row budget in
      calc/doc_paging.py is derived from — change one and you must re-measure the other. */
   .kcdoc-sheet{ font-size:10pt; line-height:1.35; }
   .kcdoc-seller{ font-size:13pt; }
   .kcdoc-line{ font-size:8.5pt; }
   .kcdoc-title{ font-size:12pt; }
   .kcdoc-copy-label{ margin-top:4pt; padding:2pt 8pt; border-width:1pt; font-size:10pt; }
   .kcdoc-substitute{ margin-bottom:8pt; padding:4pt 8pt; font-size:10pt; }
   .kcdoc-substitute span{ font-size:10pt; }
   .kcdoc-parties{ font-size:9.5pt; }
   .kcdoc-table{ font-size:9.5pt; }
   .kcdoc-table th, .kcdoc-table td{ padding:2.5pt 5pt; }
   .kcdoc-totals table{ font-size:10pt; }
   .kcdoc-totals td{ padding:3pt 5pt; }
   .kcdoc-words{ font-size:10pt; padding:5pt; }
   .kcdoc-foot{ font-size:8pt; }
   /* the column titles come back at the top of every page the table runs onto */
   .kcdoc-table thead{ display:table-header-group; }
   /* a line item is printed whole or moved to the next page — never cut in half */
   .kcdoc-table tr{ break-inside:avoid; page-break-inside:avoid; }
   /* a figure never prints on a different page from the label that names it */
   .kcdoc-totals, .kcdoc-totals tr, .kcdoc-words, .kcdoc-foot{ break-inside:avoid; page-break-inside:avoid; }
   .kcdoc-words{ background:#f3f4f0 !important; }
}

/* Shared MRP calculation note; replaces its former inline style. */
.calc-note{margin-top:6px;font-size:var(--fs-sm);color:var(--warn-ink)}

/* ---------------------------------------------------------- stock card (T222)
   stock_card_item.html and stock_card_lot.html — the permanent read-only movement history.
   T165 shipped both pages with every rule written inline, which is how a brand-new template
   arrives already over the design ratchet on the day it is born. The two pages are the same
   screen keyed on an item or on a lot, so they share ONE class set. Every value below is a
   byte-faithful port of the inline declaration it replaces: this is a move, not a restyle. */
.sc-immutable{ display:flex; gap:10px; align-items:center; background:var(--info-soft); border-color:var(--info); }
.sc-immutable-icon{ color:var(--info-ink); font-size:18px; }
.sc-immutable-note{ font-size:var(--fs-sm); color:var(--info-ink); }
.sc-find{ display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.sc-find-form{ display:flex; gap:8px; margin:0; flex:1; }
.sc-find-label{ align-self:center; font-size:var(--fs-table); }
.sc-find-key{ max-width:260px; }
.sc-find-alt{ max-width:220px; }
.sc-find-go{ font-size:var(--fs-table); }
.sc-prompt{ text-align:center; padding:30px; color:var(--muted); }
.sc-missing{ background:var(--warn-soft); border-color:var(--warn); color:var(--warn-ink); }
.sc-summary{ display:flex; flex-wrap:wrap; gap:18px; align-items:center; }
.sc-key{ font-size:var(--fs-xl); }
.sc-key-sub{ font-size:var(--fs-sm); color:var(--info-ink); }
.sc-label{ font-size:var(--fs-xs); }
.sc-figure{ font-size:22px; }
.sc-ok{ color:var(--ok-ink); }
.sc-warn{ color:var(--warn-ink); }
.sc-bad{ color:var(--bad-ink); }
.sc-info{ color:var(--info-ink); }
.sc-sect{ display:flex; align-items:center; gap:10px; }
.sc-source{ margin-left:auto; font-size:var(--fs-xs); }
.sc-gap{ background:var(--warn-soft); border-color:var(--warn); color:var(--warn-ink); font-size:12px; }
.sc-log{ padding:0; overflow:auto; }
.sc-nowrap{ white-space:nowrap; }
.sc-none{ padding:16px; }
.sc-trunc{ font-size:var(--fs-xs); margin-top:7px; }

/* ---------------------------------------------------------- allergen flag (T33, moved out by T222)
   The food-safety flag lot.html and lot_drill.html both raise: a banner for the chain, a badge
   for one row, and a stop-line inside the lot header. It was born inline in five near-identical
   copies across two templates on 2026-08-06 and pushed both files over their recorded ratchet
   budgets. A status badge is a component; it belongs here, where the next screen that must flag
   an allergen (the incident screen, the QA approval queue) can reuse it instead of copying it.
   Contrast of every pairing declared below, measured with tests/design_law_harness.py:
   --bad-ink on --bad-soft 6.88:1 · --warn-ink on --warn-soft 5.59:1 · --ok-ink on --ok-soft 5.52:1
   — all above the 4.5:1 AA bar for normal text. */
.allergen-badge{ font-size:var(--fs-xs); font-weight:700; color:var(--bad-ink); background:var(--bad-soft); border-radius:4px; padding:1px 6px; }
.allergen-badge.unknown{ font-weight:400; color:var(--warn-ink); background:var(--warn-soft); }
.allergen-banner{ background:var(--bad-soft); border-color:var(--bad); color:var(--bad-ink); }
.allergen-banner.unknown{ background:var(--warn-soft); border-color:var(--warn); color:var(--warn-ink); }
.allergen-banner.clear{ background:var(--ok-soft); border-color:var(--ok-soft); color:var(--ok-ink); font-size:var(--fs-sm); }
.allergen-banner-head{ font-size:15px; }
.allergen-banner-why{ font-size:var(--fs-sm); margin-top:3px; }
.allergen-banner-do{ font-size:12px; margin-top:3px; }
.allergen-line{ width:100%; font-size:13px; font-weight:700; color:var(--bad-ink); }
.allergen-line.unknown{ font-size:var(--fs-sm); font-weight:400; color:var(--warn-ink); }

/* ---------------------------------------------------------- lot traceability (T222)
   The genealogy node row and the headline metric tiles shared by lot.html and lot_drill.html.
   Both patterns were repeated inline two to seven times per file; moving them is what brings
   those two templates back under the budgets they were recorded against. */
.lot-metrics{ display:flex; flex-wrap:wrap; gap:22px; align-items:center; }
.lot-k{ font-size:var(--fs-xs); }
.lot-k-sm{ font-size:var(--fs-sm); }
.lot-v{ font-size:18px; }
.lot-v-lg{ font-size:var(--fs-xl); }
.lot-v-word{ font-size:14px; }
.lot-sub{ font-size:var(--fs-sm); color:var(--info-ink); }
.lot-ok{ color:var(--ok-ink); }
.lot-warn{ color:var(--warn-ink); }
.lot-bad{ color:var(--bad-ink); }
.lot-info{ color:var(--info-ink); }
.lot-level{ font-size:10.5px; margin:6px 0 3px; }
.lot-node{ display:flex; gap:8px; align-items:center; padding:4px 8px; margin-bottom:4px; border:1px solid var(--line); border-radius:6px; }
.lot-node.recalled{ background:var(--bad-soft); }
.lot-node-ic{ color:var(--muted); }
.lot-node-id{ font-weight:600; }
.lot-node-item{ font-size:var(--fs-sm); color:var(--info-ink); }
.lot-node-status{ margin-left:auto; font-size:var(--fs-xs); }

/* ---------------------------------------------------------- shared page furniture (T222)
   The handful of declarations that finance, procurement and logistics screens were each
   re-typing as an inline style attribute — a toolbar strip, a card that exists only to scroll a
   table, a tinted callout card, and four sizes/offsets with no token of their own. Named once so
   the next screen inherits them instead of copying a style="" attribute, which is what put six
   templates over the design ratchet in the first place.
   The .card-* tone trio mirrors .note.info/.warn/.bad but on a full .card, which is what these
   screens actually use. Each tone repeats its own background alongside its colour ON PURPOSE:
   GATE 3 only scores a text/background pairing declared inside ONE rule, so splitting them would
   quietly remove three real pairings from the contrast sweep. Measured with
   tests/design_law_harness.py: --info-ink on --info-soft 5.45:1 · --warn-ink on --warn-soft
   5.59:1 · --bad-ink on --bad-soft 6.88:1 — all above the 4.5:1 AA bar for normal text.
   12px and 13px sit between --fs-xs (11px) and --fs-base (14.5px) with no token of their own; a
   later token pass can fold them in, and doing it here would have been a restyle, not a move. */
.toolbar-row{ display:flex; gap:10px; align-items:center; }
.toolbar-row.wrap{ flex-wrap:wrap; }
.table-card{ padding:0; overflow:auto; }
.empty-cell{ padding:16px; }
.push-right{ margin-left:auto; }
.card-info{ background:var(--info-soft); border-color:var(--info); }
.card-info-ink{ background:var(--info-soft); border-color:var(--info); color:var(--info-ink); }
.card-warn{ background:var(--warn-soft); border-color:var(--warn); color:var(--warn-ink); }
.card-bad{ background:var(--bad-soft); border-color:var(--bad); color:var(--bad-ink); }
.fs-xs{ font-size:var(--fs-xs); }
.fs-sm{ font-size:var(--fs-sm); }
.fs-12{ font-size:12px; }
.fs-13{ font-size:13px; }
.mt-10{ margin-top:10px; }
.field-width-150{ width:150px; }
.field-width-245{ width:245px; }
.field-help-tight{ margin:8px 0 0; }

/* ---------------------------------------------------------- withholding certificate (T222)
   Moved verbatim out of a per-page <style> block inside cfo_wht_doc.html, which is conflict C9
   (a page-level stylesheet) and carried all eight of that template's raw hex literals. Paper that
   is wrong should be wrong in one place. Values are unchanged. */
.wht-copy{ border:1px solid var(--line2,#e8eaed); border-radius:12px; padding:24px; margin-top:14px; background:#fff; }
.wht-copy + .wht-copy{ page-break-before:always; }
.wht-copy h2{ text-align:center; margin:0 0 4px; }
.wht-copy .copy-label{ text-align:center; color:var(--muted); font-size:12px; margin-bottom:16px; }
.wht-grid{ display:grid; grid-template-columns:1fr 1fr; gap:8px 24px; margin:12px 0 18px; }
.wht-grid div{ border-bottom:1px solid var(--line2,#e8eaed); padding:5px 0; }
.wht-grid b{ display:inline-block; min-width:130px; }
.wht-table{ width:100%; border-collapse:collapse; }
.wht-table th,.wht-table td{ border:1px solid var(--line2,#d9dde3); padding:7px 8px; }
.wht-table th{ text-align:left; background:#f7f8fa; }
.wht-table .n{ text-align:right; }
.wht-note{ margin-top:14px; padding:9px 11px; background:#fff8ec; border:1px solid #f0d9a6; border-radius:8px; font-size:12px; }
.wht-signatures{ display:grid; grid-template-columns:1fr 1fr; gap:36px; margin-top:34px; text-align:center; }
.wht-signatures div{ border-top:1px solid #999; padding-top:6px; }
.wht-list-table{ padding:0; overflow:auto; margin-top:14px; }
@media print{
  .wht-copy{ border:0; margin:0; padding:0; }
  .wht-copy + .wht-copy{ page-break-before:always; }
}

/* ---------------------------------------------------------- recall trace (T222)
   logistics_recall_trace.html — the two lookup boxes and the headline figure row, moved out of
   inline style attributes with no change of value. */
.rt-lot{ margin-left:6px; min-width:200px; }
.rt-item{ margin-left:6px; min-width:160px; }
.rt-summary{ display:flex; gap:24px; flex-wrap:wrap; align-items:center; }

/* ---------------------------------------------------------- opening stock (T94, moved out by T222)
   opening_stock.html — the one-time cutover screen. It landed on 2026-08-06 with 24 inline style
   attributes, 9 raw hex literals and a 12px note, i.e. exactly the shape T222 had cleared off eight
   other templates hours earlier. The four status cards were hand-mixed hexes that each already had
   a semantic token (#b42318/#fff7f6 -> bad, #b7791f/#fffaf0 -> warn, #0f6e56/#f0faf5 -> ok,
   #f6fbff -> info), so they move onto the shared .card-* tone set rather than adding four more
   one-off colours; the green "Post opening stock" button becomes the .btn.alt that already existed.
   .card-ok completes that tone set — .card-info/.card-warn/.card-bad shipped with T222 and the ok
   tone was the one missing member, which is why this screen hand-rolled it.
   .card-stripe carries the 4px left rule the screen used to draw inline; it takes its colour from
   whichever tone class sits beside it, so a stripe can never disagree with its own card again.
   .card-ok repeats its background beside its colour ON PURPOSE, for the same reason the T222 trio
   does: GATE 3 only scores a text/background pairing declared inside ONE rule. Measured with
   tests/design_law_harness.py: --ok-ink on --ok-soft 5.52:1, above the 4.5:1 AA bar for normal text.
   The 12px form hint becomes .fs-sm (12.5px, the --fs-sm secondary token). That is a real 0.5px
   raise, not a rename: 12px is below the LAW-2.3a secondary floor, so moving it into a 12px class
   would have laundered the violation instead of fixing it. */
.card-ok{ background:var(--ok-soft); border-color:var(--ok); color:var(--ok-ink); }
.card-stripe{ border-left-width:4px; }
.os-hint{ margin-top:6px; }
.os-gap{ margin-top:8px; }
.os-stack{ margin-top:14px; }
.os-history{ margin-top:22px; }
.os-fields{ display:flex; gap:14px; flex-wrap:wrap; align-items:end; }
.os-ref{ min-width:260px; }
.os-list{ margin:8px 0 0 20px; }
.os-lines{ min-width:1120px; }
.os-row-drop{ padding:4px 8px; }
.os-toolbar{ padding:10px 12px; }
.os-caption{ padding:12px 14px; }

/* ---------------------------------------------------------- recipe version control (T147) */
.recipe-launch{ float:right; margin-left:var(--sp-3); }
.recipe-heading-icon{ vertical-align:-0.125rem; color:var(--accent); }
.recipe-toolbar{ display:flex; gap:var(--sp-3); align-items:end; flex-wrap:wrap; }
.recipe-toolbar form{ display:flex; gap:var(--sp-2); align-items:end; }
.recipe-toolbar label,.recipe-form label{ font-size:var(--fs-table); color:var(--muted); display:block; }
.recipe-toolbar input,.recipe-form input,.recipe-form select,.recipe-form textarea{ width:100%; margin-top:0.25rem; }
.recipe-governing{ background:var(--ok-soft); border-color:var(--ok); }
.recipe-form{ margin-top:0.875rem; }
.recipe-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(11.25rem,1fr)); gap:0.5625rem; margin-bottom:0.5625rem; }
.component-line{ display:grid; grid-template-columns:8.125rem 1fr 9.375rem 7.5rem auto; gap:0.375rem; margin:0.375rem 0; }
.component-line label{ min-width:0; }
.component-line .remove-component{ align-self:end; min-width:2.75rem; min-height:2.75rem; }
.recipe-toolbar .btn,.recipe-form .btn{ min-height:2.75rem; }
.recipe-detail-head{ display:flex; justify-content:space-between; gap:var(--sp-3); align-items:start; }
.recipe-detail-head h2{ margin:0.1875rem 0 0.625rem; }
.recipe-meta th{ width:8.125rem; }
.source-hash{ font-family:monospace; font-size:var(--fs-table); word-break:break-all; }
.recipe-method{ padding:var(--sp-3); margin-top:0.625rem; background:var(--info-soft); border-left:0.25rem solid var(--accent); }
.recipe-approve{ margin-top:var(--sp-3); }
.recipe-section-first{ margin-top:0; }
.recipe-current-id{ font-size:1.125rem; }
@media(max-width:50rem){
  .component-line{ grid-template-columns:1fr 1fr; }
  .recipe-meta th{ width:auto; }
}

/* T267 — product photo panel on the item screen. LAYOUT ONLY, deliberately:
   no colour, no background, no font-size here, so the design-law gates
   (hex-strict / font-floor / contrast) have nothing new to score. Captions
   reuse the existing .muted / .sc-label classes. */
.t267-photo-wrap{ display:flex; gap:1rem; align-items:flex-start; flex-wrap:wrap; }
.t267-photo-main{ max-width:20rem; max-height:20rem; width:auto; height:auto; object-fit:contain; }
.t267-photo-side{ display:flex; flex-direction:column; gap:0.5rem; min-width:14rem; flex:1; }
.t267-photo-strip{ display:flex; gap:0.5rem; flex-wrap:wrap; }
.t267-photo-strip a{ display:flex; flex-direction:column; align-items:center; gap:0.125rem; text-decoration:none; }
.t267-photo-thumb{ width:4.5rem; height:4.5rem; object-fit:cover; }
/* T275 - the product's picture on a stock-issue / warehouse-transfer PICK row. Deliberately
   LARGER than the search list's 3rem: the picker is standing at the rack matching a physical
   sack against the screen, not scanning a dense index, and a package that differs only by its
   label colour has to be visibly wrong at arm's length. 4rem still leaves the 192px derivative
   three times the pixels a floor phone can draw. Layout only - no font-size anywhere here, and
   the placeholder declares a background with NO colour beside it, so the contrast gate has no
   pairing to score. .t275-pick-thumb-ph is the photograph-less slot (Owner ruling D23,
   2026-08-08): the SAME square as a real photograph, so the rows stay aligned whether a code is
   photographed or not, and the img's own width/height reserve that square too, so nothing shifts
   while the lazy pictures arrive. It repeats the size instead of leaning on .t275-pick-thumb -
   the harness fails if the two ever drift apart - and it uses the SAME grey as the search list's
   own placeholder (.t274-thumb-ph), so one operator never meets two different "no photograph"
   boxes on two screens. */
.t275-pick-thumb-col{ width:4rem; }
.t275-pick-thumb{ width:4rem; height:4rem; object-fit:cover; display:block; }
.t275-pick-thumb-ph{ width:4rem; height:4rem; background:var(--line); display:block; }
/* T274 - the product picture beside the name in a search result list. A 3rem cell against the
   192px derivative is four times the pixels a phone screen can draw, and the fixed square keeps
   the rows aligned while the lazy images arrive (no layout shift).
   .t274-thumb-ph is the photograph-less slot (Owner ruling D23, 2026-08-08): a same-size neutral
   grey box, so the column and every row in it stay aligned and no row is ever text-only. The
   grey is the design system's own neutral --line (#e9ebef), NOT a new palette colour - it is the
   quietest tone that still reads as a box behind the 30-of-50 rows that have no photograph, and
   one token swap here retunes it. The rule declares no color:, because the box holds no text -
   nothing for the contrast gate to score and nothing for it to miss.
   HOW LOUD IS IT, measured: --line against the white row (--surface) is 1.19:1, so the box is
   quiet to the point of nearly disappearing. If it should be plainly SEEN, swap this one token
   to var(--neutral) (#878d95, 3.35:1 - clears the WCAG 1.4.11 non-text 3:1 threshold). Nothing
   else has to change: an existing :root token keeps the no-raw-hex floor clean, and this rule
   still declares no color:, so contrast scoring still has no pairing to judge. The choice is the
   Owner's taste, not a defect - see the T274 report and
   KC-DOCS/05_TRAFFIC/GATE_EVIDENCE/T274/T274_evidence/t274_box_contrast.py, which re-derives
   both numbers from this file.
   Same three declarations, same numbers, as .t274-thumb above: the harness fails if the two ever
   differ, which is what makes "same size" a machine promise rather than a comment. */
.t274-thumb-col{ width:3rem; }
.t274-thumb{ width:3rem; height:3rem; object-fit:cover; display:block; }
.t274-thumb-ph{ width:3rem; height:3rem; background:var(--line); display:block; }
/* T276 — the reference photograph on the QC inspector's screens. Two shapes, because the
   inspector meets the product in two different ways. On the RELEASE REGISTER (/qa) it is a 4rem
   row square: the same size as the picker's row (T275), because it is the same job — match a
   physical sack against a screen at arm's length — and the 192px derivative still gives that
   square three times the pixels a floor tablet can draw. On the LOT screen (/lot?id=) one lot is
   being held, released or recalled, so the picture is the 12rem reference the inspector actually
   reads a label off, contained rather than cropped so nothing on the sack is cut away.
   .t276-qc-thumb-ph is the photograph-less row slot (Owner ruling D23, 2026-08-08): the SAME
   square as a real photograph so the register's rows stay aligned, in the SAME grey as the search
   list and the pick row, so one operator never meets three different "no photograph" boxes. It
   repeats the size instead of leaning on .t276-qc-thumb — the harness fails if the two ever drift
   apart. Layout only, with one background declared without a colour beside it, so the hex-strict,
   font-floor and contrast gates have nothing new to score. */
.t276-qc-thumb-col{ width:4rem; }
.t276-qc-thumb{ width:4rem; height:4rem; object-fit:cover; display:block; }
.t276-qc-thumb-ph{ width:4rem; height:4rem; background:var(--line); display:block; }
.t276-qc-ref{ display:flex; flex-direction:column; gap:0.25rem; max-width:12rem; }
.t276-qc-ref-img{ max-width:12rem; max-height:12rem; width:auto; height:auto; object-fit:contain; display:block; }

/* ---------------------------------------------------------- promotions (T31)
   A document line whose price was bent by a promotion prints a second line naming that promotion
   and what it took off, on the order confirmation, on the tax invoice and on the promotion
   screens. One rule so all three say the same thing.
   SIZE: --fs-table, the DATA floor, not the smaller secondary token. This line carries a
   promotion code and two money figures on a statutory document, so it is data, and nothing a
   customer must read on a tax invoice should be smaller than the numbers around it. It is made
   visually secondary by COLOUR (--muted), never by shrinking it. The design-law harness reached
   the same verdict independently -- an unclassified selector falls to the data floor -- and this
   rule was raised to satisfy it, not renamed to dodge it. */
.doc-promo{ display:block; font-size:var(--fs-table); color:var(--muted); }
.doc-promo b{ font-weight:600; color:var(--ink); }
.promo-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(210px,1fr)); gap:12px 16px; }
.promo-actions{ display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-top:12px; }
.promo-head{ display:flex; justify-content:space-between; align-items:center; gap:12px; }
.promo-live{ font-weight:600; color:var(--ok-ink); }
.promo-off{ color:var(--muted); }

/* ============================================================================
   LAW-6 PAGE ANATOMY — the mandated regions (T369, 2026-08-10).
   Every region below has exactly ONE place on every screen. A page does not
   style these; it fills the matching block in base.html. Adding markup of your
   own instead of using a region is what tests/t369_page_anatomy_harness.py
   refuses. Region letters match the skeleton in 09_DESIGN_LAW LAW-6.
   ========================================================================= */

/* [B]+[C] page head — the subtitle and the action row share one row so the
   title (in .topbar, region A) is never repeated inside the page. */
.page-head{ display:flex; align-items:flex-start; gap:16px; margin:0 0 14px; flex-wrap:wrap; }
.page-head:has(> .page-sub:empty):has(> .page-actions:empty){ display:none; margin:0; }
.page-sub{ flex:1 1 320px; min-width:0; color:var(--muted); font-size:var(--fs-base); line-height:var(--lh-loose); margin:0; }
.page-actions{ display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-left:auto; }

/* [D] message region — banners live HERE and nowhere else. The .note component
   (LAW-3.1) does the colour; this container only fixes the place. Repeating the
   background on the tone class is deliberate (design-law GATE 3 scores a pairing
   only when one rule declares both colour and background) -- do not "clean up". */
.msg-region{ display:block; margin:0 0 14px; }
.msg-region:empty{ display:none; margin:0; }
.note .note-title{ display:block; font-weight:600; margin-bottom:2px; }
.note .note-act{ display:inline-block; margin-top:6px; font-weight:600; text-decoration:underline; color:inherit; }

/* [E] assist strip (T367) — assist tools sit in ONE named strip at the top of
   the work area. Routine documents stay in the left menu; they never mix. */
.assist-strip{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; padding:9px 13px; margin:0 0 14px; background:var(--accent-soft); color:var(--accent-ink); border:1px solid var(--line); border-radius:var(--radius-sm); font-size:var(--fs-table); }
.assist-strip .assist-label{ font-weight:600; color:var(--accent-ink); background:var(--accent-soft); display:inline-flex; align-items:center; gap:6px; margin-right:2px; }
.assist-strip a{ color:var(--accent-ink); background:var(--accent-soft); text-decoration:none; border:1px solid var(--line); border-radius:var(--radius-pill); padding:3px 11px; font-size:var(--fs-table); }
.assist-strip a:hover{ background:var(--surface); }

/* [J] page footer — provenance only: where the numbers came from, as of when,
   and who to ask. Never navigation, never a second action bar. */
.page-foot{ margin:22px 0 0; padding-top:12px; border-top:1px solid var(--line); color:var(--muted); background:var(--surface); font-size:var(--fs-table); line-height:var(--lh-loose); }
.page-foot:empty{ display:none; border:none; margin:0; padding:0; }

/* [K] the ONE confirm dialog — a blocking question is a designed screen, never
   the browser's own alert()/confirm() box (LAW-8.2 H6). Shell-owned; a page
   opens it with data-kc-confirm, it never builds its own. */
.kc-confirm-ov{ position:fixed; inset:0; background:rgba(20,24,30,.42); z-index:920; display:flex; align-items:center; justify-content:center; padding:20px; }
.kc-confirm-bx{ background:var(--surface); color:var(--ink); border-radius:var(--radius-lg); box-shadow:var(--shadow-1); width:min(460px,96vw); padding:20px 22px; }
.kc-confirm-bx h2{ margin:0 0 8px; font-size:var(--fs-lg); font-weight:600; }
.kc-confirm-bx .kc-confirm-msg{ color:var(--muted); background:var(--surface); font-size:var(--fs-base); line-height:var(--lh-loose); margin:0 0 16px; }
.kc-confirm-bx .kc-confirm-acts{ display:flex; gap:8px; justify-content:flex-end; }
.kc-confirm-bx .btn{ min-height:44px; }

/* App-shell + [B]/[C]/[E] responsive floor -- LAW-6.6: legible and operable at 375px. */
.mobile-nav-toggle,.mobile-nav-overlay{ display:none; }
@media (max-width:640px){
  body{ display:block; }
  .side{
    position:fixed; inset:0 auto 0 0; width:min(82vw,20rem); height:100dvh;
    z-index:30; transform:translateX(-100%); transition:transform .18s ease;
    box-shadow:var(--shadow-3);
  }
  body.kc-nav-open{ overflow:hidden; }
  body.kc-nav-open .side{ transform:translateX(0); }
  .mobile-nav-overlay{
    display:block; position:fixed; inset:0; z-index:29;
    background:color-mix(in srgb,var(--ink) 42%,transparent);
    opacity:0; pointer-events:none; transition:opacity .18s ease;
  }
  body.kc-nav-open .mobile-nav-overlay{ opacity:1; pointer-events:auto; }
  .mobile-nav-toggle{
    display:inline-flex; align-items:center; justify-content:center; flex:0 0 44px;
    width:44px; height:44px; border:1px solid var(--line); border-radius:var(--radius-sm);
    color:var(--ink); background:var(--surface); font-size:var(--fs-xl); cursor:pointer;
  }
  .main{ width:100%; height:100dvh; }
  .topbar{ padding:8px 12px; gap:8px; flex-wrap:wrap; }
  .topbar h1{ flex:1 1 14rem; min-width:0; }
  .topbar .crumb{ order:3; flex:1 1 100%; }
  .topbar form[action="/search"]{ order:4; flex:1 1 100%; margin-left:0 !important; }
  .topbar form[action="/search"] input{ width:100% !important; min-height:44px; }
  .content{ width:100%; max-width:none; padding:16px 12px; }
  .page-head{ flex-direction:column; gap:10px; }
  .page-sub{ flex:none; width:100%; }
  .page-actions{ margin-left:0; width:100%; }
  .content .btn,.content button,
  .content input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
  .content select,.content textarea{ min-height:44px; }
  .content input[type="checkbox"],.content input[type="radio"]{ width:44px; height:44px; margin:0; }
  .service-work-grid,.service-field-grid{ grid-template-columns:1fr !important; }
  .mobile-stack{ flex-direction:column; align-items:stretch !important; flex-wrap:wrap; }
  .mobile-stack > .btn,.mobile-stack > button{ justify-content:center; }
  .assist-strip a{ min-height:44px; display:inline-flex; align-items:center; }
}
@media (prefers-reduced-motion: reduce){
  .side,.mobile-nav-overlay{ transition:none; }
}

/* The page-title icon and the message list: two classes that exist so a template
   never needs an inline style for them (LAW-1.1 tokens/classes, never literals).
   Added by T369 because the screen template would otherwise ship with the very
   inline style the ratchet forbids a new template from carrying. */
.h-ic{ vertical-align:-2px; margin-right:5px; color:var(--accent); }
.note-list{ margin:4px 0 0; padding-left:18px; }

/* Marketplace channel screens: shared sync form, stat strip and small print. */
.mp-form-row{ display:flex; gap:12px; flex-wrap:wrap; align-items:flex-end; }
.mp-channel-select{ min-width:120px; }
.mp-stat-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr)); gap:10px; }
.mp-stat{ text-align:center; padding:10px; }
.mp-stat-value{ font-size:22px; font-weight:700; }
.mp-warn-head{ color:var(--warn-ink); }
.mp-flag{ color:var(--bad); }
.tag.tag-neutral{ background:var(--neutral-soft); }
.mt-4{ margin-top:4px; }
.mt-8{ margin-top:8px; }
.mt-14{ margin-top:14px; }

/* R54: the shared furniture used by every document form. */
.form-shell{ border:1px solid var(--line); border-radius:var(--radius-md); background:var(--surface); margin-bottom:16px; box-shadow:var(--shadow-1); }
.form-shell-head{ display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; padding:12px 14px; border-bottom:1px solid var(--line2); }
.form-shell-title{ display:inline; margin:0 8px 0 0; font-size:var(--fs-lg); }
.form-shell-actions{ display:flex; gap:8px; flex-wrap:wrap; }
.form-shell-body{ padding:14px; }
.form-shell-body.is-locked{ opacity:.88; }
.form-shell-audit{ padding:0 14px; border-bottom:1px solid var(--line2); }
