/* ============================================================
   Aixly MES — mockup design system
   Aslan internal-tool conventions. Three-layer tokens
   (primitive -> semantic -> component). No hardcoded hex
   outside the primitive layer. Tabular figures on numbers.
   Mobile-first: min-width media queries only (frontend-responsive).
   Consolidated into one file for the mockup set (production
   build splits into tokens/reset/theme/base/components/utilities).
   ============================================================ */

/* ---------- Layer 1: primitives ---------- */
:root{
  /* One UI family (internal-tool rule). IBM Plex Sans + IBM Plex Mono: matches
     the canonical Aixly marketing identity (aixly.netlify.app, ADR-004) —
     engineering/industrial heritage, distinctive, tabular figures for tables. */
  --font-body:'IBM Plex Sans',system-ui,-apple-system,sans-serif;
  --font-mono:ui-monospace,'IBM Plex Mono','JetBrains Mono',Menlo,monospace;

  --text-xs:.75rem; --text-sm:.875rem; --text-base:1rem; --text-lg:1.25rem;
  --text-xl:1.563rem; --text-2xl:1.953rem; --text-3xl:2.441rem;
  --leading-tight:1.2; --leading-normal:1.5;

  --white:#ffffff;
  /* Canvas + rule lines + ink, matched exactly to the Aixly marketing site
     (aixly.netlify.app): a blueprint off-white, a faintly cooler panel, bluish
     1px rule lines, and a navy "ink" for text/dark surfaces. */
  --paper:#eef2f7; --panel:#f7f9fc; --ink:#15223e;
  --line:#c6d2e2; --line2:#aebcd2;
  --gray-1:#f8fafc; --gray-2:#f1f5f9; --gray-3:#e2e8f0; --gray-4:#cbd5e1;
  --gray-5:#94a3b8; --gray-6:#64748b; --gray-7:#475569; --gray-8:#334155;
  --gray-9:#1e293b; --gray-10:#0f172a;

  /* Brand ramp — Aixly industrial blue, reseeded to the canonical marketing
     identity (aixly.netlify.app: --blue #1e57bd / --blue-d #163f8c, ADR-004).
     --brand-9 is the primary; ramp derived around it. */
  --brand-1:#eef3fb; --brand-2:#d4e2f5; --brand-3:#aec7ea;
  --brand-5:#3a6fd0; --brand-6:#2a5fc0;
  --brand-9:#1e57bd; --brand-10:#163f8c; --brand-11:#102d66;
  --sidebar-base:var(--ink);       /* dark surface = the site's #15223e ink */

  --success-1:#e9f7ef; --success-3:#bfe6cd; --success-9:#16a34a; --success-11:#0f7a37;
  --warning-1:#fff4e5; --warning-3:#f3d9b5; --warning-9:#d97706; --warning-11:#b45309;
  --danger-1:#fdecec;  --danger-2:#fbdcdc;  --danger-3:#f3c7c7;  --danger-9:#dc2626; --danger-11:#b91c1c;
  --info-1:#e6f3fb;    --info-3:#bcdff2;    --info-9:#0284c7;    --info-11:#036aa1;

  /* Chart palette — categorical, separate from brand, NO indigo/violet/fuchsia.
     Max 6 (use 3-5); always pair with a legend (internal-tool-conventions). */
  --chart-1:#1e57bd; --chart-2:#16a34a; --chart-3:#0891b2; --chart-4:#d97706; --chart-5:#475569;

  /* breakpoints (Tailwind set — industry consensus; min-width only) */
  --bp-sm:640px; --bp-md:768px; --bp-lg:1024px; --bp-xl:1280px;

  /* ---------- Layer 2: semantic ---------- */
  --color-primary:var(--brand-9); --color-primary-hover:var(--brand-10);
  --color-primary-active:var(--brand-11); --color-on-primary:var(--white);
  --color-success:var(--success-9); --color-warning:var(--warning-9);
  --color-danger:var(--danger-9); --color-info:var(--info-9);

  --bg-page:var(--white); --bg-surface:var(--paper); --bg-sidebar:var(--sidebar-base);
  --bg-inverse:var(--gray-10);
  --text-primary:var(--ink); --text-secondary:var(--gray-7);
  --text-muted:var(--gray-5); --text-on-dark:var(--white);
  --border-subtle:var(--line); --border-strong:var(--line2);

  --space-1:.25rem; --space-2:.5rem; --space-3:.75rem; --space-4:1rem;
  --space-5:1.5rem; --space-6:2rem; --space-7:3rem; --space-8:4rem;

  /* Radius — sharp, near-square edges to match the marketing site's blueprint
     look (its controls/boxes are 2-4px). Pill kept for true round controls. */
  --radius-sm:2px; --radius-md:3px; --radius-lg:4px; --radius-pill:999px;
  /* Shadows — subtle, brand-tinted, NO glow */
  --shadow-sm:0 1px 2px rgba(15,23,42,.06);
  --shadow-md:0 4px 12px rgba(30,87,189,.08);
  --shadow-lg:0 12px 28px rgba(30,87,189,.12);
  --ease-out:cubic-bezier(.16,1,.3,1); --dur-fast:150ms; --dur-normal:250ms;
  --sidebar-w:256px; --sidebar-rail:64px; --sidebar-sheet:288px;
  --control-min:44px; /* WCAG 2.5.5 touch target — inputs, kiosk controls */
  /* Button height. Phones keep the full touch target; a pointer doesn't need it,
     so ≥768px buttons come down to a tighter desk height (see the md query). */
  --btn-h:var(--control-min); --btn-h-sm:36px;
  --topbar-h:56px;      /* the app bar; .page-action-bar sticks directly below it */
  --bottom-nav-h:56px; /* mobile bottom tab-bar height (above the home-bar inset) */
}

/* ---------- reset-ish ---------- */
*,*::before,*::after{box-sizing:border-box}
/* [hidden] must win even over display:grid (card-stack table rows) — needed for
   the live list filter to hide rows on mobile, and for any toggled element. */
[hidden]{display:none!important}
html{scroll-padding-top:5rem}
body{margin:0;font-family:var(--font-body);font-size:var(--text-base);
  line-height:var(--leading-normal);color:var(--text-primary);
  /* Blueprint grid behind the content area (the marketing-site signature).
     Bordered white cards lift off it; the sidebar's solid fill covers it. */
  background-color:var(--bg-surface);
  background-image:linear-gradient(rgba(30,87,189,.045) 1px,transparent 1px),
                   linear-gradient(90deg,rgba(30,87,189,.045) 1px,transparent 1px);
  background-size:40px 40px;
  -webkit-font-smoothing:antialiased;font-variant-numeric:tabular-nums lining-nums}
h1,h2,h3,h4{margin:0;line-height:var(--leading-tight);font-weight:650;letter-spacing:-.015em}
p{margin:0}
a{color:var(--color-primary);text-decoration:none}
a:hover{text-decoration:underline}
table,.tabular,[data-tabular]{font-variant-numeric:tabular-nums lining-nums}
button{font:inherit}
:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}

/* ---------- app shell (mobile-first: single column, sidebar off-canvas) ---------- */
.app{display:grid;grid-template-columns:1fr;min-height:100vh}
/* Mobile (<768px): the sidebar is replaced by the fixed bottom tab bar + section
   hubs, so it is removed from the layout entirely (re-shown as a rail ≥768px). */
/* Three bands: brand, a nav that takes the slack and scrolls, and the account
   block. Only the middle one scrolls, so the workspace/identity footer is
   always on screen without position:sticky. */
.sidebar{background:var(--bg-sidebar);color:var(--text-on-dark);
  position:fixed;left:0;top:0;width:var(--sidebar-sheet);height:100vh;z-index:40;
  display:none;flex-direction:column;padding:var(--space-4) 0;overflow:hidden}
.sidebar-nav{flex:1 1 auto;min-height:0;overflow-y:auto}
.sidebar-brand{display:flex;align-items:center;gap:var(--space-3);
  padding:var(--space-2) var(--space-5) var(--space-5);font-weight:700;font-size:var(--text-lg);
  letter-spacing:-.01em}
/* Collapse control, docked to the brand row. Hidden until ≥1024px — the 64px
   rail has no room beside the mark, so the topbar twin drives it there. */
.sidebar-collapse{display:none;align-items:center;justify-content:center;margin-left:auto;
  flex:0 0 auto;width:32px;height:32px;padding:0;border:1px solid transparent;
  border-radius:var(--radius-sm);background:none;color:var(--brand-2);cursor:pointer;font-size:18px}
.sidebar-collapse:hover{background:rgba(255,255,255,.1);color:var(--white)}
.sidebar-collapse:focus-visible{outline:2px solid var(--brand-5);outline-offset:1px}
/* Brand mark: real Aixly logo (black-on-transparent PNG), inverted to white
   for the dark navy sidebar. */
.sidebar-brand .mark{width:auto;height:26px;flex:0 0 auto;border-radius:0;
  background:none;filter:brightness(0) invert(1)}
.sidebar-section{padding:var(--space-3) var(--space-5) var(--space-2);font-size:var(--text-xs);
  text-transform:uppercase;letter-spacing:.06em;color:var(--brand-2);opacity:.65}
.nav-link{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3) var(--space-5);
  color:var(--brand-2);font-size:var(--text-sm);font-weight:500;border-left:3px solid transparent;white-space:nowrap;position:relative}
.nav-link>span{min-width:0;overflow:hidden;text-overflow:ellipsis}
.nav-link .ti{flex:0 0 auto}
.nav-link:hover{background:rgba(255,255,255,.06);color:var(--white);text-decoration:none}
.nav-link.active{background:rgba(255,255,255,.1);color:var(--white);border-left-color:var(--brand-5)}
.nav-link .ti{font-size:18px}
.sidebar-foot{padding:var(--space-2) var(--space-5) 0;font-size:var(--text-xs);
  color:var(--brand-2);opacity:.6}

/* ---------- sidebar account block (workspace · who you are · sign out) ----------
   Pinned to the foot of the sidebar by the flex band above; it never scrolls. */
.sidebar-account{flex:0 0 auto;padding-top:var(--space-3);
  border-top:1px solid rgba(255,255,255,.1)}
.account{display:flex;align-items:center;gap:var(--space-2);padding:0 var(--space-4) var(--space-2)}
.account-link{display:flex;align-items:center;gap:var(--space-3);min-width:0;flex:1 1 auto;
  padding:var(--space-1);border-radius:var(--radius-sm);color:inherit}
.account-link:hover{text-decoration:none}
.account-id{display:flex;flex-direction:column;min-width:0;line-height:1.25}
.account-name{font-size:var(--text-sm);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.account-role{font-size:var(--text-xs);opacity:.7;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.account-signout{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:32px;height:32px;padding:0;border:1px solid transparent;border-radius:var(--radius-sm);
  background:none;color:var(--text-secondary);cursor:pointer;font-size:18px}
.account-signout:hover{background:var(--bg-surface);color:var(--text-primary)}
/* on the dark sidebar the same block reads in the on-dark palette */
.sidebar-account .account-link{color:var(--white)}
.sidebar-account .account-link:hover{background:rgba(255,255,255,.06)}
.sidebar-account .account-role{color:var(--brand-2)}
.sidebar-account .account-signout{color:var(--brand-2)}
.sidebar-account .account-signout:hover{background:rgba(255,255,255,.1);color:var(--white)}
/* topbar placement (phones, and any width while the sidebar is hidden) */
.account--compact{padding:0}

/* ---------- org switcher (tenant context) ---------- */
.org-switcher{position:relative;padding:0 var(--space-4) var(--space-3)}
.org-switcher-btn{display:flex;align-items:center;gap:var(--space-3);width:100%;text-align:left;
  padding:var(--space-3);background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);
  border-radius:var(--radius-md);color:var(--white);cursor:pointer}
.org-switcher-btn:hover{background:rgba(255,255,255,.1)}
.org-switcher-mark,.org-option-mark{flex:0 0 auto;width:28px;height:28px;border-radius:var(--radius-sm);
  display:grid;place-items:center;font-weight:800;font-size:var(--text-sm);
  background:linear-gradient(135deg,var(--brand-5),var(--brand-9));color:var(--white)}
.org-switcher-text,.org-option-text{min-width:0;flex:1 1 auto;display:flex;flex-direction:column;line-height:1.25}
.org-switcher-name,.org-option-name{font-size:var(--text-sm);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.org-switcher-sub,.org-option-sub{font-size:var(--text-xs);color:var(--brand-2);opacity:.7;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.org-switcher-btn > .ti{flex:0 0 auto;opacity:.7}
/* opens upward — the switcher now sits at the foot of the sidebar */
.org-menu{position:absolute;left:var(--space-4);right:var(--space-4);bottom:calc(100% - var(--space-2));z-index:30;
  min-width:220px;background:var(--bg-page);border:1px solid var(--border-subtle);border-radius:var(--radius-md);
  box-shadow:var(--shadow-lg);padding:var(--space-2);display:grid;gap:2px}
.org-menu[hidden]{display:none}  /* the hidden attr must win over display:grid */
.org-option{display:flex;align-items:center;gap:var(--space-3);width:100%;text-align:left;padding:var(--space-2) var(--space-3);
  background:none;border:0;border-radius:var(--radius-sm);cursor:pointer;color:var(--text-primary)}
.org-option:hover{background:var(--bg-surface);text-decoration:none}
.org-option .org-option-name{color:var(--text-primary)}
.org-option .org-option-sub{color:var(--text-muted);opacity:1}
.org-option.is-current{background:var(--brand-1)}
.org-option > .ti{margin-left:auto;color:var(--color-primary)}
.org-option-mark.mark--platform{background:var(--brand-10)}
.org-menu-sep{height:1px;background:var(--border-subtle);margin:var(--space-2) var(--space-1)}

.main{display:flex;flex-direction:column;min-width:0}
/* Mobile-first app-bar: brand left, essential actions right, evenly padded and
   vertically centred. Search is hidden on phones (it crowds the bar) and shown
   again at ≥768px where the topbar-brand drops out for the sidebar. */
/* A fixed height, not a min-height: .page-action-bar sticks to the same token,
   so the two bars can never drift into a 1px overlap. */
.topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);
  padding:0 var(--space-4);height:var(--topbar-h);flex:0 0 auto;background:var(--bg-page);
  border-bottom:1px solid var(--border-subtle);position:sticky;top:0;z-index:20}
/* Sidebar reveal toggle — twin of the one in the sidebar brand row. Hidden on
   phones (no sidebar there), shown on the 64px rail, and shown at any width
   while the sidebar is hidden, where it is the only way to bring it back. */
.topbar-icon-btn{display:none;align-items:center;justify-content:center;flex:0 0 auto;
  width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--radius-sm);
  background:none;color:var(--text-secondary);cursor:pointer;font-size:20px}
.topbar-icon-btn:hover{background:var(--bg-surface);color:var(--text-primary)}
.topbar-icon-btn:focus-visible{outline:2px solid var(--color-primary);outline-offset:1px}
.topbar-brand{display:flex;align-items:center;gap:var(--space-2);min-width:0;flex:0 1 auto;
  font-weight:700;font-size:var(--text-base);color:var(--text-primary);letter-spacing:-.01em}
.topbar-brand:hover{text-decoration:none}
.topbar-brand .mark{flex:0 0 auto;width:auto;height:24px;border-radius:0;background:none}
.topbar-brand-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.topbar-search{display:none;align-items:center;gap:var(--space-2);background:var(--bg-surface);
  border:1px solid var(--border-subtle);border-radius:var(--radius-md);padding:var(--space-2) var(--space-3);
  color:var(--text-muted);flex:1 1 220px;min-width:0;max-width:440px}
.topbar-search input{border:0;background:none;outline:none;font:inherit;width:100%;min-width:0;color:var(--text-primary)}
.topbar-search{position:relative}
.global-search-results{position:absolute;z-index:40;top:calc(100% + var(--space-2));left:0;right:0;min-width:min(30rem,calc(100vw - var(--space-6)));max-height:min(28rem,70vh);overflow:auto;background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-md);box-shadow:var(--shadow-lg);padding:var(--space-2)}
.global-search-result{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);min-height:44px;padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);color:var(--text-primary)}
.global-search-result:hover,.global-search-result.is-active{background:var(--brand-1);text-decoration:none}
.global-search-result-body{display:flex;flex-direction:column;min-width:0}.global-search-result-body strong,.global-search-result-body small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.global-search-result-body small{color:var(--text-muted)}
.global-search-empty{margin:0;padding:var(--space-3);color:var(--text-muted)}
/* margin-left:auto, not justify-content on the parent — the bar's other children
   (brand, search, reveal toggle) each drop out at some breakpoint, and the user
   cluster must stay hard right regardless of which ones are present. */
.topbar-user{display:flex;align-items:center;gap:var(--space-2);flex-shrink:0;margin-left:auto}
.avatar{width:34px;height:34px;border-radius:var(--radius-pill);background:var(--brand-5);color:var(--white);
  display:grid;place-items:center;font-size:var(--text-sm);font-weight:600;text-decoration:none;flex-shrink:0}
.content{padding:var(--space-4);max-width:1200px;width:100%}

/* ---------- mobile bottom tab bar (<768px; replaces the hamburger) ----------
   Five thumb-reachable destinations fixed to the bottom third of the screen,
   each a ≥44px touch target with an icon + short label. Padded by the iOS
   home-bar inset (env safe-area, with a 0 fallback for non-notched devices).
   Hidden ≥768px where the desktop sidebar returns. */
.bottom-nav{position:fixed;left:0;right:0;bottom:0;z-index:30;display:flex;
  background:var(--bg-page);border-top:1px solid var(--border-subtle);
  box-shadow:var(--shadow-lg);
  padding-bottom:env(safe-area-inset-bottom,0px)}
.bottom-nav-item{flex:1 1 0;min-width:0;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:2px;min-height:var(--bottom-nav-h);padding:var(--space-2) var(--space-1);
  color:var(--text-secondary);font-size:var(--text-xs);font-weight:500;text-decoration:none;
  text-align:center}
.bottom-nav-item .ti{font-size:22px;line-height:1}
.bottom-nav-item>span{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bottom-nav-item:hover{text-decoration:none;color:var(--text-primary)}
.bottom-nav-item.active{color:var(--color-primary)}
.bottom-nav-item.active .ti{color:var(--color-primary)}
/* clear the fixed bar so it never covers page content (only when the bar shows) */
.main{padding-bottom:calc(var(--bottom-nav-h) + env(safe-area-inset-bottom,0px))}

/* ---------- section hub page (big stacked touch targets) ---------- */
.hub-list{display:flex;flex-direction:column;gap:var(--space-3);max-width:640px}
.hub-item{display:flex;align-items:center;gap:var(--space-4);width:100%;
  min-height:64px;padding:var(--space-4) var(--space-5);
  background:var(--bg-page);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-sm);color:var(--text-primary);font-weight:600;font-size:var(--text-base);
  text-decoration:none;transition:border-color var(--dur-fast) var(--ease-out),box-shadow var(--dur-fast) var(--ease-out)}
.hub-item:hover{text-decoration:none;border-color:var(--brand-3);box-shadow:var(--shadow-md)}
.hub-item-icon{flex:0 0 auto;width:40px;height:40px;border-radius:var(--radius-md);display:grid;
  place-items:center;background:var(--brand-1);color:var(--color-primary)}
.hub-item-icon .ti{font-size:22px}
.hub-item-label{flex:1 1 auto;min-width:0}
.hub-item-go{flex:0 0 auto;color:var(--text-muted);font-size:20px}

/* ---------- page action bar (sticky top, Save lives here) ---------- */
/* A chrome bar, not a section: it holds one line of controls, so its height is
   the control height plus a hair — never the page's own padding scale. */
.page-action-bar{position:sticky;top:var(--topbar-h);z-index:10;display:flex;align-items:center;flex-wrap:wrap;
  justify-content:space-between;gap:var(--space-2) var(--space-4);padding:var(--space-2) var(--space-4);
  background:var(--bg-page);border-bottom:1px solid var(--border-subtle)}
.page-action-bar .back-link{display:inline-flex;align-items:center;gap:var(--space-2);
  color:var(--text-secondary);font-weight:500;white-space:nowrap}
.page-action-bar-actions{display:flex;align-items:center;gap:var(--space-2)}
.page-title{font-size:var(--text-xl);font-weight:700}
/* The tab strip is the header's own control row — it reads as part of the
   record, so the two sit close together (see .page-header + .tabs below). */
.page-header{margin:0 0 var(--space-2)}
.page-header h1{font-size:var(--text-2xl)}
.page-subtitle{color:var(--text-secondary);margin-top:var(--space-2)}
.page-meta-row{display:flex;flex-wrap:wrap;gap:var(--space-2);align-items:center;margin-top:var(--space-3)}

/* ---------- buttons ---------- */
/* Height comes from --btn-h, not from vertical padding, so every button in a
   row lines up and the desk/phone sizes are one token apart. */
.btn-primary,.btn-secondary,.btn-text,.btn-danger{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
  min-height:var(--btn-h);padding:var(--space-1) var(--space-4);border-radius:var(--radius-sm);
  font-family:var(--font-mono);font-weight:500;font-size:var(--text-xs);letter-spacing:.04em;text-transform:uppercase;
  border:1px solid transparent;cursor:pointer;transition:background var(--dur-fast) var(--ease-out);white-space:nowrap}
.btn-primary{background:var(--color-primary);color:var(--color-on-primary)}
.btn-primary:hover{background:var(--color-primary-hover);text-decoration:none;color:var(--white)}
.btn-secondary{background:var(--bg-page);color:var(--text-primary);border-color:var(--border-strong)}
.btn-secondary:hover{background:var(--bg-surface);text-decoration:none}
.btn-text{background:none;color:var(--text-secondary)}
.btn-text:hover{background:var(--bg-surface);text-decoration:none}
.btn-danger{background:var(--danger-1);color:var(--danger-11);border-color:var(--danger-3)}
.btn-danger:hover{background:var(--danger-2);text-decoration:none}
.btn-sm{min-height:var(--btn-h-sm);padding:var(--space-1) var(--space-3);font-size:var(--text-xs)}

/* Explainer marker — the standing paragraph a card title doesn't need. The
   prose lives in the bubble; the page keeps the room. Same treatment as the
   icon-rail nav tooltip, but the text wraps: an explainer is a sentence, not a
   label. Its own bubble rather than title= — a native tooltip waits a second,
   renders in the OS font, and never appears on keyboard focus. */
.tip{position:relative;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;
  margin-left:var(--space-2);color:var(--text-muted);font-size:.85em;font-weight:400;cursor:help}
.tip:hover{color:var(--text-secondary)}
.tip::after{content:attr(data-tip);position:absolute;top:calc(100% + var(--space-2));left:0;z-index:60;
  width:max-content;max-width:min(32rem,70vw);white-space:normal;text-align:left;
  background:var(--bg-inverse);color:var(--white);padding:var(--space-2) var(--space-3);
  border-radius:var(--radius-sm);box-shadow:var(--shadow-lg);
  font-family:var(--font-body);font-size:var(--text-xs);font-weight:400;line-height:1.45;
  letter-spacing:normal;text-transform:none;
  opacity:0;visibility:hidden;transition:opacity var(--dur-fast) var(--ease-out)}
.tip:hover::after,.tip:focus-visible::after{opacity:1;visibility:visible}
/* near the right edge the bubble flips its anchor so it can't run off-screen */
.tip--end::after{left:auto;right:0}
/* a measured rate that has drifted from the planned one — a flag, not an alarm.
   line-height:1 so the rows carrying one stay the same height as the rest. */
.rate-flag{display:inline-flex;vertical-align:middle;line-height:1;margin-left:var(--space-1);
  color:var(--warning-9);cursor:help}
.rate-flag:hover{color:var(--warning-11)}
/* a bare list where the markers would be noise (checks in a step, etc.) */
.plain-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}

/* ---------- edit-in-place inside a modal (routing step) ----------
   The same contract as the page-level Edit: the read value and its control live
   in the SAME cell, so pressing Edit swaps one for the other and no row moves.
   Note the toggle is on descendants only — never on the <dialog> itself, whose
   display belongs to [open]. */
.rs-modal .rs-edit{display:none}
.rs-modal.is-editing .rs-view{display:none}
.rs-modal.is-editing .rs-edit{display:block}
.rs-modal .rs-edit input,.rs-modal .rs-edit select,.rs-modal .rs-edit textarea{
  width:100%;max-width:20rem;padding:var(--space-2) var(--space-3);
  border:1px solid var(--border-strong);border-radius:var(--radius-sm);
  font:inherit;background:var(--bg-page);color:var(--text-primary)}
.rs-modal .rs-edit textarea{max-width:none;resize:vertical}
.rs-modal .modal-head .cluster{gap:var(--space-2);flex-wrap:nowrap}

/* ---------- blueprint utilities (marketing-site signatures) ---------- */
/* Mono uppercase eyebrow/label, used above titles and on section headers. */
.lbl{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--color-primary);font-weight:500}
/* Framed figure with corner ticks — for charts, screenshots, key panels. */
.frame{position:relative;border:1px solid var(--border-strong);background:var(--bg-page);padding:10px}
.frame::before,.frame::after{content:"";position:absolute;width:11px;height:11px;
  border:2px solid var(--color-primary);z-index:2}
.frame::before{left:-1px;top:-1px;border-right:none;border-bottom:none}
.frame::after{right:-1px;bottom:-1px;border-left:none;border-top:none}

/* ---------- cards / kpi ---------- */
.card{background:var(--bg-page);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);
  padding:var(--space-5)}
.card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4)}
/* Titled card whose body is a full-bleed table: pad + divide the header so it
   isn't jammed against the border, while table cells stay edge-aligned. */
.table-card>.card-head{margin:0;padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--border-subtle)}
/* List-table primary (name) column stays on ONE line on desktop — never the
   ugly two-line wrap. In card-stack mode (<=600px) it's the headline and may wrap. */
@media(min-width:601px){.data-table--list td.col-primary{white-space:nowrap}}
.card-title{font-size:var(--text-base);font-weight:650}
/* Connection-status light (work-orders header, 2026-07-08). */
.conn-dot{display:inline-flex;align-items:center;gap:.3rem;font-size:var(--text-xs);font-weight:600;
  vertical-align:middle;margin-left:var(--space-2);padding:.1rem .5rem;border-radius:999px}
.conn-dot--on{background:var(--success-1,var(--brand-1));color:var(--color-success);border:1px solid var(--success-9,var(--border-subtle))}
.grid{display:grid;gap:var(--space-5)}
.grid-2,.grid-3,.grid-4{grid-template-columns:1fr}
.kpi-row{display:grid;grid-template-columns:1fr;gap:var(--space-5);margin-bottom:var(--space-5)}
.kpi-card{background:var(--bg-page);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);
  padding:var(--space-5);transition:border-color var(--dur-normal) var(--ease-out);
  display:flex;flex-direction:column;min-height:8.5rem}   /* uniform tiles: flex column + absolute floor */
.kpi-card:hover{border-color:var(--border-strong)}      /* flat, border-driven — no glow */
/* Drill-down KPI tile (e.g. Below reorder → filtered items list). */
.kpi-card--link{display:block;text-decoration:none;color:inherit;cursor:pointer}
.kpi-card--link:hover{border-color:var(--color-primary)}
.kpi-card--alert{border-color:var(--danger-9);background:var(--danger-1)}
.kpi-card--alert .kpi-value,.kpi-card--alert .kpi-delta{color:var(--danger-11)}
/* On-hand cell: number + stock badge, kept right-aligned with breathing room. */
.qty-with-badge{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-2);white-space:nowrap}

/* Routing editor (ADR-002): one operation per <tr>, inline editable, rendered as
   a sortable data-table--list so the inputs sit under labeled, click-to-sort
   headers. The controls carry form="rf-<opId>" (HTML form-association) since a
   <form> can't wrap <tr>/<td>; the real <form> elements live after the table. */
.routing-table input,.routing-table select{width:100%;padding:var(--space-2) var(--space-3);
  border:1px solid var(--border-strong);border-radius:var(--radius-sm);font:inherit;
  background:var(--bg-page);color:var(--text-primary);min-height:var(--control-min)}
.routing-table .ri-num,.routing-table .ri-seq{text-align:right;font-variant-numeric:tabular-nums}
/* Kill the native spinner on tabular number inputs. Chrome reserves ~20px for it
   inside the box, which in an 88px Seq column left about 10px of usable width —
   the number was rendering outside the visible area, so the field read as empty.
   These are typed, not stepped; the arrows were never worth the column. */
.routing-table input[type="number"],.qc-table input[type="number"]{appearance:textfield;-moz-appearance:textfield}
.routing-table input[type="number"]::-webkit-outer-spin-button,
.routing-table input[type="number"]::-webkit-inner-spin-button,
.qc-table input[type="number"]::-webkit-outer-spin-button,
.qc-table input[type="number"]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
/* and give the digits room to breathe rather than sitting on the border */
.routing-table .ri-seq{padding-inline:var(--space-2)}
/* Carry the custom chevron over from the old .routing-form select (the native
   chevron regressed when we dropped that class). */
.routing-table select{appearance:none;-webkit-appearance:none;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat:no-repeat;background-position:right .7rem center;padding-right:2.2rem}
/* Desktop column widths so numeric inputs stay compact and Operation takes slack.
   Signatures is a 1-digit field, so it gets less than the other numerics; operator
   instructions is a sentence, so it gets a real minimum and the wrapper scrolls
   rather than the text truncating. */
/* .data-table is width:100%, which makes an over-constrained table squeeze its
   narrow columns instead of letting the wrapper scroll. With nine editable
   columns that took Seq down to a 25px box whose number rendered outside the
   visible area — the field looked empty while holding a value. Size to content,
   keep filling the wrapper when there's room, and let .table-wrap scroll. */
.routing-table{width:auto;min-width:100%}
@media(min-width:601px){
  .routing-table .col-id{width:96px;min-width:96px}
  .routing-table td.num{width:120px}
  .routing-table .ri-type{min-width:8.5rem}
  .routing-table .ri-instr{min-width:20rem}
  .routing-table .col-actions{width:1%;white-space:nowrap}
  .routing-table .col-actions .cluster{justify-content:flex-end}
}
/* The Add-operation row sits in the <tfoot>, visually set off from the edits. */
.routing-table tfoot td{border-top:1px dashed var(--border-subtle);background:var(--bg-surface)}
/* Mobile (<=600px) card-stack: the value column is too narrow for a full-width
   input, so stack the label over a full-width control for the editable cells. */
@media(max-width:600px){
  .routing-table td:not(.col-primary):not(.col-actions){grid-template-columns:1fr;gap:var(--space-1);text-align:left}
  .routing-table td:not(.col-primary):not(.col-actions)::before{justify-self:start}
}
/* The real association forms are hidden helpers — collapse them entirely. */
form[id^="rf-"][hidden],form[id^="del-"][hidden]{display:none}

/* Process map (ADR-002): vertical stepper of a WO's operations. */
.process-map{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
.process-step{display:flex;align-items:flex-start;gap:var(--space-3);padding:var(--space-3) 0;position:relative}
.process-step:not(:last-child)::after{content:"";position:absolute;left:15px;top:32px;bottom:-4px;width:2px;background:var(--border-subtle)}
.process-step .step-seq{flex:none;width:32px;height:32px;border-radius:50%;display:grid;place-items:center;
  font-weight:700;font-size:var(--text-sm);background:var(--bg-page);color:var(--text-secondary);
  border:1px solid var(--border-strong);z-index:1}
.process-step--done .step-seq{background:var(--brand-2);color:var(--brand-11);border-color:var(--brand-2)}
.process-step.is-current .step-seq{background:var(--color-primary);color:var(--white);border-color:var(--color-primary)}
.process-step .step-body{display:flex;flex-direction:column;gap:2px;padding-top:4px}
.process-step .step-name{font-weight:600;display:flex;align-items:center;gap:var(--space-2)}
.process-step .step-meta{display:flex;align-items:center;gap:var(--space-2)}
/* Process map grouped by produced item (a WO can make several items). */
.op-item-group{margin-bottom:var(--space-5)}
.op-item-group:last-child{margin-bottom:0}
.op-item-title{display:flex;align-items:center;gap:var(--space-2);font-size:var(--text-base);
  font-weight:650;margin:0 0 var(--space-1);padding-bottom:var(--space-2);
  border-bottom:1px solid var(--border-subtle)}
.kpi-top{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-3)}
.kpi-icon{flex:0 0 auto;width:38px;height:38px;border-radius:var(--radius-md);display:grid;place-items:center;
  background:var(--brand-1);color:var(--brand-9)}
.kpi-icon .ti{font-size:20px}
/* Compact record stat line — the SINGLE-RECORD replacement for a .kpi-row.
   A detail page has nothing to compare against, so a row of KPI tiles is a
   dashboard pattern in the wrong place: it costs 8.5rem of height to say six
   numbers. One line of label · value pairs says the same thing above the fold.
   Zeros dim (.stat--zero) so a fresh record is not a wall of $0.00. */
.stat-line{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-2) var(--space-5);
  margin-bottom:var(--space-4);padding-bottom:var(--space-3);border-bottom:1px solid var(--border-subtle)}
.stat-line .stat{display:inline-flex;align-items:baseline;gap:var(--space-2);white-space:nowrap}
.stat-line .stat-key{color:var(--text-secondary);font-size:var(--text-sm)}
.stat-line .stat-val{font-size:var(--text-lg);font-weight:700;letter-spacing:-.01em;
  font-variant-numeric:tabular-nums}
.stat-line .stat--zero .stat-val{color:var(--text-muted);font-weight:600}
.stat-line .stat-note{color:var(--text-muted);font-size:var(--text-sm)}

.kpi-label{color:var(--text-secondary);font-size:var(--text-sm);display:flex;align-items:center;gap:var(--space-2)}
.kpi-value{font-size:var(--text-2xl);font-weight:700;margin-top:var(--space-2);letter-spacing:-.01em}
.kpi-delta{font-size:var(--text-sm);margin-top:var(--space-1);display:inline-flex;align-items:center;gap:.2rem;font-weight:600}
.kpi-delta.up{color:var(--color-success)} .kpi-delta.down{color:var(--color-danger)}
.kpi-delta.flat{color:var(--text-muted);font-weight:500}
.kpi-spark{margin-top:auto;padding-top:var(--space-3);display:block;width:100%;height:32px}   /* pin to card bottom */

/* ---------- chart cards + dependency-free, CSP-safe charts (SVG attributes) ---------- */
.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,340px),1fr));gap:var(--space-5);margin-bottom:var(--space-5)}
.chart-card{background:var(--bg-page);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-md);padding:var(--space-5);transition:box-shadow var(--dur-normal) var(--ease-out)}
.chart-card:hover{box-shadow:var(--shadow-lg)}
.chart-sub{font-size:var(--text-xs);color:var(--text-muted)}
.area-chart{display:block;width:100%;height:240px}
.donut-chart{display:block;width:200px;height:200px;margin:var(--space-3) auto}
.chart-legend{display:flex;flex-wrap:wrap;gap:var(--space-4);margin-top:var(--space-3);font-size:var(--text-xs);color:var(--text-secondary)}
.chart-legend span{display:inline-flex;align-items:center;gap:var(--space-2)}
.chart-legend i{width:10px;height:10px;border-radius:3px;display:inline-block}     /* legend = the chart's 2nd encoding */
.chart-legend--center{justify-content:center}
.swatch--chart-1{background:var(--chart-1)} .swatch--chart-2{background:var(--chart-2)}
.swatch--chart-3{background:var(--chart-3)} .swatch--chart-4{background:var(--chart-4)} .swatch--chart-5{background:var(--chart-5)}
.stroke-chart-1{stroke:var(--chart-1)} .stroke-chart-2{stroke:var(--chart-2)}
.stroke-chart-3{stroke:var(--chart-3)} .stroke-chart-4{stroke:var(--chart-4)} .stroke-chart-5{stroke:var(--chart-5)}
.fill-chart-1{fill:var(--chart-1)} .fill-chart-2{fill:var(--chart-2)} .fill-chart-grid{fill:var(--gray-2)}

/* ---------- stat tiles (compact, for detail pages) ---------- */
.stat-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,140px),1fr));gap:var(--space-4)}
.stat-tile{background:var(--bg-page);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);padding:var(--space-4)}
.stat-tile .lbl{font-size:var(--text-xs);color:var(--text-secondary)}
.stat-tile .val{font-size:var(--text-xl);font-weight:700;margin-top:var(--space-1)}

/* ---------- machine / utilization mini-bars ---------- */
.util-bar{height:10px;border-radius:var(--radius-pill);background:var(--gray-2);overflow:hidden;margin-top:var(--space-2)}
.util-bar>span{display:block;height:100%;border-radius:inherit;background:var(--chart-1)}

/* ---------- badges / chips ---------- */
.badge{display:inline-flex;align-items:center;gap:var(--space-1);padding:.15rem .5rem;border-radius:var(--radius-sm);
  font-family:var(--font-mono);font-size:var(--text-xs);font-weight:500;letter-spacing:.03em;border:1px solid transparent;white-space:nowrap}
.badge--success{background:var(--success-1);color:var(--success-11);border-color:var(--success-3)}
.badge--warning{background:var(--warning-1);color:var(--warning-11);border-color:var(--warning-3)}
.badge--danger{background:var(--danger-1);color:var(--danger-11);border-color:var(--danger-3)}
.badge--info{background:var(--info-1);color:var(--info-11);border-color:var(--info-3)}
.badge--neutral{background:var(--gray-2);color:var(--gray-7);border-color:var(--gray-3)}
.chip{display:inline-flex;align-items:center;gap:var(--space-1);padding:.15rem .5rem;border-radius:var(--radius-sm);
  background:var(--gray-2);color:var(--gray-7);font-size:var(--text-xs);font-weight:500;white-space:nowrap}
/* A .chip can also be a clickable token (e.g. the serial-format builder) —
   strip the native <button> chrome so it still reads as a chip, not a button. */
button.chip{border:0;cursor:pointer;font-family:var(--font-mono)}
button.chip:hover{background:var(--gray-3)}
button.chip:focus-visible{outline:2px solid var(--color-primary);outline-offset:1px}
.dot{width:8px;height:8px;border-radius:50%;display:inline-block}
.dot--ok{background:var(--success-9)} .dot--warn{background:var(--warning-9)}
.dot--down{background:var(--danger-9)} .dot--idle{background:var(--gray-4)}

/* ---------- tables ---------- */
.data-table{width:100%;border-collapse:collapse;background:var(--bg-page);font-size:var(--text-sm)}
.data-table th{text-align:left;padding:var(--space-3) var(--space-4);color:var(--text-secondary);
  font-weight:600;font-size:var(--text-xs);text-transform:uppercase;letter-spacing:.04em;
  border-bottom:2px solid var(--border-subtle);background:var(--bg-surface)}
.data-table td{padding:var(--space-2) var(--space-4);border-bottom:1px solid var(--border-subtle)}
.data-table tbody tr:hover{background:var(--brand-1)}
.data-table .num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
.data-table .col-id,.data-table td .badge,.data-table td .chip{white-space:nowrap}
/* table chrome: ids/dates/numbers never wrap; long names clamp to 2 lines */
.data-table td.nowrap,.data-table th{white-space:nowrap}
/* Opt-in: a table whose every cell stays on one line (horizontal scroll instead
   of wrapping) — used by the work-orders table. */
.data-table--nowrap td{white-space:nowrap}
/* A badge/chip trailing a primary cell (e.g. "On hold" after a WO product) needs
   space from the name. */
.data-table .col-primary .badge,.data-table .col-primary .chip{margin-left:var(--space-2)}
/* A badge is taller than the text it sits beside, which made the rows carrying
   one 4px taller than the rest — enough to break the rhythm of a short table.
   Cap its line box so every row is the same height. */
.data-table td .badge,.data-table td .chip{padding-block:0;line-height:1.45}
.table-wrap{background:var(--bg-page);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);
  overflow:auto}  /* horizontal scroll instead of cramming/wrapping */

/* ---------- work orders: view toggle + kanban board (moved from an inline
   <style> block — the prod CSP blocks inline styles, which left this unstyled) ---------- */
.view-toggle{display:inline-flex;border:1px solid var(--border-strong);border-radius:var(--radius-md);overflow:hidden;padding:0}
.view-toggle button{padding:var(--space-2) var(--space-4);font-size:var(--text-sm);font-weight:600;
  background:var(--bg-page);color:var(--text-secondary);border:0;cursor:pointer;display:inline-flex;align-items:center;gap:var(--space-2)}
.view-toggle button[aria-pressed="true"]{background:var(--color-primary);color:var(--color-on-primary)}
.board{display:grid;gap:var(--space-4);grid-template-columns:1fr}
@media (min-width:1024px){.board{grid-template-columns:repeat(4,1fr)}}
.board-col{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);padding:var(--space-3)}
.board-col-head{display:flex;align-items:center;justify-content:space-between;font-weight:650;font-size:var(--text-sm);
  padding:var(--space-2) var(--space-1) var(--space-3)}
.board-card{background:var(--bg-page);border:1px solid var(--border-subtle);border-radius:var(--radius-md);
  box-shadow:var(--shadow-sm);padding:var(--space-3);margin-bottom:var(--space-3);display:grid;gap:var(--space-2)}
.board-card:last-child{margin-bottom:0}
.board-card .row-link{font-weight:600}
.board-card-meta{display:flex;flex-wrap:wrap;gap:var(--space-2);align-items:center}

/* ---------- filter bar ---------- */
/* Always one row: search flexes to absorb/yield space, selects + buttons keep
   their size; if the viewport is genuinely too narrow, the bar scrolls
   horizontally rather than wrapping to a second line. */
.filter-bar{display:flex;flex-wrap:nowrap;gap:var(--space-3);align-items:center;padding:var(--space-4) 0;
  overflow-x:auto}
.filter-bar > select,.filter-bar > .btn-secondary,.filter-bar > .btn-text,
.filter-bar > .btn-primary,.filter-bar > label,.filter-bar > .filter-bar-end{flex:0 0 auto}
.filter-bar input[type=search],.filter-bar select,.input,
.form-inline-grid input,.form-inline-grid select{padding:var(--space-3);border:1px solid var(--border-strong);
  border-radius:var(--radius-sm);font:inherit;background:var(--bg-page);color:var(--text-primary);min-height:var(--control-min)}
.filter-bar input[type=search]{flex:1 1 auto;min-width:140px}
/* One-line truncating table cell — full text on hover via title= (e.g. Reason). */
.cell-truncate{max-width:22ch;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@media(max-width:600px){.data-table--list .cell-truncate{max-width:none;white-space:normal}}

/* ---------- field list / forms ---------- */
.field-list{display:grid;gap:var(--space-1);background:var(--bg-page);border:1px solid var(--border-subtle);
  border-radius:var(--radius-lg);padding:var(--space-2) var(--space-5)}
.field-row{display:grid;grid-template-columns:1fr;gap:var(--space-2);padding:var(--space-3) 0;
  border-bottom:1px solid var(--border-subtle);align-items:center}
.field-row:last-child{border-bottom:0}

/* Two columns once there is room. A record page is a reference surface — you
   scan it for one value — and a single tall column pushes half the fields below
   the fold. Rows keep their own separator, so the pair still reads as a list
   rather than a grid of boxes. Single column below 900px, where two columns
   would just wrap awkwardly. */
/* `body.editing .edit-only{display:block}` is what reveals the edit form, and it
   OVERRIDES `.field-list{display:grid}` — so an edit-mode field list had a
   grid-template-columns that did nothing, silently collapsing to one column
   while read mode sat in two. Restore grid for field lists specifically. */
body.editing .edit-only.field-list{display:grid}

/* Read and edit rows must be the SAME HEIGHT or the layout creeps downward as
   you go: an <input> carries a 38px min-height that plain text does not, so a
   14-row list drifted ~28px by the bottom even with both modes on one grid.
   Matching the control height in read mode holds every row in place. */
.field-list--split > .field-row{min-height:var(--control-min,38px)}
.field-list--split > .field-row > .field-val{display:flex;align-items:center;min-height:var(--control-min,38px);flex-wrap:wrap;gap:var(--space-2)}

@media (min-width:900px){
  .field-list--split{grid-template-columns:1fr 1fr;column-gap:var(--space-6)}
  /* The last row in an odd-length column would otherwise keep a border with
     nothing under it. */
  .field-list--split > .field-row:nth-last-child(-n+2){border-bottom:0}
  /* Each column is half the width it was, so the global 200px label track
     (set at 640px for a full-width list) would squeeze the control. A narrower
     label track gives the input room without the label wrapping. */
  .field-list--split > .field-row{grid-template-columns:150px minmax(0,1fr);gap:var(--space-3)}
}
@media (min-width:1280px){
  .field-list--split > .field-row{grid-template-columns:180px minmax(0,1fr)}
}
.field-row dt,.field-row label,.field-row .field-key{color:var(--text-secondary);font-size:var(--text-sm);font-weight:500;margin:0}
.field-row dd,.field-row .field-val{margin:0}
/* A badge/chip annotating a field label (e.g. "Current cost [execution view]")
   needs breathing room from the label text. */
.field-key .badge,.field-key .chip{margin-left:var(--space-2)}
.field-row input,.field-row select,.field-row textarea{width:100%;padding:var(--space-2) var(--space-3);min-height:38px;
  border:1px solid var(--border-strong);border-radius:var(--radius-sm);font:inherit;background:var(--bg-page)}
/* Radios/checkboxes are not text fields — opt them out of the full-width box sizing
   above (otherwise they render as big bordered circles). */
.field-row input[type="radio"],.field-row input[type="checkbox"]{width:16px;height:16px;min-height:0;
  padding:0;border-width:1px;border-radius:0;flex:0 0 auto;vertical-align:middle}
.field-row label.cluster{display:flex;align-items:center;gap:var(--space-2)}
.field-row textarea{min-height:auto}
/* Buttons can be start-aligned instead of the right-aligned default. */
.form-actions--start{justify-content:flex-start}
/* Edit mode packs inputs, so tighten the row rhythm vs the read-mode <dl>. */
body.editing .field-list .field-row{padding-top:var(--space-2);padding-bottom:var(--space-2)}
.field-row textarea{min-height:calc(var(--control-min) * 1.6)}
.form-actions{display:flex;justify-content:flex-end;gap:var(--space-3);margin-top:var(--space-5);flex-wrap:wrap}
.req{color:var(--color-danger)}

/* ---------- settings cluster ---------- */
.settings-cluster{padding:var(--space-5);background:var(--bg-page);border:1px solid var(--border-subtle);
  border-radius:var(--radius-md)}
.settings-cluster-title{margin:0 0 var(--space-4);font-size:var(--text-sm);text-transform:uppercase;
  letter-spacing:.05em;color:var(--text-secondary)}
.settings-rows{display:grid;gap:var(--space-3);margin:0}
.settings-row{display:grid;grid-template-columns:120px 1fr;gap:var(--space-3);align-items:center}
.settings-row dt,.settings-row .field-key{color:var(--text-secondary);font-size:var(--text-sm);margin:0}
.settings-row dd,.settings-row .field-val{margin:0}

/* ---------- tabs ---------- */
.tabs{display:flex;gap:var(--space-1);border-bottom:1px solid var(--border-subtle);margin:var(--space-5) 0 var(--space-4);
  overflow-x:auto;-webkit-overflow-scrolling:touch}
/* Tabs that belong to a record header are that header's own control row — the
   default standalone top margin opens a hole under the SKU/type chips. */
.page-header + .tabs{margin-top:var(--space-2)}
.tab{padding:var(--space-3) var(--space-4);font-size:var(--text-sm);font-weight:600;color:var(--text-secondary);
  border-bottom:2px solid transparent;cursor:pointer;background:none;border-top:0;border-left:0;border-right:0;white-space:nowrap}
.tab.active{color:var(--color-primary);border-bottom-color:var(--color-primary)}
a.tab{text-decoration:none}a.tab:hover{color:var(--color-primary)}
/* Hub sub-nav tabs sit directly under the action bar: drop the large top margin
   so there isn't a big gap between the page title bar and the tabs. */
.content>.tabs:first-child{margin-top:0}
.tab-panel{display:none} .tab-panel.active{display:block}

/* ---------- empty state ---------- */
.empty-state{display:grid;place-items:center;text-align:center;gap:var(--space-3);padding:var(--space-8) var(--space-5);
  background:var(--bg-page);border:1px dashed var(--border-strong);border-radius:var(--radius-lg)}
.empty-state .ti{font-size:40px;color:var(--text-muted)}

/* ---------- misc ---------- */
.stack{display:grid;gap:var(--space-4)}
.cluster{display:flex;gap:var(--space-3);align-items:center;flex-wrap:wrap}
.between{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3) var(--space-4);flex-wrap:wrap}
.muted{color:var(--text-secondary)} .small{font-size:var(--text-sm)}
.mono{font-family:var(--font-mono)}
/* small utilities to keep templates free of inline style= (Aslan convention) */
.fieldset-reset{border:0;padding:0;margin:0}
.w-input-sm{max-width:160px}
/* A short numeric field (day counts, quantities) does not need .w-input-sm's
   room for a decimal rate — Production defaults' auto-archive days field. */
.w-input-xs{max-width:90px}
/* A field whose whole value has to be readable at once — an invite URL a human
   is about to hand over. Wide, and it still shrinks on a phone. */
.w-input-url{width:min(46ch,100%)}

/* ---------- compact field grid (label + control + hint, stacked; several
   across a card via .grid.grid-N) ---------- */
/* .field-row is NOT reusable here: its >=640px rule (grid-template-columns:
   200px 1fr) turns every row into a label-left/value-right pair, which fights
   a field meant to stack top-to-bottom inside one grid-N cell. */
.field-compact{display:grid;gap:var(--space-2);align-content:start}
.field-compact-label{color:var(--text-secondary);font-size:var(--text-sm);font-weight:500}
.field-compact input{padding:var(--space-2) var(--space-3);min-height:var(--control-min);
  border:1px solid var(--border-strong);border-radius:var(--radius-sm);font:inherit;background:var(--bg-page)}
/* Compact select for action-bar controls (e.g. dashboard Period) — tighter than
   the form .input default so it doesn't tower over the page title. */
.input--sm{padding:var(--space-2) var(--space-3);max-width:170px;font-size:var(--text-sm)}
.table-narrow{min-width:340px;font-size:var(--text-sm)}
.btn-reset{width:100%;text-align:left;border:0;background:none;cursor:pointer}
.list-tight{margin:var(--space-2) 0 0 1.1rem;display:grid;gap:var(--space-1)}
.mb-5{margin-bottom:var(--space-5)}
/* progress fill width comes from a CSP-safe .pct-N utility class on the span */
.callout{display:flex;gap:var(--space-3);padding:var(--space-4);border-radius:var(--radius-md);
  background:var(--warning-1);border:1px solid var(--warning-3);color:var(--warning-11);font-size:var(--text-sm)}
.callout--info{background:var(--info-1);border-color:var(--info-3);color:var(--info-11)}
.callout--success{background:var(--success-1);border-color:var(--success-3);color:var(--success-11)}
.callout--danger{background:var(--danger-1);border-color:var(--danger-3);color:var(--danger-11)}
.callout--warning{background:var(--warning-1);border-color:var(--warning-3);color:var(--warning-11)}
.callout .ti{flex:0 0 auto;margin-top:2px}
.progress{height:8px;background:var(--gray-2);border-radius:var(--radius-pill);overflow:hidden;min-width:80px}
.progress > span{display:block;height:100%;background:var(--color-primary);border-radius:var(--radius-pill)}
/* CSP-safe progress fills: pick the nearest .pct-N (5% steps) server-side instead
   of an inline style="--pct" (the deployed CSP blocks inline style attributes). */
.pct-0{width:0} .pct-5{width:5%} .pct-10{width:10%} .pct-15{width:15%} .pct-20{width:20%}
.pct-25{width:25%} .pct-30{width:30%} .pct-35{width:35%} .pct-40{width:40%} .pct-45{width:45%}
.pct-50{width:50%} .pct-55{width:55%} .pct-60{width:60%} .pct-65{width:65%} .pct-70{width:70%}
.pct-75{width:75%} .pct-80{width:80%} .pct-85{width:85%} .pct-90{width:90%} .pct-95{width:95%} .pct-100{width:100%}

/* spacing + list utilities (avoid inline style) */
.plain-list{list-style:none;margin:0;padding:0}
.mt-3{margin-top:var(--space-3)} .mt-4{margin-top:var(--space-4)} .mt-5{margin-top:var(--space-5)}
.mb-0{margin-bottom:0} .mb-4{margin-bottom:var(--space-4)} .mb-5{margin-bottom:var(--space-5)} .mb-6{margin-bottom:var(--space-6)}

/* mockup banner (identifies these as design mockups, not the live app) */
.mockup-flag{position:fixed;bottom:0;right:0;z-index:50;background:var(--brand-11);color:var(--white);
  font-size:var(--text-xs);padding:.3rem .7rem;border-top-left-radius:var(--radius-md);opacity:.85}

/* ---------- a11y + small chrome ---------- */
.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
/* org switcher rendered as a CSRF POST button — reset native button chrome to match the old anchor */
.org-option-form{display:block;width:100%;margin:0}
button.org-option{width:100%;border:0;background:none;font:inherit;text-align:left;cursor:pointer}
/* Segmented Table/Board view toggle (ported from work-orders.html mockup — was page-scoped) */
.view-toggle{display:inline-flex;border:1px solid var(--border-strong);border-radius:var(--radius-md);overflow:hidden}
.view-toggle button{padding:var(--space-2) var(--space-4);font-size:var(--text-sm);font-weight:600;background:var(--bg-page);color:var(--text-secondary);border:0;cursor:pointer;display:inline-flex;align-items:center;gap:var(--space-2)}
.view-toggle button[aria-pressed="true"]{background:var(--color-primary);color:var(--color-on-primary)}
.brand-sub{font-size:var(--text-sm);font-weight:500;color:var(--brand-2);opacity:.7}
.row-link{color:inherit;font-weight:600}
.row-link:hover{color:var(--color-primary)}
/* A row whose click opens a modal instead of navigating uses the same overlay,
   so it needs a button that reads as the row's text, not as a control. */
button.row-link{padding:0;border:0;background:none;font:inherit;font-weight:600;
  text-align:left;cursor:pointer;color:inherit}
/* Whole-row click → detail (frontend-data-tables): the primary link's ::before
   overlays the row so a click anywhere navigates, while the <a> stays the real
   focusable/accessible element. Secondary links/buttons in the row use .cell-link
   to sit above the overlay. */
.data-table tbody tr{position:relative}
.data-table tbody tr:hover{background:var(--bg-surface);cursor:pointer}
.row-link--full::before{content:"";position:absolute;inset:0}
.data-table td .cell-link{position:relative;z-index:1}
/* Bulk-select column — hidden until the table enters .selecting mode. The
   checkbox sits above the row-link overlay (z-index) so it stays clickable. */
.col-select{width:1%;white-space:nowrap}
.data-table--list .col-select{display:none}
.data-table--list.selecting .col-select{display:table-cell}
.col-select input{position:relative;z-index:2;width:18px;height:18px;cursor:pointer}
.col-actions .inline-form{position:relative;z-index:2}
/* Bulk action bar — sticks to the bottom of the viewport while selecting, within
   the content column (so it never overlaps the sidebar). */
.bulk-bar{position:sticky;bottom:0;z-index:25;display:flex;align-items:center;justify-content:space-between;
  gap:var(--space-3);margin-top:var(--space-3);padding:var(--space-3) var(--space-4);
  background:var(--bg-page);border:1px solid var(--brand-3);border-radius:var(--radius-md);
  box-shadow:0 -6px 18px -10px rgba(15,23,42,.35)}
.bulk-bar[hidden]{display:none}
.bulk-count{font-weight:600}
.filter-bar-end{margin-left:auto}

/* Click-to-sort headers */
.data-table--list thead th.th-sort{cursor:pointer;user-select:none}
.data-table--list thead th.th-sort:hover{color:var(--text-primary)}
/* Keep the header's own color; only the caret is brand-colored to show sort. */
.data-table--list thead th[aria-sort]::after{content:"";display:inline-block;margin-left:.4em;vertical-align:middle;
  border-left:4px solid transparent;border-right:4px solid transparent}
.data-table--list thead th[aria-sort=ascending]::after{border-bottom:5px solid var(--color-primary)}
.data-table--list thead th[aria-sort=descending]::after{border-top:5px solid var(--color-primary)}
/* Row-action buttons (e.g. approve ✓ / reject ✗) stay on one line in the cell. */
.col-actions .cluster{flex-wrap:nowrap;gap:var(--space-2);justify-content:flex-end}
.col-actions .cluster .inline-form{display:inline-flex}
.kebab{display:inline-grid;place-items:center;width:32px;height:32px;border:0;background:none;
  border-radius:var(--radius-sm);cursor:pointer;color:var(--text-secondary)}
.kebab:hover{background:var(--bg-surface)}

/* edit-mode toggle (Detail <-> Edit) */
.edit-only{display:none}
body.editing .view-only{display:none}
body.editing .edit-only{display:block}
body.editing .edit-only.inline{display:inline-flex}
/* Same fix as .edit-only.field-list above (mockup.css:585): the generic
   display:block here would collapse a 3-across .grid-N (e.g. Production
   defaults' edit-mode fields) to one column, while the read-mode .grid-N sat
   in three — the exact reflow-on-Edit this toggle exists to prevent. */
body.editing .edit-only.grid{display:grid}

/* form-actions: cancel/secondary far left, primary right */
.form-actions .btn-text,.form-actions .btn-secondary{margin-right:auto}

/* demo credentials (security-demo-credentials) */
.demo-creds{margin-bottom:var(--space-5);padding:var(--space-4);background:var(--warning-1);
  border:1px solid var(--warning-3);border-radius:var(--radius-md)}
.demo-creds-warning{margin:0 0 var(--space-3);font-size:var(--text-sm);color:var(--warning-11)}
.demo-creds-row{display:grid;grid-template-columns:60px 1fr 1fr;gap:var(--space-2);align-items:center;
  margin-bottom:var(--space-2)}
.demo-creds-role{font-weight:600;font-size:var(--text-sm)}
.demo-creds-copy{position:relative;display:inline-flex;align-items:center;gap:var(--space-2);
  padding:var(--space-2) var(--space-3);font-family:var(--font-mono);font-size:var(--text-sm);
  background:var(--bg-page);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);cursor:pointer}
.demo-creds-copy:hover{border-color:var(--color-primary)}
.demo-creds-feedback{position:absolute;inset:0;display:none;align-items:center;justify-content:center;
  background:var(--color-success);color:var(--white);border-radius:var(--radius-sm);font-weight:600}
.demo-creds-copy.is-copied .demo-creds-feedback{display:flex}
/* app.js sets .is-copied for 1.2s on any [data-copy] control — without a visible
   change the click is indistinguishable from a dead button. */
[data-copy].is-copied{color:var(--success-11);border-color:var(--success-9)}

/* toast region — polite (status) + assertive (alert) ; bottom-center mobile, bottom-right desktop */
.toast-region{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:60;display:grid;gap:var(--space-2);
  width:min(92vw,360px)}
.toast{background:rgba(255,255,255,.88);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  color:var(--text-primary);padding:var(--space-3) var(--space-4);border-radius:var(--radius-md);
  font-size:var(--text-sm);box-shadow:var(--shadow-lg);border:1px solid var(--border-subtle);
  border-left:4px solid var(--brand-9)}
.toast--error{border-left-color:var(--danger-9)}
.toast--success{border-left-color:var(--success-9)}
.toast--warning{border-left-color:var(--warning-9)}

/* ---------- Modal (native <dialog>, centered) — reusable shell ---------- */
dialog.modal{border:0;border-radius:var(--radius-lg);padding:0;width:min(92vw,520px);
  background:var(--bg-page);color:var(--text-primary);box-shadow:var(--shadow-lg)}
dialog.modal::backdrop{background:rgba(15,23,42,.45)}
.modal-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);
  padding:var(--space-4) var(--space-5);border-bottom:1px solid var(--border-subtle)}
.modal-title{font-size:var(--text-lg);font-weight:650;margin:0}
.modal-close{background:none;border:0;cursor:pointer;color:var(--text-secondary);display:grid;place-items:center;
  width:32px;height:32px;border-radius:var(--radius-sm)}
.modal-close:hover{background:var(--bg-surface);color:var(--text-primary)}
/* A tall modal (Plan downtime carries seven fields) must not push its own
   actions off the bottom of the viewport: the body scrolls, the header and the
   action bar stay put. Capping the dialog at 85vh is what gives the body
   something to scroll within — without it there is no overflow to contain and
   position:sticky has nothing to stick to.

   [open] IS LOAD-BEARING, not decoration. A <dialog>'s hidden state is the UA
   rule `dialog:not([open]){display:none}`, so ANY unconditional `display` on
   the dialog itself outranks it and the thing can never hide: close(), the X,
   Cancel and Escape all still remove [open] and nothing happens on screen —
   the modal simply stays up, and survives a page change because it was never
   closed, it was always drawn. Written as `.modal{display:flex}` this broke
   every centered modal in the app. Same rule as mockup.css:335 and the reason
   kiosk.css:256 / scheduling.css:314 both gate on [open]. */
dialog.modal[open]{display:flex;flex-direction:column;max-height:85vh}
.modal-head{position:sticky;top:0;z-index:1;background:var(--bg-elevated,var(--white))}
.modal-body{padding:var(--space-5);overflow-y:auto;flex:1 1 auto;min-height:0}
.modal-actions{display:flex;justify-content:flex-end;gap:var(--space-3);
  padding:var(--space-4) var(--space-5);border-top:1px solid var(--border-subtle);
  position:sticky;bottom:0;z-index:1;background:var(--bg-elevated,var(--white))}

/* Custom field validation (replaces the browser's native bubble) */
.field-error{display:flex;align-items:center;gap:6px;margin:var(--space-2) 0 0;
  color:var(--danger-9);font-size:var(--text-sm)}
.field-error::before{content:"";flex:0 0 auto;width:14px;height:14px;border-radius:50%;
  background:var(--danger-9);
  -webkit-mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='white'><path d='M12 2 1 21h22zM12 16a1 1 0 100 2 1 1 0 000-2zm-1-6h2v5h-2z'/></svg>") center/contain no-repeat;
  mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='white'><path d='M12 2 1 21h22zM12 16a1 1 0 100 2 1 1 0 000-2zm-1-6h2v5h-2z'/></svg>") center/contain no-repeat}
input.is-invalid,select.is-invalid,textarea.is-invalid,.is-invalid{border-color:var(--danger-9)!important}
input.is-invalid:focus,select.is-invalid:focus,textarea.is-invalid:focus{outline-color:var(--danger-9)}

/* Confirm dialog (replaces window.confirm) — narrower than a form modal. */
.modal--confirm{width:min(92vw,420px)}
.modal--confirm .modal-body{padding-bottom:var(--space-4)}
.modal--confirm [data-confirm-msg]{margin:0;font-size:var(--text-base)}
.bom-subassembly summary{cursor:pointer;font-weight:600;list-style-position:inside}
.bom-subassembly ul{margin:var(--space-2) 0 0 var(--space-5);padding:0;color:var(--text-secondary)}

/* ---------- Overflow action menu (reusable dropdown of actions) ---------- */
.menu-wrap{position:relative}
.menu-pop{position:absolute;right:0;top:calc(100% + var(--space-2));z-index:30;min-width:210px;
  background:var(--bg-page);border:1px solid var(--border-subtle);border-radius:var(--radius-md);
  box-shadow:var(--shadow-lg);padding:var(--space-2);display:grid;gap:2px}
.menu-pop[hidden]{display:none}
.menu-pop form{margin:0}
.menu-item{display:flex;align-items:center;gap:var(--space-2);width:100%;text-align:left;background:none;
  border:0;padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);cursor:pointer;
  color:var(--text-primary);font:inherit;font-size:var(--text-sm)}
.menu-item:hover{background:var(--bg-surface)}
.menu-item.is-danger{color:var(--danger-9)}
.menu-label{margin:0 0 2px;padding:2px var(--space-3);font-size:var(--text-xs);
  text-transform:uppercase;letter-spacing:.04em;color:var(--text-secondary)}

/* Status badge that opens a "move to…" menu of valid transitions */
.status-menu{display:inline-block}
.status-trigger{background:none;border:0;padding:0;cursor:pointer;display:inline-flex;align-items:center;
  gap:4px;font:inherit;position:relative;z-index:2}
.status-trigger .ti-chevron-down{color:var(--text-secondary);font-size:14px}
.status-menu .menu-pop{min-width:180px;left:0;right:auto}

/* ---------- Styled select control (custom chevron, not browser-default) ---------- */
.input,.field-row select,.form-inline-grid select,.filter-bar select,.modal-body select{
  appearance:none;-webkit-appearance:none}
select.input,.field-row select,.form-inline-grid select,.filter-bar select,.modal-body select{
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat:no-repeat;background-position:right .7rem center;padding-right:2.2rem}

/* login layout */
.auth-wrap{min-height:100vh;display:grid;place-items:center;background:var(--bg-surface);padding:var(--space-5)}
.auth-card{position:relative;width:100%;max-width:400px;background:var(--bg-page);
  border:1px solid var(--border-strong);border-radius:0;padding:var(--space-6)}
/* Corner ticks — the marketing site's blueprint "frame" detail. */
.auth-card::before,.auth-card::after{content:"";position:absolute;width:11px;height:11px;
  border:2px solid var(--color-primary)}
.auth-card::before{left:-1px;top:-1px;border-right:none;border-bottom:none}
.auth-card::after{right:-1px;bottom:-1px;border-left:none;border-top:none}
.auth-brand{display:flex;align-items:center;gap:var(--space-3);font-weight:700;font-size:var(--text-lg);
  margin-bottom:var(--space-5)}
.auth-brand .wordmark{height:30px;width:auto}
.auth-brand .brand-sub{color:var(--text-muted)}
.form-group{margin-bottom:var(--space-4)}
.form-label{display:block;font-weight:500;margin-bottom:var(--space-2);font-size:var(--text-sm)}
.form-control{width:100%;min-height:var(--control-min);padding:var(--space-2) var(--space-3);font:inherit;
  border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-page)}
.form-control:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(20,40,63,.08)}
.btn-block{width:100%;justify-content:center}

/* ---------- Phase 1: account-lifecycle + flash chrome (tokens-only) ---------- */
/* flash message strip (post-redirect feedback) */
.flash-stack{display:grid;gap:var(--space-3);margin-bottom:var(--space-4)}
.auth-card .flash-stack{margin-bottom:var(--space-5)}
/* auth-card helper text/blocks lifted out of the mockups' inline <style> */
.auth-card--wide{max-width:460px}
.auth-head{margin-bottom:var(--space-5)}
.auth-head h1{font-size:var(--text-xl);margin-bottom:var(--space-2)}
.auth-foot{margin-top:var(--space-5);text-align:center;color:var(--text-secondary);font-size:var(--text-sm)}
.auth-note{margin-top:var(--space-4);padding:var(--space-3);border:1px solid var(--border-subtle);
  border-radius:var(--radius-md);background:var(--bg-surface);color:var(--text-secondary);
  font-size:var(--text-sm);display:flex;gap:var(--space-2)}
.auth-note .ti{color:var(--text-secondary);flex:0 0 auto}
.auth-email{font-weight:600;color:var(--text-primary)}
.verify-icon{font-size:40px;color:var(--color-primary);margin-bottom:var(--space-3)}
.steps-hint{display:flex;gap:var(--space-2);align-items:center;color:var(--text-secondary);
  font-size:var(--text-sm);margin-bottom:var(--space-5)}
.steps-hint .ti{color:var(--color-success)}
/* invite summary card (accept-invite) */
.invite-summary{display:flex;gap:var(--space-3);align-items:flex-start;padding:var(--space-4);
  border:1px solid var(--border-subtle);border-radius:var(--radius-md);background:var(--bg-surface);
  margin-bottom:var(--space-5)}
.invite-summary .org-mark{width:38px;height:38px;border-radius:var(--radius-md);background:var(--brand-11);
  color:var(--white);display:grid;place-items:center;font-weight:700;flex:0 0 auto}
.invite-summary-text{font-size:var(--text-sm);color:var(--text-secondary)}
.invite-summary-text .org-name{display:block;font-size:var(--text-base);font-weight:600;
  color:var(--text-primary);margin-bottom:var(--space-1)}
.invite-role{margin-top:var(--space-2)}
.form-control[readonly]{background:var(--bg-surface);color:var(--text-secondary)}
/* settings: account + members one-offs */
.account-form{max-width:480px}
/* The roster paste is tabular data, so it needs the width the invite form does
   not: a textarea left at its default cols=20 renders ~205px, which is narrower
   than a single line of `name, email, role, pin, rate`. Monospace so the
   columns line up while somebody is checking their paste. */
.roster-form{max-width:52rem}
.roster-form textarea{width:100%;font-family:var(--font-mono);
  font-size:var(--text-sm);line-height:1.5;resize:vertical;white-space:pre;overflow-x:auto}
.roster-form input[type=file]{width:100%}
.field-hint{margin-top:var(--space-2)}
.role-grid{display:grid;grid-template-columns:1fr;gap:var(--space-4);margin-top:var(--space-4)}
.role-card{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-md);
  padding:var(--space-4)}
.role-card h3{font-size:var(--text-sm);display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-3)}
.role-card ul{margin:0;padding-left:var(--space-5);font-size:var(--text-sm);color:var(--text-secondary)}
.role-card li{margin-bottom:var(--space-1)}
.role-select,.status-select{padding:var(--space-2) var(--space-3);border:1px solid var(--border-strong);
  border-radius:var(--radius-sm);font:inherit;background:var(--bg-page);color:var(--text-primary);min-height:var(--control-min)}
.rate-cell{white-space:nowrap}
.inline-form{display:inline}
/* no-active-workspace state */
.no-workspace{max-width:520px;margin:var(--space-8) auto;text-align:center}
.no-workspace .ti{font-size:48px;color:var(--text-muted);margin-bottom:var(--space-3)}
.form-actions--center{justify-content:center}
@media (min-width:640px){ .role-grid{grid-template-columns:repeat(2,1fr)} }
@media (min-width:1024px){ .role-grid{grid-template-columns:repeat(4,1fr)} }

/* kiosk (full-screen shop-floor terminal — larger touch targets) */
.kiosk{min-height:100vh;background:var(--brand-11);color:var(--white);display:flex;flex-direction:column}
.kiosk-bar{display:flex;align-items:center;justify-content:space-between;padding:var(--space-5) var(--space-6);
  border-bottom:1px solid rgba(255,255,255,.12)}
.kiosk-body{flex:1;display:grid;place-items:center;padding:var(--space-6)}
.kiosk-card{width:100%;max-width:520px;background:var(--white);color:var(--text-primary);border-radius:var(--radius-lg);
  padding:var(--space-6);box-shadow:var(--shadow-lg)}
.btn-xl{padding:var(--space-5) var(--space-6);font-size:var(--text-lg);min-height:64px}

/* ===========================================================
   Responsive — mobile-first, min-width only (frontend-responsive).
   Base styles above target the smallest screen. Each query adds
   capability as the viewport grows. Layer-4 chrome only.
   =========================================================== */

/* list-table card stack is the BASE (mobile); un-stacked at >=601px below */
.col-actions{width:1%;white-space:nowrap}
.data-table--list,.data-table--list tbody,.data-table--list tr,.data-table--list td{display:grid}
.data-table--list thead{display:none}
.data-table--list tbody{gap:var(--space-4)}
.data-table--list .table-wrap,.table-wrap:has(.data-table--list){overflow:visible}
/* one card per row */
.data-table--list tr{border:1px solid var(--border-subtle);border-radius:var(--radius-lg);
  padding:var(--space-4) var(--space-5);background:var(--bg-page);box-shadow:var(--shadow-sm);gap:0}
/* label-left / value-right rows with hairline dividers */
.data-table--list td{grid-template-columns:1fr auto;gap:var(--space-4);align-items:center;
  min-height:38px;padding:var(--space-2) 0;border:0;border-bottom:1px solid var(--border-subtle);text-align:right}
.data-table--list td:last-child{border-bottom:0}
.data-table--list td::before{content:attr(data-label);justify-self:start;text-align:left;
  color:var(--text-secondary);font-size:var(--text-sm);font-weight:500}
.data-table--list td[data-label=""]::before,.data-table--list td:not([data-label])::before{display:none}
.data-table--list td[data-label=""],.data-table--list td:not([data-label]){grid-template-columns:1fr}
.data-table--list td .progress{min-width:120px;margin-left:auto}
/* headline: pulled to the top of the card, full width, no label */
.data-table--list .col-primary{order:-1;grid-template-columns:1fr;text-align:left;
  font-size:var(--text-lg);font-weight:600;min-height:0;
  padding:0 0 var(--space-3);margin-bottom:var(--space-2);border-bottom:1px solid var(--border-subtle)}
.data-table--list .col-primary::before{display:none}
/* actions: footer button row, full-width buttons */
.data-table--list .col-actions{order:99;display:flex;gap:var(--space-2);text-align:left;
  padding-top:var(--space-3);margin-top:var(--space-2);border-bottom:0}
.data-table--list .col-actions::before{display:none}
.data-table--list .col-actions>.btn-primary,
.data-table--list .col-actions>.btn-secondary,
.data-table--list .col-actions>.btn-danger,
.data-table--list .col-actions>.btn-text{flex:1;justify-content:center}
.data-table--list .col-actions>.kebab{flex:0 0 44px}

/* >=480px (large phone): KPI/4-col tiles go 2-up */
@media (min-width:480px){
  .kpi-row,.grid-4{grid-template-columns:repeat(2,1fr)}
}

/* >=601px: list tables return to normal table layout; sticky action column */
@media (min-width:601px){
  .data-table--list,.data-table--list tbody,.data-table--list tr,.data-table--list td{display:revert}
  .data-table--list thead{display:revert}
  .data-table--list .table-wrap,.table-wrap:has(.data-table--list){overflow:auto}
  .data-table--list tr{border:0;border-bottom:1px solid var(--border-subtle);border-radius:0;
    padding:0;background:none;box-shadow:none}
  .data-table--list td{gap:0;min-height:0;padding:var(--space-3) var(--space-4);
    border:0;border-bottom:1px solid var(--border-subtle);text-align:left}
  .data-table--list td::before{display:none}
  .data-table--list .col-primary{order:0;font-size:var(--text-sm);font-weight:500;margin:0;padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--border-subtle)}
  .data-table--list .col-actions{order:0}
  .data-table--list .col-actions>.btn-primary,
  .data-table--list .col-actions>.btn-secondary,
  .data-table--list .col-actions>.btn-danger,
  .data-table--list .col-actions>.btn-text{flex:0 0 auto}
  /* keep the action column reachable on horizontal scroll (frontend-data-tables) */
  .data-table .col-actions{position:sticky;right:0;background:var(--bg-page)}
  .data-table tbody tr:hover .col-actions{background:var(--brand-1)}
}

/* >=640px (sm): two-up content grids, label/value field rows, roomier padding, toast bottom-right */
@media (min-width:640px){
  .grid-2{grid-template-columns:repeat(2,1fr)}
  .field-row{grid-template-columns:200px 1fr;gap:var(--space-4)}
  .content{padding:var(--space-4) var(--space-6) var(--space-6)}
  .toast-region{left:auto;right:24px;transform:none}
}

/* >=768px (md / tablet): sidebar becomes a 64px icon rail with tooltips (frontend-responsive) */
@media (min-width:768px){
  .app{grid-template-columns:var(--sidebar-rail) 1fr}
  .sidebar{display:flex;position:sticky;left:0;width:var(--sidebar-rail);height:100vh;top:0;padding:var(--space-4) 0}
  /* desktop has the sidebar back — the mobile bottom bar + topbar brand are gone,
     and the desktop search + roomier topbar padding return */
  .bottom-nav{display:none}
  .main{padding-bottom:0}
  .topbar-brand{display:none}
  .topbar{padding:0 var(--space-6)}
  .topbar-icon-btn{display:inline-flex}
  .topbar-search{display:flex}
  /* A pointer doesn't need the 44px touch target — buttons and the bars that
     hold them come down to desk height. */
  :root{--btn-h:34px;--btn-h-sm:28px}
  .page-action-bar{padding:var(--space-2) var(--space-6)}
  /* The sidebar owns the account block from here up; the topbar copy is only
     re-shown when the sidebar is hidden (see the sidebar-hidden block below). */
  .topbar .account{display:none}
  .sidebar-brand{justify-content:center;padding:var(--space-2) 0 var(--space-5)}
  .sidebar-brand > span:not(.mark),.brand-sub,.sidebar-section,
  .nav-link > span,.org-switcher-text,.org-switcher-btn > .ti,
  .sidebar-foot,.account-id{display:none}
  .nav-link{justify-content:center;padding:var(--space-3) 0}
  .nav-link > .ti{font-size:20px}
  .org-switcher{padding:0 var(--space-2) var(--space-3)}
  .org-switcher-btn{justify-content:center;padding:var(--space-3) 0}
  /* the rail's account block stacks avatar over sign-out, both centred */
  .sidebar-account .account{flex-direction:column;gap:var(--space-1);padding:0 0 var(--space-2)}
  .sidebar-account .account-link{flex:0 0 auto;justify-content:center}
  /* hover tooltip for the icon-only rail (data-label injected by shell.js) */
  .nav-link[data-label]:hover::after{content:attr(data-label);position:absolute;left:calc(100% + var(--space-2));
    top:50%;transform:translateY(-50%);white-space:nowrap;background:var(--bg-inverse);color:var(--white);
    padding:var(--space-1) var(--space-3);border-radius:var(--radius-sm);font-size:var(--text-xs);
    box-shadow:var(--shadow-md);z-index:50;pointer-events:none}
}

/* Sidebar hidden (user toggle, remembered per browser): collapse the grid to a
   single column and drop the sidebar so the main area spans full width. The body
   class outranks the .app/.sidebar rules in the media queries above. */
body.sidebar-hidden .app{grid-template-columns:1fr}
body.sidebar-hidden .sidebar{display:none}
/* The sidebar took the collapse button and the account block down with it, so
   the topbar twins come back — otherwise there is no way to restore the
   sidebar, and no way to sign out. */
body.sidebar-hidden .topbar-icon-btn{display:inline-flex}
body.sidebar-hidden .topbar .account{display:flex}

/* >=1024px (lg): full 256px sidebar, multi-column grids */
@media (min-width:1024px){
  .app{grid-template-columns:var(--sidebar-w) 1fr}
  .sidebar{width:var(--sidebar-w)}
  .sidebar-brand{justify-content:flex-start;padding:var(--space-2) var(--space-5) var(--space-5)}
  .sidebar-brand > span:not(.mark),.brand-sub,.sidebar-section,
  .nav-link > span,.org-switcher-btn > .ti,.sidebar-foot{display:revert}
  .org-switcher-text,.account-id{display:flex} /* not display:revert — a span would revert to inline and lose the column stack */
  /* full sidebar: the collapse button fits beside the wordmark, so the topbar
     twin steps back (the sidebar-hidden rule above still re-shows it) */
  .sidebar-collapse{display:inline-flex}
  .topbar-icon-btn{display:none}
  .sidebar-account .account{flex-direction:row;gap:var(--space-2);padding:0 var(--space-4) var(--space-2)}
  .sidebar-account .account-link{flex:1 1 auto;justify-content:flex-start}
  .nav-link{justify-content:flex-start;padding:var(--space-3) var(--space-5)}
  .nav-link > .ti{font-size:18px}
  .nav-link[data-label]:hover::after{content:none}
  .org-switcher{padding:0 var(--space-4) var(--space-3)}
  .org-switcher-btn{justify-content:flex-start;padding:var(--space-3)}
  .kpi-row,.grid-4{grid-template-columns:repeat(4,1fr)}
  .grid-3{grid-template-columns:repeat(3,1fr)}
  /* A record page has no search, and by this width the sidebar holds everything
     else the app bar was holding — brand, workspace, account, collapse. What's
     left is one bell alone in 56px of white, so the bar goes and the record's
     own action bar becomes the top chrome.
     :not(.sidebar-hidden) is load-bearing: with the sidebar gone the app bar is
     the only chrome left, and hiding it would strand the user with no way to
     bring the sidebar back and no way to sign out. */
  body.no-search:not(.sidebar-hidden) .topbar{display:none}
  body.no-search:not(.sidebar-hidden) .page-action-bar{top:0}
}

/* ── Phase 5 — inventory / BOM / conflict-resolution components (tokens only) ── */
.form-inline-grid{display:flex;flex-wrap:wrap;gap:var(--space-3);align-items:flex-end;padding:var(--space-4)}
.form-inline-grid label{display:flex;flex-direction:column;gap:var(--space-1);font-size:var(--text-sm);color:var(--text-secondary)}
.form-inline-grid input,.form-inline-grid select{min-width:10rem}

.inline-approve{display:inline-block;position:relative}
.inline-approve > summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:var(--space-1)}
.inline-approve > summary::-webkit-details-marker{display:none}
.inline-approve[open] > summary{margin-bottom:var(--space-2)}
.inline-approve .stack-sm{position:absolute;z-index:20;background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:var(--radius-md);padding:var(--space-3);min-width:16rem;box-shadow:0 4px 16px rgba(0,0,0,.12)}
.stack-sm{display:flex;flex-direction:column;gap:var(--space-2)}

.diff-table .diff-side{width:30%}
.diff-table .diff-row-changed td{background:transparent}
.diff-pick{display:flex;gap:var(--space-3);align-items:center;font-size:var(--text-sm)}
.diff-pick label{display:flex;gap:var(--space-1);align-items:center;cursor:pointer}
.owner-tag{font-size:var(--text-xs)}

/* --- Phase 8: platform console + notifications (tokens only) --------------- */
.plat-tag{font-size:var(--text-xs);font-weight:600;color:var(--brand-2);opacity:.8;
  padding:0 var(--space-5) var(--space-3)}
.config-section{margin-bottom:var(--space-6)}
.impersonation-banner{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;
  padding:var(--space-3) var(--space-6);background:var(--warning-1);color:var(--warning-11);
  border-bottom:1px solid var(--warning-3);font-size:var(--text-sm)}
.impersonation-banner .spacer,.inline-form.spacer{margin-left:auto}
/* dismissible/static verify-email banner (soft gate) */
.verify-banner{display:flex;gap:var(--space-3);align-items:flex-start;padding:var(--space-3) var(--space-4);
  border:1px solid var(--warning-3);background:var(--warning-1);color:var(--warning-11);
  border-radius:var(--radius-md);font-size:var(--text-sm);margin-bottom:var(--space-4)}
.verify-banner .ti-mail-exclamation{flex:0 0 auto;font-size:18px}
.verify-banner-body{flex:1}
.verify-banner-body strong{display:block;margin-bottom:var(--space-1);color:var(--text-primary)}
/* notifications bell + needs-attention menu */
.notif{position:relative}
.notif-btn{position:relative;display:inline-grid;place-items:center;width:38px;height:38px;
  border:1px solid var(--border-subtle);border-radius:var(--radius-md);background:var(--bg-page);
  color:var(--text-secondary);cursor:pointer}
.notif-btn:hover{background:var(--bg-surface)}
.notif-dot{position:absolute;top:6px;right:7px;min-width:16px;height:16px;padding:0 4px;border-radius:var(--radius-pill);
  background:var(--danger-11);color:var(--white);font-size:10px;font-weight:600;display:grid;place-items:center}
.notif-menu{position:absolute;right:0;top:calc(100% + var(--space-2));z-index:30;width:340px;max-width:90vw;
  background:var(--bg-page);border:1px solid var(--border-subtle);border-radius:var(--radius-md);
  box-shadow:var(--shadow-md);overflow:hidden}
.notif-menu[hidden]{display:none}
.notif-menu-head{display:flex;justify-content:space-between;align-items:center;padding:var(--space-3) var(--space-4);
  border-bottom:1px solid var(--border-subtle);font-weight:600;font-size:var(--text-sm)}
.notif-empty{padding:var(--space-4);color:var(--text-muted);font-size:var(--text-sm)}
.notif-item{display:flex;gap:var(--space-3);align-items:flex-start;padding:var(--space-3) var(--space-4);
  border-bottom:1px solid var(--border-subtle);text-decoration:none;color:var(--text-primary);font-size:var(--text-sm)}
.notif-item:last-child{border-bottom:0}
.notif-item:hover{background:var(--bg-surface)}
.notif-item.is-unread{background:var(--brand-1)}
.notif-item .ti{flex:0 0 auto;margin-top:2px}
.notif-item-time{display:block;color:var(--text-muted);font-size:var(--text-xs);margin-top:2px}
.notif-foot{padding:var(--space-2) var(--space-4)}

/* Item images (inventory) — optional image_path thumbnails. List rows get a
   small square thumb (or a neutral glyph so mixed data stays aligned); the
   item detail page shows the drawing larger above the field list. */
.col-thumb{width:48px}
td.col-thumb{padding-top:var(--space-1);padding-bottom:var(--space-1)}
.item-thumb{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;
  border:1px solid var(--border-subtle);border-radius:var(--radius-md);background:var(--bg-page);
  vertical-align:middle}
img.item-thumb{object-fit:contain;padding:2px}
.item-thumb--empty{color:var(--text-muted);font-size:var(--text-lg)}
.item-detail-media{margin:0 0 var(--space-4)}
.item-detail-image{display:block;max-width:200px;max-height:160px;object-fit:contain;
  border:1px solid var(--border-subtle);border-radius:var(--radius-md);background:var(--bg-page);
  padding:var(--space-2)}

/* Customizable dashboard (spec 2026-07-08). Ordered widget grid: KPI tiles are
   small auto-fill cells; wide widgets (tables/charts) span the full row. */
.dash-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(13rem,1fr));gap:var(--space-3);align-items:stretch}
.dash-w--kpi{min-width:0;display:flex}   /* stretch row → wrapper fills height */
.dash-w--kpi>.kpi-card{flex:1}           /* → card fills the wrapper, so all tiles match */
.dash-w--wide{grid-column:1/-1}
/* Customize panel */
.dash-edit{margin-bottom:var(--space-4);padding:var(--space-4)}
.dash-edit-list{list-style:none;margin:var(--space-3) 0;padding:0;display:flex;flex-direction:column;gap:var(--space-2)}
.dash-edit-item{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);background:var(--gray-1)}
.dash-edit-item.is-dragging{opacity:.5;border-color:var(--brand-9)}
.dash-edit-handle{cursor:grab;color:var(--text-muted);display:inline-flex}
.dash-edit-label{display:flex;align-items:center;gap:var(--space-2);flex:1;min-width:0}
.dash-edit-actions{display:flex;gap:var(--space-3);flex-wrap:wrap}

/* Dashboard customize — search + richer catalog rows (expanded 2026-07-08). */
.dash-edit-search{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);margin-bottom:var(--space-3);color:var(--text-muted)}
.dash-edit-search input{border:0;background:transparent;flex:1;font:inherit;color:var(--text-primary);outline:none}
.dash-edit-list{max-height:60vh;overflow-y:auto}
.dash-edit-label{display:flex;align-items:center;gap:var(--space-3)}
.dash-edit-icon{color:var(--text-muted);display:inline-flex}
.dash-edit-text{display:flex;flex-direction:column;min-width:0}
.dash-edit-name{display:flex;align-items:center;gap:var(--space-2);font-weight:600}
.dash-edit-empty{text-align:center;padding:var(--space-3)}

/* Dashboard customize — KPI/Panel type badges + section headings (2026-07-08). */
.wtype{flex:none;font-size:var(--text-xs);font-weight:700;padding:.1rem .4rem;border-radius:var(--radius-sm);letter-spacing:.02em}
.wtype--kpi{background:var(--brand-3);color:var(--brand-11)}
.wtype--panel{background:var(--gray-3);color:var(--text-secondary)}
.dash-edit-heading{font-weight:700;font-size:var(--text-sm);margin:var(--space-4) 0 var(--space-2)}
.dash-edit-list--enabled{border:1px dashed var(--border-strong);border-radius:var(--radius-sm);padding:var(--space-2);min-height:2.5rem}
.dash-edit-list--add{max-height:44vh;overflow-y:auto}
.dash-edit-group{list-style:none;font-size:var(--text-xs);font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted);padding:var(--space-2) var(--space-1) var(--space-1);display:flex;align-items:center;gap:var(--space-1)}

/* Dashboard customize — search + type filter row (2026-07-08). */
.dash-edit-filters{display:flex;gap:var(--space-2);align-items:stretch;margin-bottom:var(--space-3);flex-wrap:wrap}
.dash-edit-filters .dash-edit-search{flex:1;min-width:12rem;margin-bottom:0}
.dash-typefilter{display:inline-flex;border:1px solid var(--border-subtle);border-radius:var(--radius-sm);overflow:hidden;flex:none}
.dash-typefilter button{border:0;background:transparent;padding:var(--space-1) var(--space-3);font:inherit;font-size:var(--text-sm);color:var(--text-secondary);cursor:pointer;border-right:1px solid var(--border-subtle)}
.dash-typefilter button:last-child{border-right:0}
.dash-typefilter button.active{background:var(--brand-1);color:var(--brand-11);font-weight:600}

/* Dashboard customize — quick actions (2026-07-08). */
.dash-edit-quick{display:flex;gap:var(--space-3);margin-bottom:var(--space-2);flex-wrap:wrap}

/* Edit applies to the Details tab only (2026-07-28): on a tabbed detail page the
   in-place edit form lives in that panel, so the button is hidden elsewhere
   rather than sitting there doing nothing. Pages without tabs are unaffected. */
.tab-not-editable [data-action="enter-edit"]{display:none !important}
/* A9 — the measured rate sits beside the planned one; accepting it is a button,
   never an automatic write (a rate learned from one bad night would otherwise
   quietly re-plan the shop). */
.ri-accept { display:inline; }
.ri-accept .btn-text { padding:0 var(--space-1); }
/* Guided onboarding tour. The spotlight sits above the dim scrim created by
   the dialog shadow; the card remains thumb-friendly and readable on phones. */
.account-tour{border:0;background:transparent;color:var(--text-muted);padding:var(--space-2);cursor:pointer;border-radius:var(--radius-sm);display:inline-flex}
.account-tour:hover,.account-tour:focus-visible{background:var(--gray-2);color:var(--text-primary)}
.tour-card{position:fixed;z-index:1002;right:var(--space-5);bottom:var(--space-5);width:min(25rem,calc(100vw - 2rem));padding:var(--space-5);background:var(--bg-page);border:1px solid var(--border-strong);border-radius:var(--radius-lg);box-shadow:0 0 0 100vmax rgb(15 23 42 / .48),var(--shadow-lg)}
.tour-card h2{margin:var(--space-3) 0 var(--space-2);font-size:var(--text-xl)}
.tour-card p{margin:0;color:var(--text-secondary);line-height:1.5}
.tour-count{font-size:var(--text-xs);color:var(--text-muted);font-weight:600}
.tour-skip{float:right;border:0;background:transparent;color:var(--text-secondary);text-decoration:underline;cursor:pointer}
.tour-actions{display:flex;justify-content:space-between;gap:var(--space-3);margin-top:var(--space-5)}
.tour-target{position:relative;z-index:1003;outline:3px solid var(--brand-7);outline-offset:3px;border-radius:var(--radius-sm)}
@media(max-width:767px){.tour-card{right:1rem;bottom:calc(5rem + env(safe-area-inset-bottom));padding:var(--space-4)}.tour-target{z-index:1003}}
/* C5 — the per-step quality-check editor. Wide enough for six columns without
   the modal itself scrolling sideways; the table keeps its own wrapper.
   Two reasons the old `.modal--wide { max-width: … }` never did anything: a cap
   can't widen a fixed width, and `dialog.modal` (element+class) outranks a bare
   class, so the 520px shell won regardless. Both selectors are qualified. */
dialog.modal--wide { width:min(96vw, 64rem); }
/* A step read in full — bigger than the default shell, but a label/value list
   set 1000px wide is a worse read, not a better one. */
dialog.modal--lg { width:min(94vw, 44rem); }
.qc-table input[type="text"],.qc-table input[type="number"],.qc-table select{width:100%;
  padding:var(--space-2) var(--space-3);border:1px solid var(--border-strong);
  border-radius:var(--radius-sm);font:inherit;background:var(--bg-page);color:var(--text-primary)}
.qc-table td.num input{text-align:right;font-variant-numeric:tabular-nums}

/* ---- Printing, app-wide -----------------------------------------------------
   Anything carrying [data-print] prints the CONTENT, not the app around it: the
   sidebar, the topbar and the buttons are chrome on a screen and noise on paper.
   Shared here rather than per-page, because the roster's password sheet and a
   traveler are the same problem — a sheet somebody carries onto the floor. */
@media print {
  .sidebar, .topbar, .toast-stack, .toast-region, .impersonation-banner,
  .page-action-bar button, .page-action-bar .btn-secondary,
  .no-print { display: none !important; }
  .app, .main, .content { display: block; margin: 0; padding: 0; }
  .card { border: 0; box-shadow: none; padding: 0; }
  .data-table { font-size: 10pt; }
  a[href]::after { content: none !important; }
}
