/* ══════════════════════════════════════════════════════════════════════
   BUILD STAMP — 2026-08-11ze
   Read at boot by main.js and compared against BUILD_ID.

   WHY THIS EXISTS. index.html, js/main.js and css/main.css deploy as three
   separate files and can arrive out of step. The ?v= token already catches
   index.html disagreeing with main.js — but a STALE STYLESHEET served under a
   fresh token was invisible: the new markup renders, the new rules are simply
   absent, and what you see is unstyled content that looks like a layout bug.
   Chris hit exactly that on 11za — spec chips rendering as one run-on string
   because .av-chip did not exist in the css the browser had. Nothing errored.

   The property itself lives in the main :root token block below, NOT in a
   block of its own up here: a second :root at the top of the file is the first
   one several audits find, and they promptly reported that the theme had lost
   color-scheme and its whole palette. Caught by those audits within a minute of
   being introduced, which is the entire argument for keeping them.

   Keep this in step with BUILD_ID in js/main.js. The gate enforces it.
   ══════════════════════════════════════════════════════════════════════ */
/* ═══════════════════════════════════════════════════════
   SELF-HOSTED FONTS (2026-08-06h · Horizon H1a)
   Plus Jakarta Sans / DM Sans, latin + latin-ext woff2,
   vendored under /fonts (from @fontsource, pinned by file). Replaces the
   Google Fonts CDN — the last third-party fetch. font-display:swap keeps
   first paint instant on a cold cache.
═══════════════════════════════════════════════════════ */
/* plus-jakarta-sans-latin-ext-400-normal */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(../fonts/plus-jakarta-sans-latin-ext-400-normal.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
/* plus-jakarta-sans-latin-400-normal */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(../fonts/plus-jakarta-sans-latin-400-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
/* plus-jakarta-sans-latin-ext-500-normal */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 500;
  src: url(../fonts/plus-jakarta-sans-latin-ext-500-normal.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
/* plus-jakarta-sans-latin-500-normal */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 500;
  src: url(../fonts/plus-jakarta-sans-latin-500-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
/* plus-jakarta-sans-latin-ext-600-normal */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 600;
  src: url(../fonts/plus-jakarta-sans-latin-ext-600-normal.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
/* plus-jakarta-sans-latin-600-normal */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 600;
  src: url(../fonts/plus-jakarta-sans-latin-600-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
/* plus-jakarta-sans-latin-ext-700-normal */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 700;
  src: url(../fonts/plus-jakarta-sans-latin-ext-700-normal.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
/* plus-jakarta-sans-latin-700-normal */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 700;
  src: url(../fonts/plus-jakarta-sans-latin-700-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
/* dm-sans-latin-ext-400-normal */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(../fonts/dm-sans-latin-ext-400-normal.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
/* dm-sans-latin-400-normal */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(../fonts/dm-sans-latin-400-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
/* dm-sans-latin-ext-500-normal */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 500;
  src: url(../fonts/dm-sans-latin-ext-500-normal.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
/* dm-sans-latin-500-normal */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 500;
  src: url(../fonts/dm-sans-latin-500-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
/* dm-sans-latin-ext-700-normal */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 700;
  src: url(../fonts/dm-sans-latin-ext-700-normal.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
/* dm-sans-latin-700-normal */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 700;
  src: url(../fonts/dm-sans-latin-700-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

/* ═══════════════════════════════════════════════════════
   CERIOAPP INVENTORY PLANNING — MAIN STYLES
   Palette: Navy #0b1728 / Electric Blue #1a6fff / White surfaces
   Aligned with CERIO brand identity
═══════════════════════════════════════════════════════ */

:root {
  /* 11zb — this stylesheet's build, read at boot by main.js and compared with
     BUILD_ID. See the note at the top of this file. */
  --css-build: "20261019k";

  /* 07h — NATIVE CONTROL PAINTING. Checkboxes, radios, number spinners, date
     pickers, select arrows, the file-picker button, range tracks, textarea
     resize grips and scrollbars are painted by the BROWSER, not by our tokens.
     `color-scheme` is the only thing that tells it which palette to use, and
     the app had never set it — so on the dark theme every one of those rendered
     in its light-mode form (the giveaway was a bright white unchecked tick box
     on a near-black row, found in 07g). Declared explicitly on BOTH themes
     rather than only on dark: without it the value is `normal`, which lets a
     UA or an OS preference decide, and our light theme must stay light no
     matter what the user's system is set to. */
  color-scheme: light;

  /* ── Z-INDEX LAYER SCALE (single source of truth) ──
     Every stacking context uses one of these named tiers. Do not hard-code
     z-index elsewhere; add a tier here if a new layer is genuinely needed.
     Ascending = closer to the viewer. */
  --z-base:            1;     /* in-flow raised elements (sticky cells, map)   */
  --z-sticky-header:   5;     /* sticky table headers/footers                  */
  --z-dropdown-inline: 60;    /* inline popups anchored to a control (menus,
                                 multi-select panels) — must clear page chrome */
  --z-page-chrome:     100;   /* sidebar, topbar, banners                      */
  --z-overlay:         1000;  /* base modal overlays (dimmed backdrop + modal) */
  --z-overlay-popup:   1100;  /* popups opened FROM inside a modal (a rename
                                 prompt over the dropdown-manager modal, a
                                 multi-select panel inside a form modal)       */
  --z-app-dialog:      1200;  /* app alert/prompt/confirm — always on top of
                                 whatever opened them                          */
  --z-toast:           1300;  /* toasts: visible above everything, briefly     */

  /* Cerio brand blue (accent) — var name kept so all existing usages remap */
  /* Brand accent — Cerio blue. (Formerly named --orange, which no longer
     matched the value; renamed 2026-07-27 so names describe actual colours.) */
  --cerio-blue:      #012BBF;         /* deep blue: primary accent, links, titles */
  --cerio-blue-dark: #011f8f;         /* hover/pressed shade of the accent */
  --cerio-blue-tint: #EEF1FB;         /* pale blue tint: selected nav, accent bg */

  /* Text hierarchy */
  --surface-dark: #1A1A1A;        /* dark chrome surfaces: sidebar, toast, topbar, dark accent chips */
  --text-primary:        #1A1A1A;
  --text-secondary:    #5F5E5A;
  --text-tertiary:  #6D6C64;         /* 06m: was #8F8E86 — 2.83:1 on --surface-subtle, below AA. 4.54:1 at the tightest now. */
  --text-faint:  #B4B2A9;
  --cerio-blue-tint-dark: #E6EBFB;

  /* 01a — the division accents as TOKENS, so the search pills (and anything else that
     wants them later) adapt with the theme. PowerGen deliberately has none of its own:
     it is --cerio-blue, which is already theme-aware. Values mirror DIVISION_COLORS. */
  --div-ups:        #5B3F9E;  --div-ups-tint:  rgba(91,63,158,0.10);
  --div-switchgear: #0F6E56;  --div-switchgear-tint: rgba(15,110,86,0.10);
  --div-mechanical: #8A6A1F;  --div-mechanical-tint: rgba(138,106,31,0.11);

  /* Surfaces */
  --surface-page:          #F8F8F4;         /* warm off-white page (NOT pure white) */
  --surface-card:     #FFFFFF;         /* cards, modals, dropdowns */
  --surface-subtle:   #EFEEE8;         /* subtle surface: inactive pills/buttons */

  /* Borders */
  --border-default:      #E8E7E0;
  --border-soft: #F0EFE9;
  --border-strong:#D8D6CE;

  /* Semantic (mapped to guide severity / tone) */
  /* Severity tints — Cerio Theme Guide s2. Semantic ONLY: High = threshold
     crossed, Medium = needs attention, Low = ok / on track (green per the
     2026-07-13 correction). Do not use decoratively. */
  --sev-high-bg: #FCEBEB; --sev-high-border: #F0BABA; --sev-high-text: #791F1F;
  --sev-med-bg:  #FBF3E1; --sev-med-border:  #EFE0BC; --sev-med-text:  #633806;
  --sev-low-bg:  #F2F8EA; --sev-low-border:  #DCE9CC; --sev-low-text:  #3B6D11;
  --green:       #0F6E56;
  --red:         #A32D2D;
  --amber:       #BA7517;

  /* 06r: count badges get their OWN red, deliberately NOT re-declared under
     [data-theme="dark"]. --red is a text ink and dark flips it to #FF8B8B, a
     pale pink meant for type on dark surfaces; used as a badge FILL under white
     text that measured 2.25:1. The nav badges live on the Horizon bar, whose
     blue gradient is identical in both themes, so their fill must not follow the
     theme either. White on #D32F2F = 4.98:1 (AA). Chris picked this over the
     light-mode #A32D2D (7.07:1) because it reads better against the blue. */
  --badge-red:   #D32F2F;

  /* KPI card indicators — bright, meant to pop (separate from muted severity) */
  --kpi-red:     #FA534A;
  --kpi-yellow:  #FFFD55;
  --kpi-green:   #75FA8D;
  --kpi-gray:    #5F5E5A;

  /* Fonts */
  --font-display:'Plus Jakarta Sans', ui-sans-serif, system-ui, sans-serif;
  --font-body:   'DM Sans', ui-sans-serif, system-ui, sans-serif;
  /* THERE IS NO --font-mono, AND THAT IS THE POINT (2026-08-12f, Chris).
     The app carried JetBrains Mono as a third family for figures. Its woff2
     ships a DOTTED ZERO and its feature table carries no `zero`, `ss__` or
     `cv__`, so font-feature-settings had nothing to switch to and the only
     fix was a different face. 12e named the face in a token so the swap could
     happen in one place; 12f made the swap, and the answer was to drop the
     family rather than replace it. Every figure in the app is now DM Sans —
     the same face as the text around it — and the four jetbrains-mono woff2
     files are gone from /fonts, so the app ships two families, not three.
     WHAT THIS COST, stated plainly so nobody re-adds it by accident: DM Sans
     is proportional, so figures no longer align column-to-column. The visible
     places are the timeline delivery labels and the .up-when date column.
     DO NOT reach for `monospace` as a one-off here — the generic keyword
     resolves to whatever the OS ships (Menlo, Consolas, Liberation Mono) and
     would put a THIRD, unpredictable face back into the type system. If a
     future surface genuinely needs fixed-width figures, bring the decision
     back to Chris and add a named, vendored face — not a fallback. */

  --radius:      14px;    /* H1a: Horizon rounding — cards, modals            */
  --radius-sm:   8px;     /* H1a: buttons, inputs, chips                      */
  --radius-lg:   16px;    /* H1a: hero surfaces (adopted by components in H2) */
  /* H1a: real elevation tokens. Deliberately UNCONSUMED until H2 gives the
     component kit its shadows — tokens first, adoption per stage. */
  --shadow:      0 1px 2px rgba(25,25,20,.04), 0 10px 28px rgba(25,25,20,.06);
  --shadow-lg:   0 12px 40px rgba(25,25,20,.14);
  --transition:  0.15s ease;
}

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

/* H1a: buttons do NOT inherit font/color from the page by default — the UA
   gives them a system font and black text. Two shipped bugs (06c AI review,
   06e Recently Viewed) came from individual buttons missing the opt-in.
   Inherit globally; every existing button that sets its own font/color is
   unaffected, and the unstyled stragglers become correct instead of black. */
button { font: inherit; color: inherit; }

/* Display-size scaling. The app's CSS is rem-based, so scaling the root font
   size scales the main content. The sidebar is deliberately px-based (and
   --sidebar-w is a fixed px width), so it does NOT scale — matching the
   "main content only" behaviour chosen in Settings. Viewport units (vh/vw)
   are unaffected, so full-height layouts and modal max-heights stay correct. */
html { font-size: calc(15px * var(--ui-scale, 1)); }

/* Global keyboard focus indicator (Cerio Theme Guide s13). Accessibility:
   several component rules set `outline: none`, so without this a keyboard
   user has no visible focus. :focus-visible only shows for keyboard nav. */
button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[role="button"]:focus-visible {
  outline: 2px solid var(--cerio-blue);
  outline-offset: 2px;
  border-radius: 4px;
}

body {
  font-family: var(--font-body);
  background: var(--surface-page);
  color: var(--text-primary);
  min-height: 100vh;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ── Utilities ── */
.hidden { display: none !important; }
.screen { min-height: 100vh; }

/* ── ROLE VISIBILITY, DECLARATIVE (8b / F-002 Defect 2) ──────────────────
   Until this block existed there was NO CSS RULE FOR .editor-only AT ALL. The
   class was inert: visibility came entirely from one imperative sweep at login
   (applyRoleVisibility, js/main.js), which never re-runs. Five render paths emit
   .editor-only into innerHTML AFTER that sweep, so anything they drew kept the
   class and none of the meaning.

   EVERY RULE HERE IS HIDE-ONLY. None of them restores a display value, and that
   is deliberate: the obvious `display: revert` on a show-rule is WRONG. Measured
   2026-08-15 — the two proposal nav buttons get `display: flex` from
   `.hz-menu .nav-item` (an AUTHOR-origin rule), so `revert` rolls back past it to
   the user-agent's `inline-block` for a <button>, losing align-items and gap. By
   only ever hiding, an element that is allowed keeps whatever rules it already had.

   The imperative sweep is deliberately LEFT IN PLACE. It agrees with these rules
   everywhere, and removing it would also remove the two id-specific re-shows that
   run after it, one of which is partner-facing. This build makes the class real;
   retiring the sweep is a separate decision.

   #nav-approvals-btn is the one exception, and it is why the first selector carries
   a :not(). It is marked .editor-only but EXTERNAL FULL must keep it — the Pending
   Approvals tab scoped to their own requests (js/main.js:1283 re-shows it after the
   sweep). A blanket rule would beat that `style.display = ''` and cost partners the
   tab, so it is excluded here and hidden by its own hide-only rule below.


   (A second exception for #status-dropdown-wrap lived here until 04g, when the footer
   Status control was removed outright — a project has no stage, so there was nothing left
   to reveal. Removed with it.) */
body:not([data-role-can-edit="1"])   .editor-only:not(#nav-approvals-btn),
body:not([data-role-can-edit="1"])   .canedit-only,
body:not([data-role="admin"])        .admin-only,
body:not([data-role-ext-or-edit="1"]) .editor-or-external,
body:not([data-role-approver="1"])   .approver-only { display: none; }

body:not([data-role-can-edit="1"]):not([data-role-ext-full="1"]) #nav-approvals-btn { display: none; }


/* ═══════════════════════════════════════════════════════
   LOGIN
═══════════════════════════════════════════════════════ */
.login-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #0b1728 0%, #132240 60%, #1a3a6b 100%);
  padding: 24px;
}

.login-card {
  box-shadow: var(--shadow);   /* H3a: freed from the blanket no-shadow rule */
  background: var(--surface-card);
  border-radius: 16px;
  padding: 48px 44px;
  width: 100%;
  max-width: 420px;
  text-align: center;
}

.login-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-bottom: 28px;
}

.login-title {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 500;
  color: var(--text-primary);
  margin-bottom: 10px;
}

.login-desc {
  font-size: 0.9rem;
  color: var(--text-secondary);
  margin-bottom: 32px;
  line-height: 1.6;
}

.login-error {
  background: #fef2f2;
  color: var(--red);
  border: 0.5px solid #fecaca;
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  font-size: 0.85rem;
  margin-bottom: 12px;
}

.login-footer {
  font-size: 0.78rem;
  color: var(--text-tertiary);
  margin-top: 24px;
}

/* ═══════════════════════════════════════════════════════
   APP LAYOUT
═══════════════════════════════════════════════════════ */
#app-screen {
  display: flex;
  flex-direction: column;   /* H1b: horizon bar on top, content below */
  min-height: 100vh;
}

/* ═══════════════════════════════════════════════════════
   HORIZON BAR (H1b) — the sidebar's replacement.
   Identity element: the gradient stays the BRAND blues in BOTH themes
   (literals on purpose — the dark theme re-maps --cerio-blue to a light
   accent that must not wash out the bar). Everything inside the bar is
   white-on-gradient; the dropdown menus are normal themed surfaces.
═══════════════════════════════════════════════════════ */
.hz-bar {
  position: sticky; top: 0;
  z-index: var(--z-page-chrome);
  display: flex; align-items: center; gap: 12px;
  height: 56px; padding: 0 18px; flex-shrink: 0;
  background: linear-gradient(90deg, #011C7E, #012BBF 55%, #0A3BE0);
  color: #fff;
}
.hz-brand { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.hz-logo { height: 34px; width: auto; max-width: 96px; object-fit: contain; background: #fff; border-radius: 8px; padding: 3px 7px; }
.hz-brand-text { display: flex; flex-direction: column; line-height: 1.15; }
.hz-brand-name { font-family: var(--font-display); font-size: 0.95rem; font-weight: 700; color: #fff; }
.hz-brand-sub { font-size: 0.58rem; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; color: rgba(255,255,255,0.55); }

/* Base .nav-item is a minimal reset; each context (pill rail, dropdown menu,
   mobile drawer) styles it fully. */
/* 21m: .nav-item is an <a> now (so the browser offers "Open link in new tab"), hence
   text-decoration and the explicit colour reset — everything else about the look is unchanged. */
.nav-item { border: none; background: transparent; cursor: pointer; font-family: inherit; text-decoration: none; color: inherit; transition: all var(--transition); }

/* Module pills.
   06q: `margin-right:auto` here is what pushes the search pill, favorites,
   Recently Viewed and the avatar to the far right. It used to live on .hz-cmd
   (`margin-left:auto`), but applyExternalGating hides #nav-global-search for
   partners — and a display:none element contributes no margin, so for every
   external user the whole right-hand group collapsed leftwards (449px short of
   the right edge at 1440). The spacer belongs on an element that is always in
   the bar, not on one that role gating can remove. */
.hz-mods { display: flex; align-items: center; gap: 2px; min-width: 0; margin-right: auto; }
.hz-mods > .nav-item {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 13px; border-radius: 99px; position: relative;
  font-size: 0.8rem; font-weight: 600; color: rgba(255,255,255,0.72);
  white-space: nowrap;
}
.hz-mods > .nav-item:hover { color: #fff; }
.hz-mods > .nav-item.active { background: rgba(255,255,255,0.18); color: #fff; box-shadow: inset 0 0 0 1px rgba(255,255,255,0.25); }
.hz-mods > .nav-item .nav-badge { margin-left: 2px; }

/* More menu */
.hz-more { position: relative; display: flex; }
.hz-more-btn {
  display: inline-flex; align-items: center; gap: 5px; position: relative;
  padding: 7px 13px; border-radius: 99px; border: none; background: transparent;
  font-size: 0.8rem; font-weight: 600; color: rgba(255,255,255,0.72);
  cursor: pointer; white-space: nowrap; font-family: inherit; transition: all var(--transition);
}
.hz-more-btn:hover, .hz-more-btn[aria-expanded="true"] { color: #fff; }
.hz-more-btn[aria-expanded="true"] { background: rgba(255,255,255,0.14); }
.hz-caret { font-size: 0.6rem; opacity: 0.8; }
/* 06r: same fixed red as .nav-badge — the dot mirrors those badges, so it's
   pointless for the two to disagree in dark. */
.hz-more-dot { position: absolute; top: 4px; right: 6px; width: 7px; height: 7px; border-radius: 50%; background: var(--badge-red, #D32F2F); }

/* Shared dropdown surface (More, Recently Viewed, avatar). First consumer of
   the H1a shadow tokens — new Horizon components adopt them from day one. */
.hz-menu {
  position: absolute; top: calc(100% + 8px); right: 0;
  min-width: 230px; padding: 6px;
  background: var(--surface-card); color: var(--text-primary);
  border: 0.5px solid var(--border-default); border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  z-index: calc(var(--z-page-chrome) + 3);
}
.hz-more-menu { left: 0; right: auto; }
.hz-menu .nav-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 12px; border-radius: var(--radius-sm); text-align: left;
  font-size: 0.84rem; font-weight: 500; color: var(--text-secondary);
}
.hz-menu .nav-item:hover { background: var(--surface-subtle); color: var(--text-primary); }
.hz-menu .nav-item.active { background: var(--cerio-blue-tint); color: var(--cerio-blue); }
.hz-menu .nav-item svg { stroke: currentColor; flex-shrink: 0; }
.hz-menu .nav-item .nav-badge { margin-left: auto; }
/* 01za (Chris: "There should be no icons in the top header whatsoever. The icons should
   stay within the more section only.") — a button promoted from the More menu onto the bar
   (Rearrange Toolbar) carries its menu icon along, because the NODE moves. On the bar the
   icon hides; back in the menu the same node shows it again. Native bar buttons have no
   svg, so this rule is inert for them. */
#hz-mods > .nav-item[data-view] > svg { display: none; }

.hz-menu-sep { height: 0.5px; background: var(--border-default); margin: 6px 4px; }

/* Command bar (promoted Ctrl+K — same #nav-global-search id, same gsOpen) */
.hz-cmd {
  /* 06q: the auto margin moved to .hz-mods — see the note there. */
  flex-shrink: 1; min-width: 0;
  display: inline-flex; align-items: center; gap: 9px;
  background: rgba(255,255,255,0.14); border: 1px solid rgba(255,255,255,0.25);
  border-radius: 99px; padding: 7px 14px; width: 230px;
  font-size: 0.77rem; color: rgba(255,255,255,0.85);
  cursor: pointer; font-family: inherit; text-align: left; transition: all var(--transition);
}
.hz-cmd:hover { background: rgba(255,255,255,0.2); }
.hz-cmd svg { flex-shrink: 0; }
.hz-cmd-text { flex: 1; overflow: hidden; white-space: nowrap; }
.hz-cmd kbd { margin-left: auto; font-size: 0.6rem; background: rgba(255,255,255,0.18); padding: 2px 7px; border-radius: 6px; font-family: inherit; flex-shrink: 0; }

/* Recently Viewed popout + icon buttons */
.hz-rv { position: relative; display: flex; flex-shrink: 0; }
.hz-rv-menu { min-width: 258px; }
.hz-iconbtn {
  width: 34px; height: 34px; border-radius: 50%; border: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,0.14); color: rgba(255,255,255,0.85);
  transition: all var(--transition);
}
.hz-iconbtn:hover, .hz-iconbtn[aria-expanded="true"] { background: rgba(255,255,255,0.24); color: #fff; }

/* Avatar + its menu */
.hz-user { position: relative; display: flex; flex-shrink: 0; }
.hz-avatar {
  width: 32px; height: 32px; border-radius: 50%; border: none; cursor: pointer;
  background: #fff; color: #012BBF; font-weight: 700; font-size: 0.7rem;
  display: flex; align-items: center; justify-content: center; text-transform: uppercase;
  font-family: inherit;
}
/* 11s: same size and same red as .hz-more-dot — one dot, one meaning, wherever
   a popout is hiding a live badge. Sits on the avatar's edge and takes no
   pointer events, so it never eats the click that opens the menu. */
.hz-avatar-dot { position: absolute; top: -1px; right: -1px; width: 7px; height: 7px; border-radius: 50%; background: var(--badge-red, #D32F2F); pointer-events: none; }
.hz-avatar-menu { min-width: 244px; }
.hz-menu-user { padding: 8px 12px 4px; }
.hz-menu-user .user-name { font-size: 0.88rem; font-weight: 600; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hz-menu-user .user-role { font-size: 0.68rem; color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.06em; margin-top: 1px; }
.hz-theme-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 12px; font-size: 0.8rem; color: var(--text-secondary); }

/* ── 18j — the Division Fields registry table (it had NO styles at all) ────── */
.df-registry { border-collapse: collapse; }
.df-registry th { text-align: left; font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-tertiary); padding: 8px 10px; border-bottom: 1px solid var(--border-default); }
.df-registry th.df-th-actions { text-align: right; }
.df-registry td { padding: 8px 10px; border-bottom: 0.5px solid var(--border-default); font-size: 0.86rem; vertical-align: middle; }
.df-registry .df-row:hover td { background: var(--surface-subtle); }
.df-registry .df-row-retired td { opacity: 0.55; }
.df-registry .df-cell-name { font-weight: 500; }
.df-registry .df-list-name { font-size: 0.76rem; color: var(--text-tertiary); }
.df-registry .df-cell-actions { text-align: right; white-space: nowrap; }
.df-registry .df-act { font-size: 0.78rem; padding: 3px 9px; margin-left: 4px; }
.df-registry .df-act[disabled] { opacity: 0.3; cursor: default; }
.df-registry .df-act-spacer { display: inline-block; }
.df-edit-row td { background: var(--surface-subtle); padding: 12px 10px; }
.df-edit-grid { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
.df-edit-grid > label { display: flex; flex-direction: column; gap: 4px; font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-tertiary); }
.df-edit-grid > label > .form-input { min-width: 150px; font-size: 0.85rem; }
.df-edit-grid .df-edit-req { flex-direction: row; align-items: center; gap: 6px; text-transform: none; font-size: 0.84rem; font-weight: 500; color: var(--text-primary); padding-bottom: 8px; cursor: pointer; }
.df-edit-grid .df-edit-btns { display: flex; gap: 8px; margin-left: auto; padding-bottom: 2px; }
.df-edit-hint { font-size: 0.74rem; color: var(--text-tertiary); margin-top: 8px; }
/* 18q — the Data Map's collapsed registry groups (native details/summary) */
.dm-reg { border: 0.5px solid var(--border-default); border-radius: 8px; margin: 6px 0; background: var(--surface-card); }
.dm-reg + .dm-reg { margin-top: 6px; }
.dm-reg-sum { display: flex; align-items: center; justify-content: space-between; gap: 12px; cursor: pointer; padding: 10px 14px; font-size: 0.86rem; font-weight: 600; color: var(--text-primary); list-style: none; border-radius: 8px; }
.dm-reg-sum::-webkit-details-marker { display: none; }
.dm-reg-sum::before { content: '▸'; color: var(--cerio-blue); font-size: 0.8rem; margin-right: 2px; }
.dm-reg[open] > .dm-reg-sum::before { content: '▾'; }
.dm-reg-sum:hover { background: var(--surface-subtle); }
.dm-reg-sum:focus-visible { outline: 2px solid var(--cerio-blue); outline-offset: -2px; }
.dm-reg-count { margin-left: auto; font-size: 0.74rem; font-weight: 500; color: var(--text-tertiary); }
.dm-reg[open] > .dm-reg-sum { border-bottom: 0.5px solid var(--border-default); border-radius: 8px 8px 0 0; }
.dm-reg .df-registry { margin: 0; }
.dm-reg .df-registry th { padding-top: 10px; }

/* 18k — the registry groups by section, like the record panels */
.df-registry .df-sec-row td { padding: 14px 10px 5px; font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--cerio-blue); border-bottom: 1px solid var(--border-default); background: transparent; }
.hz-theme-btns { display: flex; gap: 4px; }
.hz-theme-btns button {
  padding: 4px 12px; border-radius: 99px; cursor: pointer;
  border: 0.5px solid var(--border-strong); background: transparent;
  color: var(--text-secondary); font-size: 0.72rem; font-weight: 600; font-family: inherit;
  transition: all var(--transition);
}
.hz-theme-btns button.active { background: var(--cerio-blue-tint); border-color: var(--cerio-blue); color: var(--cerio-blue); }
/* 16p/16r — Division Switcher: same row treatment as Theme above it; a compact
   dropdown per Chris. */
.hz-menu-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 12px; border-radius: var(--radius-sm); border: none; background: transparent;
  text-align: left; cursor: pointer; font-family: inherit;
  font-size: 0.84rem; font-weight: 500; color: var(--text-secondary);
  transition: all var(--transition);
}
.hz-menu-item:hover { background: var(--surface-subtle); color: var(--text-primary); }
.hz-signout:hover { color: var(--red); }

/* ── Main Content ── */
.main-content {
  flex: 1;
  width: 100%;
  max-width: 1680px;
  margin: 0 auto;             /* H1b: no sidebar — content centers under the bar */
  padding: 28px 36px;
  min-height: calc(100vh - 56px);
}

.view { display: none !important; }
.view.active { display: block !important; }

.view-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 28px;
  flex-wrap: wrap;
  gap: 12px;
}

.view-title {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 500;
  color: var(--text-primary);
}

.view-date {
  font-size: 0.85rem;
  color: var(--text-tertiary);
}

.view-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* ═══════════════════════════════════════════════════════
   DASHBOARD
═══════════════════════════════════════════════════════ */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 18px;
  margin-bottom: 24px;
}

.kpi-card {
  container-type: inline-size;
  background: var(--surface-card);
  border-radius: var(--radius);
  padding: 22px 24px;
  /* H2a: the --shadow token defined (deliberately unconsumed) in H1a lands
     here. Cards lift off the page slightly; page-level panels stay flat, so
     elevation means "this is an object", not decoration. */
  box-shadow: var(--shadow);
  border: 0.5px solid var(--border-default);
  position: relative;
  overflow: hidden;
}

.kpi-card::before {
  content: '';
  position: absolute;
  top: 0; left: 0;
  width: 4px;
  height: 100%;
  background: var(--text-secondary);
  border-radius: 4px 0 0 4px;
}


.kpi-label {
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-tertiary);
  margin-bottom: 8px;
}

.kpi-value {
  font-family: var(--font-display);
  font-size: clamp(22px, 20cqi, 42px);
  white-space: nowrap;
  font-weight: 500;
  color: var(--text-primary);
  line-height: 1;
  margin-bottom: 4px;
}


.kpi-sub {
  font-size: 0.78rem;
  color: var(--text-tertiary);
}

.chart-card, .upcoming-card {
  background: var(--surface-card);
  border-radius: var(--radius);
  padding: 22px 24px;
  /* H2a: the --shadow token defined (deliberately unconsumed) in H1a lands
     here. Cards lift off the page slightly; page-level panels stay flat, so
     elevation means "this is an object", not decoration. */
  box-shadow: var(--shadow);
  border: 0.5px solid var(--border-default);
}

.chart-title {
  font-size: 0.8rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-tertiary);
  margin-bottom: 16px;
}

/* ══ 08j — THE .bar-* CHART FAMILY IS DELETED ═══════════════════════════
   .bar-chart / .bar-row / .bar-label / .bar-track / .bar-fill / .bar-count and
   the three .prob-* descendant overrides that coloured .bar-fill are gone. Their
   only two producers, renderInvGenTypeChart() and renderBarChart(), had no
   callers and are deleted in this build; .bar-chart had no producer at all.
   .prob-high / .prob-medium / .prob-low THEMSELVES SURVIVE — they colour
   .prob-pill on the dashboard's high-probability list, which is live. Only the
   `.prob-* .bar-fill` descendant rules die here. */

.upcoming-empty {
  font-size: 0.85rem;
  color: var(--text-tertiary);
  text-align: center;
  padding: 20px;
}

/* ═══════════════════════════════════════════════════════
   PROJECTS TABLE
═══════════════════════════════════════════════════════ */

.search-input {
  padding: 8px 14px 8px 34px;
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm);
  font-size: 0.88rem;
  background: var(--surface-card);
  color: var(--text-primary);
  width: 220px;
  transition: border-color var(--transition);
}

.search-input:focus {
  outline: none;
  border-color: var(--cerio-blue);
}

.filter-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}

.filter-select {
  padding: 7px 12px;
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  background: var(--surface-card);
  color: var(--text-primary);
  cursor: pointer;
  transition: border-color var(--transition);
}

.filter-select:focus {
  outline: none;
  border-color: var(--cerio-blue);
}

.filter-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  background: var(--surface-card);
  color: var(--text-primary);
  cursor: pointer;
  white-space: nowrap;
  user-select: none;
  transition: border-color var(--transition);
}
.filter-check:hover { border-color: var(--cerio-blue); }
.filter-check input { accent-color: var(--cerio-blue); cursor: pointer; margin: 0; }

/* Multi-select checkbox filters */
.ms-filter { position: relative; display: inline-block; }
.ms-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  background: var(--surface-card);
  color: var(--text-primary);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color var(--transition);
  max-width: 220px;
}
.ms-btn:hover { border-color: var(--cerio-blue); }
.ms-btn.ms-active { border-color: var(--cerio-blue); color: var(--cerio-blue); }
.ms-btn-text { overflow: hidden; text-overflow: ellipsis; }
.ms-caret { margin-left: auto; font-size: 0.7rem; opacity: 0.7; }
/* Applied by JS only when the panel would overflow the right viewport edge */
.ms-panel.ms-flip { left: auto; right: 0; }
.ms-panel {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: var(--z-dropdown-inline);
  min-width: 210px;
  max-width: calc(100vw - 24px);  /* never wider than the viewport (dormant on desktop) */
  max-height: 300px;
  overflow-y: auto;
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm);
  box-shadow: none;
  padding: 6px;
}
.ms-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 4px 8px 6px;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-tertiary);
  border-bottom: 0.5px solid var(--border-default);
  margin-bottom: 4px;
}
.ms-clear { background: none; border: none; color: var(--cerio-blue); cursor: pointer; font-size: 0.75rem; padding: 0; }
.ms-clear:disabled { color: var(--text-tertiary); cursor: default; }
.ms-opt {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 4px;
  font-size: 0.83rem;
  color: var(--text-primary);
  cursor: pointer;
}
.ms-opt:hover { background: var(--surface-subtle); }
.ms-opt input { accent-color: var(--cerio-blue); cursor: pointer; margin: 0; flex-shrink: 0; }
.ms-empty { padding: 8px; color: var(--text-tertiary); font-size: 0.8rem; }

/* ══ SCHEDULED VIEW — TIMELINE (H5) — 2026-08-10g ══════════════════════
   Supply and demand on one axis. Every colour is a SHIPPED token: --green is
   stock on hand, --cerio-blue is an incoming batch, --badge-red is past due and
   the today line. Nothing here introduces a palette. */
.tl-wrap { overflow: auto; max-width: 100%; background: var(--surface-card);
  border: 0.5px solid var(--border-default); border-radius: var(--radius); }
.tl-months { display: grid; background: var(--surface-subtle);
  border-bottom: 0.5px solid var(--border-default); position: sticky; top: 0; z-index: 4; }
.tl-months div { font-size: 0.64rem; font-weight: 500; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-tertiary); padding: 8px 10px;
  border-left: 0.5px solid var(--border-default); white-space: nowrap; }
.tl-months div:first-child { border-left: none; }
.tl-lanes { position: relative; padding-top: 14px; }
.tl-lane { display: grid; grid-template-columns: 230px 1fr;
  border-bottom: 0.5px solid var(--border-default); cursor: pointer; }
.tl-lane:last-child { border-bottom: none; }
.tl-lane:hover .tl-track { background: var(--surface-subtle); }
.tl-lane:focus-visible { outline: 2px solid var(--cerio-blue); outline-offset: -2px; }
/* 10h: the lead column is pinned to the TRACK's height and centres its content.
   Without this an alerting lane ran 68px and a quiet one 60px, so the rows went
   ragged the moment a pill appeared — and which lanes alert changes as the data
   does, so it would have looked different every day. */
.tl-lname { padding: 0 15px; height: 60px; overflow: hidden;
  display: flex; flex-direction: column; justify-content: center;
  border-right: 0.5px solid var(--border-default);
  border-left: 3px solid transparent; min-width: 0; }
.tl-lane.ins-sel .tl-lname { border-left-color: var(--cerio-blue); background: var(--cerio-blue-tint); }
.tl-lname .n { font-size: 0.8rem; font-weight: 500; color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tl-lname .c { font-size: 0.67rem; color: var(--text-tertiary); margin-top: 1px; }
/* 10h — the worst alert, in place of the subtitle. One pill fits the 200px of
   content this column has; two never do. Scoped so the shared .alert-pill keeps
   its table and record-header sizes. */
.tl-alert { display: flex; align-items: center; gap: 6px; margin-top: 3px; min-width: 0; }
.tl-alert .alert-pill { font-size: 0.66rem; padding: 2px 7px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.tl-alert-more { font-size: 0.66rem; color: var(--text-tertiary); flex-shrink: 0; }
/* A bar that opens an inventory record. */
.tl-supply.tl-clickable { cursor: pointer; }
.tl-supply.tl-clickable:hover { filter: brightness(1.12); }
.tl-supply.tl-clickable:focus-visible { outline: 2px solid var(--cerio-blue); outline-offset: 1px; }
.tl-supply.ins-sel { outline: 2px solid var(--cerio-blue); outline-offset: 1px; }
.tl-track { position: relative; height: 60px; }
.tl-gridl { position: absolute; inset: 0; pointer-events: none; }
/* A labelled bar. The label lives INSIDE it, as the Horizon mockup had it. */
.tl-supply { position: absolute; top: 10px; height: 16px; border-radius: 6px;
  font-size: 0.6rem; font-weight: 500; display: flex; align-items: center;
  padding: 0 8px; white-space: nowrap; overflow: hidden;
  background: var(--cerio-blue-tint); border: 0.5px solid var(--cerio-blue); color: var(--cerio-blue); }
.tl-supply.on-hand { background: var(--sev-low-bg); border-color: var(--sev-low-border); color: var(--sev-low-text); }
/* Too narrow to hold its label — a marker, with the text in the tooltip. */
.tl-supply.stub { min-width: 10px; padding: 0; }
[data-theme="dark"] .tl-supply { background: rgba(91,123,240,0.18); }
[data-theme="dark"] .tl-supply.on-hand { background: rgba(15,110,86,0.24); border-color: #2E7D63; color: #7FD9BC; }
.tl-deliv { position: absolute; top: 34px; width: 15px; height: 15px;
  transform: rotate(45deg); border-radius: 4px; background: var(--cerio-blue); }
.tl-deliv.done { background: var(--green); }
/* NOT animated. H6 commits to prefers-reduced-motion and a SINGLE-pulse past-due
   marker, so a permanent animation here would be built to be removed. */
.tl-deliv.late { background: var(--badge-red); }
.tl-dlbl { position: absolute; top: 36px; margin-left: 22px; white-space: nowrap;
  font-family: var(--font-body); font-size: 0.6rem; font-weight: 500;
  color: var(--text-tertiary); pointer-events: none; }
.tl-dlbl.late { color: var(--badge-red); }
/* Not covered in time — the same amber the timing-conflict alert uses. */
.tl-deliv.short { background: var(--sev-med-border); }
.tl-dlbl.short { color: var(--sev-med-text); }
[data-theme="dark"] .tl-dlbl.short { color: #E8B583; }
.tl-dlbl.done { color: var(--green); }
.tl-today { position: absolute; top: 0; bottom: 0; width: 2px;
  background: var(--badge-red); z-index: 3; pointer-events: none; }
.tl-today::before { content: 'TODAY'; position: absolute; top: 0; left: 5px;
  font-size: 0.54rem; font-weight: 500; letter-spacing: 0.08em; color: var(--badge-red); }
.tl-legend { display: flex; gap: 18px; flex-wrap: wrap; align-items: center;
  margin: 10px 2px 0; font-size: 0.74rem; color: var(--text-tertiary); }
.tl-legend .tl-key { display: inline-block; width: 24px; height: 11px; border-radius: 5px;
  margin-right: 7px; vertical-align: middle;
  background: var(--cerio-blue-tint); border: 0.5px solid var(--cerio-blue); }
.tl-legend .tl-key.on-hand { background: var(--sev-low-bg); border-color: var(--sev-low-border); }
.tl-legend .tl-key-d { display: inline-block; width: 11px; height: 11px; border-radius: 3px;
  transform: rotate(45deg); margin-right: 9px; vertical-align: middle; background: var(--cerio-blue); }
.tl-legend .tl-key-d.done { background: var(--green); }
.tl-legend .tl-key-d.late { background: var(--badge-red); }
.tl-legend-note { flex-basis: 100%; color: var(--text-tertiary); opacity: 0.85; }

/* ══ FILTER BAR — 2026-08-10a ══════════════════════════════════════════
   One Filter button, the presets beside it, the active filters beneath it.
   Reuses .col-preset-btn / .stage-preset-btn for the presets deliberately: the
   Projects stage row already looked like this, so Inventory joining it IS the
   continuity — a new preset look would have been one more convention. */
.fbar { align-items: flex-start; gap: 12px; margin-bottom: 10px; }
.fbar-lead { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; flex: 1; min-width: 0; }
/* The trailing control (Expand all) stays put as presets wrap, instead of
   drifting to wherever the last preset happened to end. */
.fbar-trail { margin-left: auto; flex-shrink: 0; }
.fbar-wrap { position: relative; flex-shrink: 0; }
/* 2026-08-10b — the seam between "which ROWS you see" and "which COLUMNS you
   see". Both sets of controls share one row now; without a divider they read as
   a single long strip of buttons where half of them do something quite
   different from the other half. */
.fbar-div {
  width: 0; align-self: center; height: 22px;
  border-left: 0.5px solid var(--border-default);
  margin: 0 4px;
}
/* The active preset's own name, read off the button inside the menu. Capped so
   an admin renaming a preset to something long cannot re-break the single row
   this control exists to protect. */
.fbar-cols-active {
  max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text-tertiary); font-weight: 400; font-size: 0.8rem;
}
.fbar-cols-menu { left: 0; right: auto; min-width: 190px; }
/* 10c — the search box, for the two bars that have one (Map, Scheduled View).
   Sized to sit level with the Filter button rather than inheriting .form-input's
   taller form-field padding, so the row reads as one strip of controls. */
/* Two classes on purpose: .form-input sets width:100% and is declared LATER in
   this file, so a single-class rule here loses and the box stretches the whole
   bar. Found in a screenshot, not in review. */
.fbar .fbar-search {
  width: 200px; flex: 0 0 auto; min-width: 140px;
  padding: 7px 12px; font-size: 0.85rem;
}
/* The Alerts summary chips ARE the preset row now, so they carry preset-row
   metrics — 5px/14px, 0.82rem — instead of the 10px/18px summary-tile padding
   and a 22px figure that made that one bar twice the height of the other six.
   The COLOURS stay: each chip's tint is its alert type, which is information a
   neutral pill would throw away, and it is the reason these were not simply
   rebuilt as .col-preset-btn. */
.fbar .alert-summary-strip { display: flex; gap: 8px; margin: 0; flex-wrap: wrap; }
.fbar .alert-summary-chip { padding: 5px 14px; gap: 6px; border-radius: 8px; }
.fbar .alert-summary-chip .chip-label { font-size: 0.82rem; }
.fbar .alert-summary-chip .chip-val { font-size: 0.82rem; font-family: var(--font-body); opacity: 0.75; }
.fbar .alert-chip-btn.chip-active { outline: 2px solid var(--cerio-blue); outline-offset: 1px; }
/* A trailing SORT control (Activity, Alerts). Sorts deliberately stay outside
   the Filter panel — a sort inside a filter is a lie about what the panel does
   — so they get the same right-hand slot Expand all uses. */
.fbar-trail .filter-select { padding: 7px 12px; font-size: 0.85rem; }
/* Stacked inside the menu: a wrapped flex row of pills reads as a shape, a
   column of them reads as a list of choices, which is what it is. */
.fbar-cols-menu .col-preset-row { flex-direction: column; align-items: stretch; gap: 2px; }
.fbar-cols-menu .col-preset-btn { text-align: left; width: 100%; }
.fbar-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 14px;
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  color: var(--text-primary);
  font-family: inherit; font-size: 0.85rem; font-weight: 500;
  cursor: pointer;
  transition: border-color var(--transition), color var(--transition);
}
.fbar-btn:hover { border-color: var(--cerio-blue); }
.fbar-btn-active { border-color: var(--cerio-blue); color: var(--cerio-blue); }
.fbar-caret { font-size: 0.7rem; opacity: 0.7; }
.fbar-count {
  min-width: 18px; padding: 0 5px; border-radius: 9px;
  background: var(--cerio-blue); color: #fff;
  font-size: 0.7rem; line-height: 18px; text-align: center;
}
.fbar-panel {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 60;
  width: min(680px, calc(100vw - 32px));
  max-height: 70vh; overflow-y: auto;
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius);
  box-shadow: 0 8px 28px rgba(0,0,0,0.18);
  padding: 12px 14px 14px;
}
.fbar-panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding-bottom: 8px; margin-bottom: 10px;
  border-bottom: 0.5px solid var(--border-default);
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-tertiary);
}
.fbar-clear { padding: 4px 10px; font-size: 0.78rem; }
.fbar-panel-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 10px;
}
/* A dimension inside the panel: header, then its own scrolling option list.
   Each list scrolls SEPARATELY rather than the panel scrolling as a whole, so a
   sixty-customer filter cannot bury the ten dimensions underneath it. */
.ms-inline {
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--surface-page);
  padding: 8px 9px 6px;
  min-width: 0;
}
.ms-inline-active { border-color: var(--cerio-blue); }
.ms-inline-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-tertiary); margin-bottom: 5px;
}
.ms-inline-head span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ms-inline-opts { max-height: 168px; overflow-y: auto; }
/* 21g/21i — type-to-narrow box inside a multi-select (cfg.search), both renderings */
.ms-inline .ms-search, .ms-panel .ms-search { width: 100%; margin: 4px 0 6px; padding: 5px 8px; font-size: 0.8rem; }
.ms-inline .ms-nomatch, .ms-panel .ms-nomatch { font-size: 0.78rem; opacity: 0.7; padding: 4px 2px; }
.ms-inline .ms-opt { padding: 4px; font-size: 0.82rem; }

/* Active filters. This row is what makes hiding the dimensions safe: a filter
   you cannot see is a filter you forget you set. */
.fbar-chips { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
.fbar-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px;
  border: 0.5px solid var(--cerio-blue);
  border-radius: 999px;
  background: var(--cerio-blue-tint);
  color: var(--text-primary);
  font-family: inherit; font-size: 0.78rem;
  cursor: pointer;
}
.fbar-chip:hover { border-color: var(--red); }
.fbar-chip-k { color: var(--text-tertiary); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em; }
.fbar-chip-x { color: var(--text-tertiary); font-size: 0.7rem; }
.fbar-chip:hover .fbar-chip-x { color: var(--red); }
.fbar-chip-clear { border-color: var(--border-default); background: none; color: var(--text-secondary); }
.fbar-chip-clear:hover { border-color: var(--text-secondary); }
@media (max-width: 700px) {
  .fbar-panel { width: calc(100vw - 32px); }
  .fbar-trail { margin-left: 0; }
}

.table-wrap {
  background: var(--surface-card);
  border-radius: var(--radius);
  border: 0.5px solid var(--border-default);
  box-shadow: none;
  overflow: auto;
  max-width: 100%;
  width: 100%;
}

.proj-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.82rem;
}

.proj-table thead {
  background: var(--surface-subtle);
  border-bottom: 2px solid var(--border-default);
}

.proj-table th {
  /* H2a: micro-caps header — smaller and firmer, so the header reads as a
     label strip and the DATA is the loudest thing in the table. */
  padding: 9px 10px 8px;
  text-align: left;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-tertiary);
  white-space: normal;
  vertical-align: bottom;
  line-height: 1.25;
}

.proj-table th.sortable { cursor: pointer; user-select: none; }
.proj-table th.th-center { text-align: center; }
.proj-table th.sortable:hover { color: var(--cerio-blue); }
.sort-icon { font-size: 10px; opacity: 0.5; margin-left: 2px; }

.proj-table td {
  padding: 11px 10px;
  border-bottom: 0.5px solid var(--border-default);
  vertical-align: middle;
  color: var(--text-primary);
  /* H2a: figures line up column-to-column instead of wandering. */
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.proj-table tr:last-child td { border-bottom: none; }

.proj-table tbody tr:hover { background: var(--cerio-blue-tint-dark); }

/* Zebra striping: every other row picks up the same subtle tone as the table
   header so long rows are easier to track. Painted on td (not tr) so it renders
   reliably; hover is also on td so it still wins over the stripe. */
/* NOTE: :nth-child() CANNOT be used here. Every data row is followed by a
   hidden expand row (.proj-alloc-expand-row / .inv-expand-row), so the visible
   rows all share the same parity and nth-child(even) matched only hidden rows.
   The render loop therefore tags alternating VISIBLE rows with .row-zebra. */
.proj-table tbody tr.row-zebra td { background: var(--surface-subtle); }
/* Hover must come after the stripe so it wins at equal specificity. */
/* 2026-10-16 (Chris): Cerio blue, not the warm grey. --cerio-blue-tint-dark is #E6EBFB on light
   and #222B47 on dark, so one token serves both themes and nothing bright happens on dark. It is
   a step deeper than --cerio-blue-tint so it still beats the zebra stripe (--surface-subtle),
   which every other row wears. THIS is the rule that actually applies — it paints a td. */
.proj-table tbody tr:hover td { background: var(--cerio-blue-tint-dark); }

/* ALERTS column: pills stack vertically and share one width so the column
   reads as a tidy list. Replaces the old whole-row colour tinting. */
.proj-table td.cell-alerts {
  white-space: normal;
  max-width: none;
  padding-top: 6px;
  padding-bottom: 6px;
  text-align: center;   /* centres the pill stack in the column */
}
/* Shrink-to-fit wrapper: an inline-block collapses to the width of its widest
   child, so the pills below (width:100%) all match the LONGEST label rather
   than stretching to fill the whole column. Equal size, no dead space. */
/* 03y (Chris: "text overlap on the right"): the stack is shrink-to-fit around its widest
   pill and had NO upper bound, so when the column narrows — the preview panel open, or a
   smaller window — the pills kept their full intrinsic width and were clipped mid-word,
   reading as text spilling into the next column. MEASURED: with the panel open the cell is
   201px while the stack wants ~240px. Bounded to the cell, and the pill text wraps rather
   than clips; at full width the sizer still gives every pill the longest label's width, so
   nothing wraps and the equal-width look is unchanged. */
.cell-alerts-stack { display: inline-block; vertical-align: middle; max-width: 100%; }
/* Specificity note: the base .alert-pill rule (display:inline-flex) is declared
   LATER in this file, and both selectors are single-class, so source order used
   to win and these pills stayed inline-flex. That made the container's intrinsic
   width the SUM of the pills instead of the widest one, so two-alert rows were
   roughly twice as wide as they should be. Scoping to .cell-alerts-stack raises
   specificity to (0,2,0) so display:block reliably applies regardless of order. */
.cell-alerts-stack .table-alert-pill {
  display: block;
  width: 100%;
  box-sizing: border-box;
  text-align: center;
  font-size: 0.72rem;        /* matches .badge (Fully Allocated etc.) */
  letter-spacing: 0.04em;    /* matches .badge */
  padding: 2px 8px;          /* matches .badge */
  margin: 3px 0;
  white-space: normal;      /* 03y: wrap when the column is narrow — never clip mid-word */
  line-height: 1.25;
}
.cell-alerts-stack .table-alert-pill:first-child { margin-top: 0; }
.cell-alerts-stack .table-alert-pill:last-child  { margin-bottom: 0; }
/* The sizer contributes WIDTH (longest label + pill padding) but no height,
   so every stack — and every width:100% pill inside — matches the longest
   label across ALL rows. Scales naturally with the display-size setting. */
/* In the table, alert pills use the SAME flat colour language as the .badge
   pills (Fully Allocated, Quote Issued): no border, badge background/tones.
   Scoped to the table stack — the record-page alert pills keep their look. */
.cell-alerts-stack .table-alert-pill { border: 0.5px solid color-mix(in srgb, currentColor 40%, transparent); }
.cell-alerts-stack .alert-pill-red    { background:var(--sev-high-bg); color:var(--sev-high-text); border:0.5px solid var(--sev-high-border); }
.cell-alerts-stack .alert-pill-orange { background:var(--alert-conflict-bg); color:var(--alert-conflict-text); border:0.5px solid var(--alert-conflict-border); }
.cell-alerts-stack .alert-pill-yellow { background:var(--sev-med-bg); color:var(--sev-med-text); border:0.5px solid var(--sev-med-border); }
.cell-alerts-stack .alert-pill-blue   { background:var(--alert-qtymm-bg); color:var(--alert-qtymm-text); border:0.5px solid var(--alert-qtymm-border); }
/* Dark-mode variants mirror [data-theme="dark"] .badge-* so both pill families stay in step.
   (No dark .badge-orange exists; derived from the same palette family.) */

.cell-alerts-stack .table-alert-sizer {
  visibility: hidden;
  height: 0;
  margin: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-top: 0;
  border-bottom: 0;
  overflow: hidden;
}

/* Circled "?" that opens the Probability Criteria reference. Sits inline with
   the field label in both the edit form and the record view. */
.prob-help-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; padding: 0; margin-left: 5px;
  border-radius: 99px; border: 0.5px solid var(--cerio-blue);
  background: transparent; color: var(--cerio-blue);
  font-size: 10px; font-weight: 500; line-height: 1;
  cursor: pointer; vertical-align: middle;
  transition: all var(--transition);
}
.prob-help-btn:hover { background: var(--cerio-blue); border-color: var(--cerio-blue); color: #fff; }

/* Probability criteria table — readable in the modal and on the Training page. */
.prob-criteria-table { width: 100%; border-collapse: collapse; }
.prob-criteria-table td, .prob-criteria-table th { vertical-align: top; }
.prob-criteria-table td:first-child { white-space: nowrap; }

/* ── Admin page section headers ──────────────────────────────────
   Groups the User Administration page into labelled sections. Blue to match
   the accent used for headings elsewhere; the rule underneath separates each
   group visually without adding another card border. */
.admin-section-title {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 500;
  color: var(--cerio-blue);            /* Cerio blue, same accent as .view-title */
  margin: 52px 0 4px;              /* generous gap ABOVE each new section */
  padding-bottom: 8px;
  border-bottom: 2px solid var(--border-default);
}
.admin-section-title:first-of-type { margin-top: 8px; }
.admin-section-sub {
  font-size: 0.85rem;
  color: var(--cerio-blue);            /* subtitle shares the blue, per request */
  opacity: 0.85;                   /* slightly softer so the header still leads */
  margin: 0 0 16px;
  max-width: 90ch;
  line-height: 1.5;
}
.admin-section-sub strong { color: var(--cerio-blue); font-weight: 500; }

.table-empty {
  text-align: center;
  color: var(--text-tertiary);
  padding: 48px !important;
  font-size: 0.9rem;
}

.col-actions {
  /* 06s (Chris): icons align from the RIGHT edge and grow leftward, so a row
     with an attachment or a filled heart no longer shifts the edit/delete
     icons relative to the rows above it. Was width:80px + centered, which
     re-centred a variable-width icon group on every row. */
  width: 1%;                 /* shrink-to-fit: the table gives the rest to content */
  white-space: nowrap;
  text-align: right !important;
  position: sticky;
  right: 0;
  background: var(--surface-card);
  box-shadow: none;
  z-index: 2;
}
.proj-table thead .col-actions {
  background: var(--surface-subtle);
  text-align: right !important;
}
/* Even spacing inside the right-aligned group, whatever icons a row has. */
.col-actions .action-btn { margin-left: 4px; }
.col-actions .action-btn:first-child { margin-left: 0; }
.proj-table tbody tr:hover .col-actions {
  background: var(--surface-subtle);
}

.table-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 14px;
  font-size: 0.82rem;
  color: var(--text-tertiary);
}

.pagination { display: flex; gap: 6px; }


/* ── Badges ── */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: 99px;
  /* Border derives from each badge's own text colour (works for every
     badge-* variant and both themes without per-colour rules). */
  border: 0.5px solid color-mix(in srgb, currentColor 40%, transparent);
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

/* superseded below by the severity-token definition */
.badge-yellow { background:var(--sev-med-bg); color:var(--sev-med-text); }
.badge-red    { background:var(--sev-high-bg); color:var(--sev-high-text); }
.badge-slate  { background: #e2e8f0; color: #475569; }
.badge-orange { background:var(--sev-med-bg); color:var(--sev-med-text); }
/* 01zg (Chris): the Partners flag in Division Fields — yellow so it reads apart from Active green */
.badge-yellow { background:#fef08a; color:#854d0e; }

/* ── Row Action Buttons ── */
.action-btn {
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 5px 7px;
  border-radius: var(--radius-sm);   /* H2a: was a hardcoded 4px */
  color: var(--text-tertiary);
  transition: all var(--transition);
  display: inline-flex;
  align-items: center;
}

.action-btn:hover { background: var(--surface-page); color: var(--cerio-blue); }
.action-btn.del:hover { color: var(--red); }

/* ═══════════════════════════════════════════════════════
   MODAL
═══════════════════════════════════════════════════════ */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(28,35,51,0.65);
  backdrop-filter: blur(3px);
  z-index: var(--z-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.modal {
  background: var(--surface-card);
  border-radius: var(--radius);   /* H2a: was a hardcoded 14px */
  width: 100%;
  max-width: 860px;
  max-height: 90vh;
  max-height: 90dvh;
  overflow-y: auto;
  box-shadow: var(--shadow-lg);   /* H2a: modals sit above everything */
}

.modal-sm { max-width: 440px; }

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 24px 15px;
  border-bottom: 0.5px solid var(--border-default);
  position: sticky;
  top: 0;
  background: var(--surface-card);
  z-index: 1;
}

.modal-title {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 700;              /* H3b: display weight, Horizon scale */
  letter-spacing: -0.015em;
  color: var(--text-primary);
}

.modal-close {
  background: transparent;
  border: none;
  font-size: 18px;
  cursor: pointer;
  color: var(--text-tertiary);
  /* H3b: ONE padding/radius declaration. This rule briefly carried two of each
     (a duplicate pair above the originals), so the later hardcoded 4px silently
     won and the token radius never applied — the same shape of bug that killed
     H2a's shadows. Hit area kept at the larger of the two; shrinking a close
     button is the wrong direction. */
  padding: 4px 9px;
  border-radius: var(--radius-sm);
  line-height: 1;
  transition: color var(--transition), background var(--transition);
}

.modal-close:hover { color: var(--red); background: var(--surface-subtle); }

.modal-body { padding: 20px 24px; }

.form-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

.form-group { display: flex; flex-direction: column; gap: 5px; justify-content: flex-end; }
/* Long labels may wrap to two lines (e.g. Radiator: Max Ambient Air Temp. (°C));
   bottom-justifying each cell keeps the inputs aligned across the row. */
.span-2 { grid-column: span 2; }
.span-3 { grid-column: span 3; }

.form-label {
  font-size: 0.78rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-tertiary);
}

.req { color: var(--cerio-blue); }

.form-input {
  padding: 9px 12px;
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm);
  font-size: 0.88rem;
  background: var(--surface-card);
  color: var(--text-primary);
  font-family: var(--font-body);
  transition: border-color var(--transition);
  width: 100%;
}

.form-input:focus {
  outline: none;
  border-color: var(--cerio-blue);
  box-shadow: 0 0 0 2px var(--cerio-blue-tint);   /* focus ring — the one shadow the guide permits */
}

/* H2a: the app already has a global keyboard focus outline (Theme Guide s13,
   near the top of this file) — it needs no help. The ONE place it fails is the
   horizon bar: a --cerio-blue outline on the blue gradient is invisible. Only
   that gap is patched here. */
.hz-mods > .nav-item:focus-visible, .hz-iconbtn:focus-visible,
.hz-avatar:focus-visible, .hz-more-btn:focus-visible, .hamburger:focus-visible,
.hz-cmd:focus-visible {
  outline: 2px solid rgba(255,255,255,0.85);
  outline-offset: 2px;
}

.form-error {
  background: #fef2f2;
  color: var(--red);
  border: 0.5px solid #fecaca;
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  font-size: 0.84rem;
  margin-top: 14px;
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 0;
  padding: 14px 24px 16px;
  border-top: 0.5px solid var(--border-default);
}

/* H3b: the footer RAIL — page tone behind the actions, and it stays put while
   a long body scrolls. Deliberately scoped to `.modal > .modal-footer`, i.e.
   a real dialog's own action bar. `.modal-footer` is reused as a plain
   button row in places that are NOT dialogs — inside .modal-body (delete
   confirm, change password), inside the proposal template import panel, and
   in two in-page footers built in main.js — where a page-tone band and a
   viewport-sticky bar would both be wrong. Those keep the plain treatment. */
.modal > .modal-footer {
  background: var(--surface-page);
  position: sticky;
  bottom: 0;
  z-index: 1;
  border-radius: 0 0 var(--radius) var(--radius);
}
/* The full-window edit pages portal the modal into the page: it has no
   max-height and no scroll of its own, so `sticky; bottom:0` there would pin
   the Save bar to the VIEWPORT and float it over the form. Those pages keep a
   normal footer that scrolls with the content. */
#view-project-edit-page .modal > .modal-footer,
#view-inventory-edit-page .modal > .modal-footer {
  position: static;
  background: transparent;
  border-radius: 0;
}

/* ═══════════════════════════════════════════════════════
   BUTTONS
═══════════════════════════════════════════════════════ */
.btn-primary {
  /* Guide s5: "Primary CTA: solid Cerio Blue background". Was --kpi-blue. */
  background: var(--cerio-blue);
  color: #fff;
  border: none;
  border-radius: var(--radius-sm);
  padding: 9px 20px;
  font-size: 0.86rem;
  font-weight: 600;              /* H2a: a button should read as a button */
  letter-spacing: 0.005em;
  cursor: pointer;
  transition: all var(--transition);
  font-family: var(--font-body);
}

.btn-primary:hover { background: var(--cerio-blue-dark); }

.btn-ghost {
  background: transparent;
  color: var(--text-secondary);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm);
  padding: 8px 18px;
  font-size: 0.88rem;
  font-weight: 500;
  cursor: pointer;
  transition: all var(--transition);
  font-family: var(--font-body);
}

.btn-ghost:hover { border-color: var(--text-secondary); color: var(--text-primary); }

/* 09j — THE DANGER BUTTON USES THE BADGE RED, NOT THE TEXT INK.
   Chris asked for it to match the alerts bubble in the top bar. Measuring it
   turned that into a defect report: --red is a TEXT ink, and dark mode flips it
   to #FF8B8B, a pale pink chosen for type ON dark surfaces. White text on that
   fill measures 2.25:1 — the SAME colour and the SAME number as the nav-badge
   bug fixed in 06r, still live here on "Move to Deleted" and "Run Cleanup".
   --badge-red #D32F2F is deliberately not re-declared under [data-theme=dark],
   so one value serves both: white on it = 4.98:1 (AA), and the button reads
   3.46:1 against the dark modal surface and 4.98:1 against the light one, both
   above the 3:1 floor for a non-text control. Hover #C62828: 5.62:1 text,
   3.06:1 against dark. A text ink is not a fill colour — this is the third time
   that sentence has had to be written down. */
.btn-danger {
  background: var(--badge-red);
  color: #fff;
  border: none;
  border-radius: var(--radius-sm);
  padding: 9px 20px;
  font-size: 0.88rem;
  font-weight: 500;
  cursor: pointer;
  transition: background var(--transition);
  font-family: var(--font-body);
}

.btn-danger:hover { background: #C62828; }

/* ═══════════════════════════════════════════════════════
   ADMIN
═══════════════════════════════════════════════════════ */
.admin-info {
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius);
  padding: 18px 22px;
  margin-bottom: 20px;
  font-size: 0.88rem;
  color: var(--text-secondary);
  line-height: 1.6;
  box-shadow: none;
}

/* ═══════════════════════════════════════════════════════
   TOAST
═══════════════════════════════════════════════════════ */
.toast {
  position: fixed;
  bottom: 28px;
  right: 28px;
  background: var(--surface-dark);
  color: #fff;
  padding: 12px 20px;
  border-radius: var(--radius-sm);
  font-size: 0.88rem;
  font-weight: 500;
  z-index: var(--z-toast);
  box-shadow: var(--shadow-lg);   /* H2a */
  max-width: 320px;
  animation: slideUp 0.22s ease;
}

.toast.success { border-left: 4px solid var(--green); }
.toast.error   { border-left: 4px solid var(--red); }
.toast.info    { border-left: 4px solid var(--cerio-blue); }

@keyframes slideUp {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ═══════════════════════════════════════════════════════
   HAMBURGER + DRAWER OVERLAY (H1b) — mobile navigation.
   Desktop hides the hamburger; ≤700px it opens the pill rail
   as a slide-in drawer (rules in the ≤700px media block).
═══════════════════════════════════════════════════════ */
.hamburger {
  display: none;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 8px 6px;
  flex-direction: column;
  gap: 5px;
  flex-shrink: 0;
}
.hamburger span {
  display: block;
  width: 22px;
  height: 2px;
  background: rgba(255,255,255,0.9);
  border-radius: 2px;
}

.hz-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.55);
  z-index: calc(var(--z-page-chrome) + 1);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.hz-overlay.open {
  opacity: 1;
  pointer-events: all;
}
/* 06p: the drawer lives INSIDE .hz-bar, and .hz-bar is position:sticky with
   z-index:100 — that is a stacking context, so .hz-mods's z-index:102 is
   resolved inside it and the whole header composites at 100. The overlay is a
   SIBLING of the header at 101, so it painted above the drawer and swallowed
   every tap: the drawer opened, looked dimmed, and no destination could be
   picked (soak 06n/06o F3). Raising the header itself is the fix — fighting
   z-index inside a stacking context cannot work. */
body.hz-drawer-open .hz-bar { z-index: calc(var(--z-page-chrome) + 3); }

/* ═══════════════════════════════════════════════════════
   RESPONSIVE — LARGE (≤1300px)
═══════════════════════════════════════════════════════ */
@media (max-width: 1300px) {
  .main-content { padding: 28px 26px; }
  .kpi-grid { grid-template-columns: repeat(3, 1fr) !important; }
}

/* 18s (Chris, landscape phone + narrow desktop window): this block was EMPTY —
   a placeholder nobody filled — and it is exactly the band where the top bar
   broke. Between the drawer breakpoint and full width the pill rail, the search
   pill and the icon cluster all wanted more room than the bar had; .hz-mods can
   shrink (min-width:0) but its children are nowrap, so they overflowed their box
   and drew straight over the search pill and the Alerts badge.
   Stage one: buy room by tightening. Stage two (≤1024) hands over to the drawer. */
@media (max-width: 1180px) {
  .hz-bar { gap: 8px; padding: 0 12px; }
  .hz-mods { gap: 0; }
  .hz-mods > .nav-item { padding: 7px 9px; font-size: 0.76rem; }
  .hz-cmd { width: auto; }               /* icon + label, no fixed 230px */
  .hz-cmd kbd { display: none; }         /* the Ctrl-K hint is the first thing to go */
  .hz-brand-sub { display: none; }
}

/* ═══════════════════════════════════════════════════════
   RESPONSIVE — TABLET (≤900px)
═══════════════════════════════════════════════════════ */
@media (max-width: 900px) {
  .main-content { padding: 24px 16px; }
  .form-grid { grid-template-columns: repeat(2, 1fr); }
  .span-3 { grid-column: span 2; }
  .kpi-grid { grid-template-columns: repeat(2, 1fr) !important; }
}

/* ═══════════════════════════════════════════════════════
   RESPONSIVE — THE TOP BAR HANDS OVER TO THE DRAWER (≤1024px)
   18s: these rules USED TO LIVE AT ≤700px, which is why a landscape phone
   (~900–1000 CSS px) and a narrow desktop window kept the pill rail and
   overlapped. They are MOVED here, not duplicated — the ≤700 block below keeps
   only what is genuinely a small-phone concern (partner branding, layout), so
   there is still one source of truth for the drawer.
   Measured: five pills + brand + search + four icons need ~1050px to sit apart.
═══════════════════════════════════════════════════════ */
@media (max-width: 1024px) {
  /* The hamburger replaces the pill rail; the rail becomes a slide-in drawer,
     with the More menu rendered inline inside it so every destination is one
     flat list — badges and role classes intact. */
  .hamburger { display: flex; }
  .hz-brand { margin-right: auto; }   /* the drawer is out of flow; brand spaces */
  .hz-cmd { width: auto; }
  .hz-cmd-text, .hz-cmd kbd { display: none; }
  .hz-mods {
    margin-right: 0;
    position: fixed; top: 0; left: 0; bottom: 0;
    width: 264px; padding: 14px 10px;
    flex-direction: column; align-items: stretch; gap: 2px;
    background: var(--surface-card);
    overflow-y: auto; overscroll-behavior: contain;
    transform: translateX(-100%);
    transition: transform 0.25s ease;
    z-index: calc(var(--z-page-chrome) + 2);
  }
  body.hz-drawer-open .hz-mods { transform: translateX(0); box-shadow: var(--shadow-lg); }
  .hz-mods > .nav-item {
    display: flex; width: 100%; text-align: left; border-radius: var(--radius-sm);
    padding: 11px 12px; font-size: 0.88rem;
    color: var(--text-secondary);
  }
  .hz-mods > .nav-item:hover { background: var(--surface-subtle); color: var(--text-primary); }
  .hz-mods > .nav-item.active { background: var(--cerio-blue-tint); color: var(--cerio-blue); box-shadow: none; }
  .hz-mods > .nav-item .nav-badge { margin-left: auto; }
  .hz-more { flex-direction: column; }
  .hz-more-btn { display: none; }
  .hz-more .hz-menu {
    /* !important: the .hidden utility is display:none !important — in the
       drawer the More menu is ALWAYS inline, whatever its toggle state. */
    display: block !important; position: static; min-width: 0; padding: 0;
    background: transparent; border: none; box-shadow: none; border-radius: 0;
  }
  .hz-menu .nav-item { padding: 11px 12px; font-size: 0.88rem; }
}

/* ═══════════════════════════════════════════════════════
   RESPONSIVE — MOBILE (≤700px)
═══════════════════════════════════════════════════════ */
@media (max-width: 700px) {
  /* 18s: the hamburger, the drawer and the search collapse MOVED UP to the
     ≤1024px block above (a landscape phone needs them too) — H1b's original
     intent, at the width where it actually applies. What stays here is what is
     genuinely a small-phone concern. */
  /* 06q: a partner logo and the full "Cerio / INVENTORY PLANNING" lockup do not
     both fit at 375px — with the logo rendering (it used to collapse to 0x0 and
     hide the problem) the avatar pushed 13px past the right edge. Partners keep
     the Cerio mark plus their own logo; the wordmark goes. Internal users are
     untouched, and the logo is capped so a wide one cannot overflow either. */
  body.external-mode .hz-brand-text { display: none; }
  .partner-brand-box img { height: 22px !important; max-width: 64px !important; }

  /* Main content fills full width below the sticky bar */
  .main-content {
    padding: 16px 12px;
  }

  /* KPIs 2×2 */
  .kpi-grid { grid-template-columns: repeat(2, 1fr) !important; }
  .kpi-card { padding: 14px 16px; }
  .kpi-value { font-size: 26px; }
  .kpi-sub { font-size: 0.72rem; }

  .inv-kpi-strip { gap: 10px; }
  .inv-kpi-val { font-size: 22px; }
  .inv-kpi { padding: 12px 14px; min-width: calc(50% - 5px); }
  .inv-kpi .kpi-label { font-size: 0.68rem; }

  /* Charts stack */

  /* View header stacks */
  .view-header { flex-direction: column; align-items: flex-start; gap: 10px; }
  .view-actions { width: 100%; flex-wrap: wrap; gap: 8px; }
  .search-input { flex: 1; min-width: 0; }

  /* Filter bar */
  .filter-bar { flex-wrap: wrap; gap: 6px; }
  .filter-select { font-size: 0.82rem; padding: 6px 8px; }

  /* Tables stay as scrollable rows */
  .table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  /* Modals slide up from bottom */
  .modal-overlay { padding: 0 !important; align-items: flex-end !important; height: 100vh; height: 100dvh; }
  .modal { max-width: 100% !important; max-height: 92vh; max-height: 92dvh; border-radius: 16px 16px 0 0 !important; padding-bottom: env(safe-area-inset-bottom); }
  .modal-sm { max-width: 100% !important; border-radius: 16px 16px 0 0 !important; }
  .modal-body { padding: 16px; }
  .modal-header { padding: 16px 16px 12px; }
  .modal-footer { flex-wrap: wrap; gap: 8px; }

  /* Form single col */
  .form-grid { grid-template-columns: 1fr !important; }
  .span-2, .span-3 { grid-column: span 1 !important; }

  /* Delivery / alloc add rows */
  .delivery-add-row { flex-direction: column !important; gap: 8px; }
  .delivery-add-row > * { width: 100% !important; }

  /* View-only single col */
  .view-only-grid { grid-template-columns: 1fr !important; }
  .view-section.span-2 { grid-column: span 1 !important; }

  /* Conflict & unallocated */
  .conflict-item { flex-wrap: wrap; gap: 8px; }
  .conflict-days { font-size: 20px; width: auto; }

  /* 08i: the mobile overrides for .inv-avail-months / -count / -label are gone
     with the 24-month rollup they sized. */

  /* Alloc meter */
  .alloc-meter-wrap { flex-direction: column; align-items: flex-start; max-width: 100%; }

  /* Login */
  .login-card { padding: 28px 20px; }

  /* Table footer */
  .table-footer { flex-direction: column; gap: 8px; }

  /* iOS: inputs under 16px trigger Safari's focus auto-zoom — hold them at 16px */
  .form-input, .filter-select, select.form-input, textarea.form-input, .search-input, .ms-btn {
    font-size: 16px;
  }

  /* Project KPI strip: 2-up like the inventory strip */
  .proj-kpi-card { min-width: calc(50% - 5px); }

  /* 08h: the phone overrides for the grouped supply/demand charts are gone with
     the charts. Meter rows are already one-per-line and need no phone variant. */
}

@media (max-width: 420px) {
  .kpi-grid { grid-template-columns: 1fr !important; }
  .inv-kpi-strip .inv-kpi { min-width: 100%; }
}

/* ── Email/Password Login Form ── */
.login-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-bottom: 20px;
  text-align: left;
}

.btn-login-submit {
  width: 100%;
  padding: 12px;
  font-size: 0.95rem;
  margin-top: 4px;
}

/* ═══════════════════════════════════════════════════════
   FILE ATTACHMENTS
═══════════════════════════════════════════════════════ */
.files-section {
  margin-top: 24px;
  padding-top: 20px;
  border-top: 0.5px solid var(--border-default);
}

/* 03g — the curve chart links: pointer + underline affordance on its title, pointer
   on batch nodes. SVG text takes text-decoration; circles just need the cursor. */
.tl-canvas [data-tl-proj], .tl-canvas [data-tl-inv] { cursor: pointer; }
.tl-canvas [data-tl-proj]:hover { text-decoration: underline; }
.tl-canvas [data-tl-inv]:hover { stroke: var(--cerio-blue, #1A3F9E); stroke-width: 2px; }

/* 03b — the Curves mode: a card per project, the record page's timeline stacked. */
/* 21c (Chris: "Can we make these views smaller? … we can shrink them down to about half their current
   size. We could probably even have two columns. If we click on one, it should open it into a bigger
   image.") — the cards were a COLUMN, so one 900-wide drawing stretched across the whole page and
   magnified everything with it: a 15px title renders near 25px on a 1500px screen, which is why the
   view looked oversized. Two columns put each card back near the drawing's own width, which IS the
   "half size" asked for and costs no redraw — the SVG carries a viewBox and width:100%, so it simply
   stops being blown up. One column again below 1000px, where two would be narrower than the drawing
   was ever meant to go. */
/* 21g: TWO, pinned — auto-fit fitted a third at 1600px (cards of 500, the chart down to 0.56 of its own
   size), and two is both what was asked for and the arithmetic he described: full width was 1.67x, so
   half of that is 0.83x, which is what two columns give on a 1600px window. */
.sched-curves { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; align-items: start; }
@media (max-width: 1000px) { .sched-curves { grid-template-columns: 1fr; } }
.sv-curve-card { cursor: zoom-in; }
/* what is separately clickable inside keeps its own cursor */
.sv-curve-card .mini-flow-bar, .sv-curve-card [data-tl-proj], .sv-curve-card [data-tl-inv] { cursor: pointer; }
.sv-curve-card .tl-legend { font-size: 0.66rem; gap: 10px; }
.sv-curve-card {
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 12px 16px 8px;
}

/* 03a (Chris, screenshot 103137): the inventory EDIT modal's Attachments sat flush
   against the left edge while the record view draws the section as a card — the
   editor matches the view now. Border stays with the .files-section rules so the
   drag-over dashed highlight keeps working. */
#inv-files-section {
  background: var(--surface-card);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 17px 19px;
}

.files-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 14px;
}

.file-upload-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  font-size: 0.82rem;
  padding: 6px 14px;
}

.file-input-hidden {
  display: none;
}

.files-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.files-empty {
  font-size: 0.85rem;
  color: var(--text-tertiary);
  padding: 16px;
  text-align: center;
  background: var(--surface-subtle);
  border-radius: var(--radius-sm);
  border: 0.5px dashed var(--border-default);
}

.file-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  background: var(--surface-subtle);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm);
  transition: border-color var(--transition);
}

.file-item:hover {
  border-color: var(--cerio-blue);
}

.file-icon {
  flex-shrink: 0;
  width: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.file-info {
  flex: 1;
  min-width: 0;
}

.file-name {
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-meta {
  font-size: 0.75rem;
  color: var(--text-tertiary);
  margin-top: 2px;
}

.file-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

.file-upload-progress {
  margin-top: 10px;
  font-size: 0.83rem;
  color: var(--cerio-blue);
  font-weight: 500;
  padding: 8px 12px;
  background: var(--cerio-blue-tint);
  border-radius: var(--radius-sm);
}

/* ═══════════════════════════════════════════════════════
   DELIVERY SCHEDULE
═══════════════════════════════════════════════════════ */
.delivery-add-row {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  margin-bottom: 14px;
  flex-wrap: wrap;
}

.delivery-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.delivery-field .form-input { width: 100%; }
.delivery-field-label {
  font-size: 0.66rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-tertiary);
}

.delivery-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 12px;
}

.delivery-item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 14px;
  background: var(--surface-subtle);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm);
  transition: border-color var(--transition), background var(--transition);
}

/* H3a: hover shifts the surface as well as the border, so the row you are on is
   obvious without relying on a 0.5px colour change. */
.delivery-item:hover { border-color: var(--cerio-blue); background: var(--surface-card); }

.delivery-date {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--text-primary);
  width: 140px;
  flex-shrink: 0;
}
.delivery-date svg { flex-shrink: 0; }

.delivery-units { flex-shrink: 0; }

.delivery-notes {
  flex: 1;
  font-size: 0.82rem;
  color: var(--text-tertiary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.delivery-summary {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 14px;
  background: var(--cerio-blue-tint);
  border: 0.5px solid rgba(249,115,22,0.2);
  border-radius: var(--radius-sm);
  font-size: 0.83rem;
  color: var(--text-secondary);
  margin-top: 4px;
}

.delivery-summary strong { color: var(--text-primary); }

/* ═══════════════════════════════════════════════════════
   INVENTORY
═══════════════════════════════════════════════════════ */
.inv-kpi-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 20px;
}

.inv-kpi {
  flex: 1;
  min-width: 150px;
  background: var(--surface-card);
  border-radius: var(--radius);
  padding: 16px 20px;
  box-shadow: none;
  border: 0.5px solid var(--border-default);
  border-left: 4px solid var(--text-secondary);
}


.inv-kpi-val {
  font-family: var(--font-display);
  font-size: 34px;
  font-weight: 500;
  color: var(--text-primary);
  line-height: 1;
  margin-top: 6px;
}


/* Allocation meter */
.alloc-meter-wrap {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  max-width: 300px;
}

.alloc-meter-track {
  flex: 1;
  height: 8px;
  background: var(--surface-subtle);
  border-radius: 99px;
  overflow: hidden;
  border: 0.5px solid var(--border-default);
}

.alloc-meter-fill {
  height: 100%;
  border-radius: 99px;
  transition: width 0.4s ease, background 0.4s ease;
}
@media (prefers-reduced-motion: reduce) { .alloc-meter-fill { transition: none; } }

.alloc-meter-label {
  font-size: 0.78rem;
  font-weight: 500;
  white-space: nowrap;
  color: var(--text-secondary);
}

/* Dashboard two-column layout */


/* ═══════════════════════════════════════════════════════
   INVENTORY AVAILABILITY CHART
═══════════════════════════════════════════════════════ */
.inv-avail-chart {
  width: 100%;
}

.proj-inv-flow {
  width: 100%;
  overflow-x: auto;
}
.proj-inv-flow .flow-node:hover rect {
  stroke-width: 1.5;
}

/* 08i: the .inv-avail-months / -col / -count / -bar-wrap / -bar / -bar-empty /
   -bar-now / -label / -label-now / -footer rules are DELETED. They belonged to
   renderInvTotalChart, the 24-month rollup Chris retired; the condensed strip
   that remains uses its own .avail-* family. Deleted rather than left orphaned,
   so nothing here looks load-bearing when it is not. .inv-avail-chart (above)
   SURVIVES — it is still the strip's container class. */

/* ═══════════════════════════════════════════════════════
   CONFLICT ALERTS
═══════════════════════════════════════════════════════ */

.conflict-item {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 12px 16px;
  border-radius: var(--radius-sm);
  margin-bottom: 8px;
  border: 0.5px solid;
}

.conflict-body {
  flex: 1;
  min-width: 0;
}

.conflict-project {
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--text-primary);
  margin-bottom: 2px;
}

.conflict-detail {
  font-size: 0.78rem;
  color: var(--text-tertiary);
  font-family: var(--font-body);
}

.conflict-days {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 500;
  text-align: center;
  line-height: 1;
  flex-shrink: 0;
  width: 52px;
  padding-top: 2px;
}

.conflict-days-high { color: var(--red); }
.conflict-days-med  { color: var(--amber); }

/* Dashboard KPI grid — fluid: fits as many columns as the width allows */
.kpi-grid {
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)) !important;
}


/* ── Sort indicators ── */
.proj-table th.sortable,
.inv-sortable {
  cursor: pointer;
  user-select: none;
  transition: color var(--transition);
  white-space: nowrap;
}

.proj-table th.sortable:hover,
.inv-sortable:hover {
  color: var(--cerio-blue);
}

.sort-icon {
  font-size: 10px;
  opacity: 0.5;
  margin-left: 3px;
  transition: opacity var(--transition);
}

th[style*="color: var(--cerio-blue)"] .sort-icon,
th[style*="color:var(--cerio-blue)"] .sort-icon {
  opacity: 1;
}

/* Make both tables scroll horizontally on wide data */
/* proj-table min-width removed — table now scrolls within wrapper */
#inv-table  { min-width: 1000px; }


/* Completed rows render like any other row (no dim / strikethrough) for readability. */
.row-complete td {
  opacity: 1;
}

/* Conflict clickable links */
.conflict-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: transparent;
  border: none;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: inherit;
  color: inherit;
  padding: 0;
  text-align: left;
  transition: color var(--transition);
}

.conflict-link:hover {
  color: var(--cerio-blue);
  text-decoration: underline;
}

.conflict-link svg {
  flex-shrink: 0;
  opacity: 0.5;
}

.conflict-link:hover svg {
  opacity: 1;
}


/* ═══════════════════════════════════════════════════════
   VIEW-ONLY MODALS
═══════════════════════════════════════════════════════ */
.view-only-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}

/* ══ 07m — RECORD PAGE: chips, KPI cards, the paired lists ══════════════
   The record used to open on a twelve-field spec wall with the four numbers
   and the two working lists below the fold. These three components put the
   numbers and the lists first. They are defined once and used by BOTH the
   full-page record and the modal fallback, because both render from
   buildInventoryViewHTML(). */
.rec-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 16px; }
.rec-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 0.78rem; font-weight: 600;
  padding: 4px 11px; border-radius: 99px;
  background: var(--surface-subtle);
  border: 0.5px solid var(--border-default);
  color: var(--text-secondary);
}
.rec-chip b { color: var(--text-primary); font-weight: 700; }

.rec-kpis {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px; margin-bottom: 16px;
}
.rec-kpi {
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-left: 3px solid var(--cerio-blue);   /* same family as .adm-card / .set-panel */
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 14px 16px 15px;
}
/* 08f — TYPOGRAPHY MATCHED TO .kpi-value / .kpi-label (Chris).
   08d shipped these at weight 700 with -0.02em tracking, which matched NOTHING
   else: the app already had two figure treatments — .kpi-value (dashboard) in
   the display face at 500, and .proj-kpi-val plus ~25 other sites in a mono
   face at 500 — and this was a third. Exactly the drift 07i existed to remove
   between Settings and User Admin, except self-inflicted.
   The mono face was the better structural match at the time (the delivery
   dates and costs directly beneath these cards were mono too), but the woff2
   the app shipped had a DOTTED ZERO and no way to change it, so Chris's call
   was "A unless the dot stays" and this became B: the dashboard treatment.
   12f settled the wider question by removing the mono family altogether, so
   the second option no longer exists — this rule is now simply correct. */
.rec-kpi-label {
  font-size: 0.75rem; font-weight: 500; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 5px;
}
/* 09r — tabular-nums REMOVED, and it is the whole bug Chris reported twice.
   08f set the family correctly (display face at 500, matching .kpi-value) and
   then this ONE extra line quietly undid the look of it. Plus Jakarta Sans
   draws a DIFFERENT '1' for tabular figures — a stem with a full base serif,
   widened to fill the fixed advance — and that serifed 1 is what reads as
   "monospace". Measured, not guessed: the same string is 294.8px tabular vs
   257.2px proportional, so the glyphs genuinely differ.
   Why Inventory looked right and Projects looked wrong: same rule on both.
   Only the '1' is redrawn; 0/2/6/9 are identical either way. An inventory
   record showing 202 / 6 / 0 / 196 has one '1' in it, a project record showing
   1 / 1 / 0 of 1 / 1 is almost nothing but 1s.
   The comment this replaces claimed the digits align across the four cards.
   They never did — the cards are four separate boxes with independently
   positioned, left-aligned values, and tabular figures align a COLUMN of
   stacked numbers, which these are not. Nothing real was lost.
   CLOSED IN 12j, AND THE NOTE THAT USED TO SIT HERE WAS WRONG. It said every
   OTHER tabular-nums in this stylesheet was inert. Fifteen of the seventeen
   were. TWO WERE NOT: .dash-alert-row b and .dash-alerts-total span:first-child
   on the dashboard Alerts tile both set font-family: var(--font-display) on the
   SAME LINE as tabular-nums — this exact pairing, in a place nobody looked.
   Measured in Chromium rather than reasoned about: the same digit string ran
   201.6px tabular against 160.4px proportional on those two, and identical to
   the pixel on the other fifteen.
   So this was NOT the only site where the property did anything. It was the
   only site anyone had checked. All seventeen declarations are now gone: the
   fifteen because they did nothing, and the two because they did the thing
   Chris reported twice. The Alerts tile is on the partner dashboard
   (PARTNER_VISIBLE), so the two were live for external users as well.
   THE LESSON, not the fix: a note recording "I checked and these are inert" is
   worth less than the measurement that produced it, because the next person
   inherits the claim without the method. The measurement is a browser probe
   over every declaration, and it is what found the two. */
.rec-kpi-val {
  font-family: var(--font-display); font-size: 1.9rem; font-weight: 500;
  color: var(--text-primary); line-height: 1.05;
}
.rec-kpi-sub { font-size: 0.76rem; color: var(--text-tertiary); margin-top: 3px; }
/* Available Today at zero. Driven by the SAME availClass the badge used here
   before, so the card and any other surface reading that value cannot
   disagree about when the item is exhausted. */
/* 08d — VERDICT COLOURS ON THE RECORD KPI CARDS (Chris).
   The 07c rule — neutral means nothing wrong, so any colour is a signal — was
   written for LISTS (the hero, Recently Viewed, Favorites), where you scan many
   records and a wall of green buries the two reds. A record page is the
   opposite: one project, one yes/no question, nothing to triage against. So a
   card that HAS a verdict shows it in both directions; a card that is just a
   number stays neutral. Colour still means something — here it means "verdict",
   and only the verdict cards carry one. */
.rec-kpi.is-low  { border-left-color: var(--red); }
.rec-kpi.is-low  .rec-kpi-val { color: var(--red); }
.rec-kpi.is-good { border-left-color: var(--green); }
.rec-kpi.is-good .rec-kpi-val { color: var(--green); }

/* Supply on the left, demand on the right — the pair you read against each
   other. Stacks below 1000px, where two columns would squeeze the delivery
   date lines inside each row. */
.rec-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px; }
@media (max-width: 1000px) { .rec-pair { grid-template-columns: 1fr; } }

.view-section {
  /* H3a: a record's sections are objects you read, so they sit on the page as
     cards like everything else in Horizon — was a flat grey box on a grey page,
     which left the section edges hard to find in dark mode. */
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 17px 19px;
}

.view-section.span-2 { grid-column: span 2; }

.view-section-title {
  /* H3a: tightened to the Horizon micro-caps scale. The BLUE stays — Chris's
     2026-07-23 decision that section titles carry the brand colour. */
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--cerio-blue);
  margin-bottom: 12px;
  padding-bottom: 8px;
  border-bottom: 0.5px solid var(--border-default);
}

.view-fields {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.view-field {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.view-field-label {
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--text-tertiary);
  min-width: 100px;
  /* Was flex-shrink:0, which meant a long label (e.g. USABLE FUEL CAPACITY
     (HOURS)) claimed the whole row and squeezed the VALUE until it broke
     mid-number ("24" -> 2/4, "$27,200,000.00" -> $27,200,/000.00). Letting the
     label wrap instead keeps values intact — they are the important part. */
  flex-shrink: 1;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.view-field-val {
  font-size: 0.88rem;
  color: var(--text-primary);
}

/* 13e — the circled question mark beside a label that needs explaining ("Still matches this
   record?" is the first one). It rides INSIDE .view-field-label, which is uppercase and
   letter-spaced, so it resets both: a letter-spaced "?" sits off-centre in its own circle.
   Sized in em so it tracks the label, and vertical-align holds it on the cap line. */
.help-dot {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.25em; height: 1.25em; margin-left: 5px; padding: 0;
  border: 1px solid var(--border-strong); border-radius: 50%;
  background: transparent; color: var(--text-tertiary);
  font-size: 0.92em; font-weight: 600; line-height: 1;
  text-transform: none; letter-spacing: 0; vertical-align: -0.18em;
  cursor: pointer; flex-shrink: 0;
}
.help-dot:hover, .help-dot:focus-visible {
  border-color: var(--text-secondary);
  color: var(--text-primary);
  background: var(--surface-subtle);
}

/* Clickable rows */
.proj-table tbody tr:hover,
#inv-table tbody tr:hover {
  background: var(--cerio-blue-tint-dark);
  cursor: pointer;
}


/* ═══════════════════════════════════════════════════════
   UNALLOCATED PROJECTS ALERT
═══════════════════════════════════════════════════════ */


/* Expand KPI grid for extra cards — fluid */
.kpi-grid {
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)) !important;
}


/* ── New tab elements ── */
.nav-badge { display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 4px;border-radius:99px;background:var(--badge-red);color:#fff;font-size:10px;font-weight:500;margin-left:auto;line-height:1; }
.kpi-card-link { cursor:pointer;border:none;text-align:left;width:100%;font-family:inherit;transition:all var(--transition); }
.kpi-card-link:hover { border-color: var(--text-primary); }
.kpi-card-link .kpi-sub { color:var(--cerio-blue);font-weight:500; }
.alert-summary-strip { display:flex;gap:12px;margin-bottom:20px;flex-wrap:wrap; }
.alert-summary-chip { display:flex;align-items:center;gap:10px;padding:10px 18px;border-radius:var(--radius);border:0.5px solid; }
.alert-chip-btn { cursor:pointer;font-family:inherit;font-size:inherit;transition:all var(--transition); }
.alert-chip-btn:hover { border-color: var(--text-primary); }
.alert-chip-btn.chip-active { outline:2px solid var(--cerio-blue);outline-offset:2px; }
.chip-label  { font-size:0.78rem;font-weight:500; }
.chip-val    { font-family:var(--font-display);font-size:22px;font-weight:500; }
.alert-meta-chip { display:inline-flex;align-items:center;gap:3px;font-size:0.76rem;font-weight:500;color:var(--text-secondary);background:var(--surface-card);border:0.5px solid var(--border-default);border-radius:4px;padding:2px 7px;white-space:nowrap; }
.badge-yellow { background:var(--sev-med-bg); color:var(--sev-med-text); }
.status-lost    { background:#fee2e2;color:#b91c1c; }
.status-expired { background:#fef9c3;color:#a16207; }
.status-active  { background:var(--surface-subtle);color:var(--text-tertiary); }

/* ── Request Access ── */
.link-btn {
  background: transparent;
  border: none;
  color: var(--cerio-blue);
  cursor: pointer;
  font-size: inherit;
  font-family: inherit;
  padding: 0;
  text-decoration: underline;
  font-weight: 500;
}

.link-btn:hover { color: var(--cerio-blue-dark); }

.req-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 0;
  border-bottom: 0.5px solid var(--border-default);
  flex-wrap: wrap;
}

.req-item:last-child { border-bottom: none; }

.req-info { flex: 1; min-width: 0; }

.req-name { font-size: 0.9rem; margin-bottom: 3px; }

.req-meta {
  font-size: 0.8rem;
  color: var(--text-tertiary);
  line-height: 1.5;
}

.req-actions { display: flex; gap: 8px; flex-shrink: 0; }

.form-success { color:var(--green);font-size:0.85rem;padding:6px 10px;background:#f0fdf4;border:0.5px solid #86efac;border-radius:6px; }
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes ai-flash {
  0%   { background: #fef9c3; }
  100% { background: transparent; }
}

/* ── Custom dropdown + add option button ─────────────── */
.custom-select-wrap {
  display: flex;
  gap: 6px;
  align-items: center;
}
.custom-select-wrap select {
  flex: 1;
}
.btn-add-option {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 6px;
  border: 0.5px solid var(--border-default);
  background: var(--surface-subtle);
  color: var(--cerio-blue);
  font-size: 1.2rem;
  font-weight: 500;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all var(--transition);
  line-height: 1;
}
.btn-add-option:hover {
  background: var(--cerio-blue);
  color: #fff;
  border-color: var(--cerio-blue);
}

/* ── Quote Tool ──────────────────────────────────────── */
.quote-tool-wrap {
  max-width: 700px;
}

/* H3b: this is a SEGMENTED CONTROL — a track with a moving thumb — not two
   loose chips, so the track gets the pill radius too and the thumb sits inside
   it concentrically. (First pass gave only the buttons a 99px radius, which
   put a pill inside a 14px box and read as a mistake.) */
.quote-mode-bar {
  display: flex;
  gap: 8px;
  margin-bottom: 24px;
  background: var(--surface-subtle);
  border: 0.5px solid var(--border-default);
  border-radius: 99px;
  padding: 4px;
}

.quote-mode-btn {
  flex: 1;
  padding: 10px 16px;
  border: none;
  border-radius: 99px;
  background: transparent;
  color: var(--text-secondary);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  transition: all var(--transition);
  font-family: inherit;
}
.quote-mode-btn:hover:not(.active) { color: var(--text-primary); }

.quote-mode-btn.active {
  background: var(--cerio-blue);
  color: #fff;
  box-shadow: none;
}

.quote-card {
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius);
  padding: 28px 28px 24px;
  margin-bottom: 20px;
  box-shadow: none;
}

.quote-card-title {
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-tertiary);
  margin-bottom: 20px;
}

.quote-fields {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin-bottom: 28px;
}

.quote-field-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.quote-label {
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--text-secondary);
}

.quote-input {
  font-size: 1rem !important;
}

.quote-results {
  border-top: 0.5px solid var(--border-default);
  padding-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.quote-result-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: var(--surface-subtle);
  border-radius: var(--radius-sm);
  border: 0.5px solid var(--border-default);
}

.quote-result-label {
  font-size: 0.85rem;
  color: var(--text-secondary);
  font-weight: 500;
}

.quote-result-val {
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 500;
  color: var(--text-primary);
}

.quote-result-highlight {
  color: var(--cerio-blue);
  font-size: 1.1rem;
}

.quote-result-profit {
  color: var(--green);
}

.quote-defs {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 14px 18px;
  background: var(--surface-subtle);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm);
}

.quote-def {
  font-size: 0.8rem;
  color: var(--text-tertiary);
}

.quote-def strong {
  color: var(--text-secondary);
}

@media (max-width: 600px) {
  .quote-fields { grid-template-columns: 1fr; }
  /* H3b: stacked on a phone the track is tall, and a 99px radius there reads as
     a capsule rather than a control — the segmented pill shape is a
     side-by-side idea only. */
  .quote-mode-bar { flex-direction: column; border-radius: var(--radius); }
}

/* ── Project KPI Strip ───────────────────────────────── */
.proj-kpi-strip {
  display: flex;
  gap: 10px;
  margin-bottom: 14px;
  flex-wrap: wrap;
}
.proj-kpi-card {
  container-type: inline-size;
  flex: 1;
  min-width: 110px;
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius);
  padding: 12px 14px;
  box-shadow: var(--shadow);   /* H3a: same elevation rule as the dashboard cards */
  border-left: 3px solid var(--cerio-blue);
}

/* Elements external (partner) users must never see — e.g. internal sales value.
   external-mode is set on <body> by applyExternalGating() for external users. */
body.external-mode .hide-external { display: none !important; }
.proj-kpi-label {
  font-size: 0.7rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-tertiary);
  margin-bottom: 4px;
}
/* NO font-family HERE ON PURPOSE. This rule carried `font-family: var(--font-mono)`
   until 12f and it never once took effect: the theme-alignment block further down
   (search .proj-kpi-val) names this selector in a list that sets var(--font-display)
   — same specificity, later in the file, so it wins. The project KPI figures have
   been Plus Jakarta Sans for as long as both rules have existed, which is why they
   look identical before and after 12f. Removed rather than repointed, because a
   declaration that loses every time is a lie about where this element gets its
   face. To change it, change the theme-alignment block — not this rule. */
.proj-kpi-val {
  font-size: clamp(0.85rem, 8cqi, 1.4rem);
  white-space: nowrap;
  font-weight: 500;
  color: var(--text-primary);
  line-height: 1.2;
}
.proj-kpi-sub {
  font-size: 0.7rem;
  color: var(--text-tertiary);
  margin-top: 2px;
}

/* ── Project Map ──────────────────────────────────────────── */
.map-wrap {
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius);
  box-shadow: none;
  overflow: hidden;
}
#project-map {
  width: 100%;
  height: calc(100vh - 190px);
  min-height: 420px;
  background: var(--surface-subtle);
}
.leaflet-container { font-family: var(--font-body), system-ui, sans-serif; }
.leaflet-popup-content { margin: 10px 12px; font-size: 0.85rem; color: var(--text-primary); }
.map-popup-title { font-weight: 500; color: var(--text-primary); margin-bottom: 2px; }
.map-popup-loc   { color: var(--text-secondary); font-size: 0.78rem; margin-bottom: 6px; }
.map-popup-proj  { padding: 4px 0; border-top: 0.5px solid var(--border-default); line-height: 1.3; }
.map-popup-proj .cust { color: var(--text-tertiary); font-size: 0.76rem; }
/* 27s — the popup's project name is the way in */
.map-popup-open { border: none; background: none; padding: 0; font: inherit; color: var(--cerio-blue); cursor: pointer; text-align: left; }
.map-popup-open:hover { text-decoration: underline; }
.map-marker-count {
  background: var(--cerio-blue); color: #fff; border-radius: 50%;
  width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
  font-weight: 500; font-size: 0.8rem; box-shadow: none;
  border: 2px solid #fff;
}

/* ══ 19r — the map color-by legend ══ */
.map-legend { display: flex; flex-wrap: wrap; gap: 6px 12px; margin: 8px 2px; }
.map-legend-chip { display: inline-flex; align-items: center; gap: 5px;
  font-size: 0.76rem; color: var(--text-secondary); }
.map-legend-dot { width: 11px; height: 11px; border-radius: 50%; flex: 0 0 11px;
  border: 1px solid rgba(255, 255, 255, 0.6); }

/* ── New status badges ────────────────────────────────────── */
.status-active    { background:#e0e7ff; color:#3730a3; }
.status-execution { background:#ffedd5; color:#b45309; }
.status-delivered { background:#ccfbf1; color:#0f766e; }

/* ── Completed Projects status sections ───────────────────── */
/* 03y (Chris, second photo): the projects table's top-LEFT corner curved away from the
   division tab strip above it, so the card and the active PowerGen tab did not meet. The
   strip starts at the table's left edge, so that corner is square — the same treatment the
   record's division panel already uses (.div-card.rec-div-panel: radius 0 8px 8px 8px).
   Scoped to the projects layout: inventory has the same shape, but Chris asked about this
   table and its strip is a separate decision. */
#proj-ins-layout .table-wrap { border-top-left-radius: 0; }

.comp-section-row td {
  background: var(--surface-subtle);
  color: var(--text-primary);
  border-left: 3px solid var(--cerio-blue);
  border-top: 2px solid var(--border-default);
  font-size: 0.92rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  /* 03y (Chris, marked on the left edge): the section label sat 9px right of the PROJECT
     header and the row names. MEASURED: the header and the cells both put content 10px in,
     while this cell put it at 19px — its own 3px accent border PLUS 16px of padding. The
     left padding is 7px so border + padding lands on the same 10px as everything else. */
  /* 04c: 7px put the label at x=47 while the row names sit at 48.5 and the PROJECT
     header at 49 — a residual ~2px Chris could still see after the first pass. MEASURED
     with a Range on the text nodes, not from the cell padding, which is what missed it:
     the 3px accent border plus 9px lands the glyphs on 49, level with the header. */
  padding: 10px 16px 10px 9px;
}
.comp-section-count {
  display: inline-block;
  margin-left: 8px;
  background: var(--surface-dark);
  color: #fff;
  border-radius: 10px;
  font-size: 0.72rem;
  font-weight: 500;
  padding: 1px 9px;
  vertical-align: middle;
}
.comp-section-note {
  margin-left: 10px;
  font-weight: 500;
  font-size: 0.74rem;
  color: var(--text-tertiary);
  text-transform: none;
  letter-spacing: 0;
}

/* ── Open Projects: rows colour-coded by alert type ───────── */
   /* red  — past-due delivery */

  /* orange — timing conflict */

 /* yellow — no inventory */


.alert-legend {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 18px;
  margin: 4px 2px 12px;
  font-size: 0.78rem;
  color: var(--text-secondary);
}
.alert-legend-label { font-weight: 500; color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.72rem; }
.alert-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.alert-swatch { width: 14px; height: 14px; border-radius: 3px; border: 0.5px solid rgba(0,0,0,0.12); display: inline-block; }
.sw-pastdue     { background:#fecaca; }
.sw-conflict    { background:#ffedd5; }
.sw-unallocated { background:#fef9c3; }

/* ── Scheduled View (delivery matrix) ─────────────────────── */
.schedule-wrap {
  overflow: auto;
  max-height: calc(100vh - 235px);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius);
  box-shadow: none;
  background: var(--surface-card);
}
.schedule-table { border-collapse: separate; border-spacing: 0; font-size: 0.82rem; width: max-content; min-width: 100%; }
.schedule-table th, .schedule-table td {
  border-right: 0.5px solid var(--border-default);
  border-bottom: 0.5px solid var(--border-default);
  padding: 6px 10px;
  white-space: nowrap;
}
/* sticky header (two rows) */
.schedule-table thead th { position: sticky; background: var(--surface-subtle); z-index: calc(var(--z-sticky-header) + 2); }
.schedule-table thead tr:first-child th { top: 0; height: 30px; }
.schedule-table thead tr:nth-child(2) th { top: 31px; }
.sched-year  { text-align: center; font-weight: 500; color: var(--text-primary); }
.sched-month { text-align: center; font-weight: 500; color: var(--text-secondary); min-width: 48px; }
/* sticky first column */
.sched-corner, .sched-proj {
  position: sticky; left: 0; background: var(--surface-card); z-index: 2;
  text-align: left; min-width: 190px; max-width: 240px;
}
.schedule-table thead .sched-corner { z-index: calc(var(--z-sticky-header) + 3); background: var(--surface-subtle); vertical-align: middle; }
.sched-proj-name { font-weight: 500; color: var(--text-primary); white-space: normal; line-height: 1.2; }
.sched-proj-cust { font-size: 0.72rem; color: var(--text-tertiary); white-space: normal; }
/* 04p — which machine this row is, shown only after the first: the division is already named
   by the lens tab, so repeating it on every row would be noise, but "PowerGen 2" beside the
   name is the only thing distinguishing two rows of one project. */
.sched-proj-prod { font-size: 0.7rem; font-weight: 600; letter-spacing: 0.02em; padding: 1px 5px;
                   border-radius: 4px; background: var(--surface-subtle); color: var(--text-secondary);
                   white-space: nowrap; }
.sched-cell { text-align: center; font-family: var(--font-body); color: var(--text-secondary); }
.sched-cell.has-units { background: var(--cerio-blue-tint); font-weight: 500; color: var(--text-primary); }
.sched-total-col, .sched-rowtotal { font-weight: 500; background: var(--surface-subtle); color: var(--text-primary); }
/* current-month emphasis */
.sched-cell.sched-current { background: rgba(245,158,11,0.10); }
.sched-cell.sched-current.has-units { background: rgba(245,158,11,0.28); }
.schedule-table thead th.sched-current { background: #fde68a; color: var(--text-primary); }
/* totals footer pinned to bottom */
.sched-foot { position: sticky; bottom: 0; background: var(--surface-subtle); font-weight: 500; color: var(--text-primary); z-index: calc(var(--z-sticky-header) + 1); }
.sched-foot.sched-proj { z-index: calc(var(--z-sticky-header) + 2); }
.schedule-table tbody tr { cursor: pointer; }
.schedule-table tbody tr:hover td:not(.has-units):not(.sched-rowtotal) { background: var(--surface-subtle); }

/* ── Scheduled View: info columns, stage sections, issue rows ── */
.sched-info-head { text-align: center; font-weight: 500; color: var(--text-secondary); }
.sched-info { text-align: center; color: var(--text-secondary); font-size: 0.8rem; min-width: 90px; }
.sched-section-cell {
  position: sticky; left: 0;
  background: var(--surface-dark); color: #fff;
  font-weight: 500; font-size: 0.8rem; letter-spacing: 0.03em;
  padding: 7px 14px; text-align: left; z-index: 1;
}
/* whole-row issue colour-coding */
.schedule-table tbody tr.sched-alert-pastdue     td, .schedule-table tbody tr.sched-alert-pastdue     th.sched-proj { background:#fecaca; }
.schedule-table tbody tr.sched-alert-pastdue     td.has-units { background:#f87171; }
.schedule-table tbody tr.sched-alert-conflict    td, .schedule-table tbody tr.sched-alert-conflict    th.sched-proj { background:#ffedd5; }
.schedule-table tbody tr.sched-alert-conflict    td.has-units { background:#fdba74; }
.schedule-table tbody tr.sched-alert-unallocated td, .schedule-table tbody tr.sched-alert-unallocated th.sched-proj { background:#fef9c3; }
.schedule-table tbody tr.sched-alert-unallocated td.has-units { background:#fde047; }
.schedule-table tbody tr.sched-alert-qtymismatch td, .schedule-table tbody tr.sched-alert-qtymismatch th.sched-proj { background:#dbe6fe; }
.schedule-table tbody tr.sched-alert-qtymismatch td.has-units { background:#93b4fd; }

/* ── Availability units summary / mismatch alert ──────────── */
.avail-summary { font-size: 0.8rem; font-weight: 500; padding: 3px 12px; border-radius: 999px; }
.avail-summary:empty { display: none; }
/* 21a — availability spreadsheet import row */
.avail-import-row { display: flex; align-items: center; gap: 10px; margin: 2px 0 10px; flex-wrap: wrap; }
.avail-import-hint { font-size: 0.78rem; opacity: 0.7; }
.avail-summary.ok      { background:#dcfce7; color:#15803d; }
.avail-summary.warn    { background:#fef9c3; color:#a16207; }
.avail-summary.over    { background:#fee2e2; color:#b91c1c; }
.avail-summary.neutral { background:var(--surface-subtle); color:var(--text-secondary); }

/* ── Role preview banner ──────────────────────────────────── */
.preview-banner {
  position: sticky;
  top: 0;
  z-index: var(--z-dropdown-inline);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  background: #fde68a;
  color: #713f12;
  border: 0.5px solid #f59e0b;
  border-radius: 8px;
  padding: 10px 16px;
  margin-bottom: 18px;
  font-size: 0.86rem;
  box-shadow: none;
}
.preview-banner strong { color: #713f12; }
.preview-banner button {
  background: #713f12;
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 6px 14px;
  font-size: 0.82rem;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
}
.preview-banner button:hover { background: #92500f; }

/* ═══════════════════════════════════════════════════════════
   CERIO THEME ALIGNMENT  (full design-system reskin)
   Overrides that bring the app in line with the Cerio Theme Guide.
═══════════════════════════════════════════════════════════ */

/* Fonts & weight restraint (guide: two fonts, max weight 500) */
body { font-family: var(--font-body); }
h1, h2, h3, h4, h5, h6, .view-title, .kpi-value, .proj-kpi-val, .chart-title, .modal-title, .login-title { font-family: var(--font-display); letter-spacing: -0.01em; }
/* Every weight in the codebase is now <= 500, so this no longer needs
   !important — it simply states the ceiling the guide requires. */
h1, h2, h3, h4, h5, h6, .view-title, .kpi-value, .proj-kpi-val, .modal-title, .login-title, strong, b, .nav-item.active, .btn-primary, .badge, th, .kpi-label { font-weight: 500; }

/* Hairline borders everywhere (guide s-borders). Unchanged. */
.kpi-card,.proj-kpi-card,.card,.chart-card,.admin-info,.table-wrap,.schedule-wrap,
.modal,.login-card,.filter-select,.form-input,.map-wrap,.view-section,
.delivery-item,.files-section { border-width: 0.5px !important; }

/* NO SHADOW — but only on the things that should genuinely stay flat.
   ─────────────────────────────────────────────────────────────────────
   H3a CORRECTION (2026-08-07d). This rule used to include .kpi-card,
   .proj-kpi-card, .chart-card, .modal, .login-card and .view-section, and it
   sits LATER in the file than their own definitions — so it silently beat the
   elevation H2a added, and H2a's headline change never actually rendered. It
   shipped to production looking unchanged. The old Theme-Guide "no shadows"
   line predates Horizon; H1a defined the shadow tokens and H2a/H3a adopt them
   deliberately, so those components are removed from this list. Inputs,
   wrappers, inline info panels and list rows stay flat on purpose — elevation
   is for objects, not for containers and rows. */
.card,.admin-info,.table-wrap,.schedule-wrap,
.filter-select,.form-input,.map-wrap,
.delivery-item,.files-section { box-shadow: none; }

/* Admin diagnostics strip + partner logo (03r). Uses THEME VARIABLES, not
   fixed colours: light is the default theme here and dark is the override, so
   hardcoded white text was invisible in light mode — which is exactly what
   happened on the first build of this strip. */
.diag-line { flex:1; min-width:0; font-size:0.63rem; line-height:1.3;
  color: var(--text-tertiary); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.diag-copy { flex:none; font-size:0.62rem; padding:2px 7px; background:transparent;
  border:0.5px solid var(--border-default); color: var(--text-secondary);
  border-radius:5px; cursor:pointer; }
.diag-copy:hover { border-color: var(--cerio-blue); color: var(--cerio-blue); }
.partner-brand-box { align-items:center; margin-top:-2px; padding:3px 7px; line-height:0;
  background:#fff; border:0.5px solid var(--border-default); border-radius:6px; }
/* 18d: a LIGHT partner logo (white artwork, transparent PNG) sits straight on the blue header — the white pill hid it */
.partner-brand-box.on-dark { background:transparent; border-color:transparent; }

/* ── Sidebar → white (guide) ── */
/* H1b: the old Theme-Guide white-sidebar overrides are gone — the horizon
   bar is the brand gradient in BOTH themes, and its menus use surface tokens. */

/* ── Login → off-white page, clean card (guide) ── */
.login-wrap { background: var(--surface-page); }
.login-card { background: var(--surface-card); border: 0.5px solid var(--border-default); }

/* ── KPI numbers: display font, weight 500, primary text ── */
.kpi-value { color: var(--text-primary); font-weight: 500; }

/* ── Buttons (guide) ── */
.btn-primary { background: var(--cerio-blue); color: #fff; border-radius: 6px; }
.btn-primary:hover { background: var(--cerio-blue-dark); }
.btn-ghost { border-radius: 6px; }

/* ── Severity / alert tints remapped to guide palette ── */
/* Open Projects rows */


/* Legend swatches */
.sw-pastdue     { background:#FCEBEB; border-color:#F0BABA; }
.sw-conflict    { background:#FBF3E1; border-color:#EFE0BC; }
.sw-unallocated { background:#F4F3EE; border-color:#E5E4DE; }
/* Schedule whole-row tints */
.schedule-table tbody tr.sched-alert-pastdue td,
.schedule-table tbody tr.sched-alert-pastdue th.sched-proj      { background:#FCEBEB; }
.schedule-table tbody tr.sched-alert-pastdue td.has-units       { background:#F6D6D6; }
.schedule-table tbody tr.sched-alert-conflict td,
.schedule-table tbody tr.sched-alert-conflict th.sched-proj     { background:#FBF3E1; }
.schedule-table tbody tr.sched-alert-conflict td.has-units      { background:#F3E3BE; }
.schedule-table tbody tr.sched-alert-unallocated td,
.schedule-table tbody tr.sched-alert-unallocated th.sched-proj  { background:#F4F3EE; }
.schedule-table tbody tr.sched-alert-unallocated td.has-units   { background:#E9E7DF; }
.schedule-table tbody tr.sched-alert-qtymismatch td,
.schedule-table tbody tr.sched-alert-qtymismatch th.sched-proj  { background:#E7EDFA; }
.schedule-table tbody tr.sched-alert-qtymismatch td.has-units   { background:#CBD9F2; }
/* has-units accent (non-alert) → blue tint */
.sched-cell.has-units { background: var(--cerio-blue-tint); color: var(--text-primary); }
.sched-cell.sched-current { background:#FBF3E1; }
.schedule-table thead th.sched-current { background:#FBF3E1; color: var(--text-primary); }
/* Schedule stage section header → subtle surface + blue accent (not near-black) */
.sched-section-cell { background: var(--surface-subtle); color: var(--text-primary); border-left: 3px solid var(--cerio-blue); }

/* Availability summary pill → guide tints */
.avail-summary.ok      { background:#F2F8EA; color:#3B6D11; }
.avail-summary.warn    { background:#FBF3E1; color:#854F0B; }
.avail-summary.over    { background:#FCEBEB; color:#791F1F; }
.avail-summary.neutral { background:var(--surface-subtle); color:var(--text-secondary); }

/* ── Preview banner → guide medium/amber tint (not saturated yellow) ── */
.preview-banner { background:#FBF3E1; color:#633806; border:0.5px solid #EFE0BC; box-shadow:none; }
.preview-banner strong { color:#633806; }
.preview-banner button { background:#633806; }
.preview-banner button:hover { background:#7a4708; }

/* ── Badges softened to guide tints ── */
.badge-green { background:var(--sev-low-bg); color:var(--sev-low-text); }
.badge-slate { background:#F4F3EE; color:#444441; }

/* Mobile top bar → white (guide: light header) */

/* Completed-section count badge → blue tint */
.comp-section-count { background: var(--cerio-blue-tint); color: var(--cerio-blue); }

/* ═══════════════════════════════════════════════════════════
   THEME TOGGLE + DARK MODE
═══════════════════════════════════════════════════════════ */
.theme-toggle { display:inline-flex; background:var(--surface-subtle); border:0.5px solid var(--border-default); border-radius:12px; padding:3px; gap:2px; }
.theme-opt { border:none; background:transparent; color:var(--text-secondary); font-family:var(--font-body); font-size:0.82rem; font-weight:500; padding:6px 18px; border-radius:9px; cursor:pointer; transition:all var(--transition); }
.theme-opt.active { background:var(--surface-card); color:var(--cerio-blue); border:0.5px solid var(--border-default); }

/* ── Dark palette (tokens) ── */
[data-theme="dark"] {
  /* 07h — the counterpart to `color-scheme: light` on :root. This is what makes
     the browser paint every native control in its dark form. See the note there. */
  color-scheme: dark;

  /* H1a "Command Center": dark is DESIGNED, not inverted — cool charcoal-blue
     surfaces, lifted panels, luminous status inks. Warm-gray values retired. */
  --sev-high-bg: #331D1F; --sev-high-border: #5C3034; --sev-high-text: #FF9C9C;
  --sev-med-bg:  #322917; --sev-med-border:  #5A4823; --sev-med-text:  #EDC85C;
  --sev-low-bg:  #1B2C22; --sev-low-border:  #2E4D3A; --sev-low-text:  #7BD9A8;
  --cerio-blue:      #6E8BFF;                 /* lightened Cerio blue for contrast on dark */
  --cerio-blue-dark: #5877f0;
  --cerio-blue-tint: rgba(110,139,255,0.16);
  /* 01a: the division accents lifted for the dark ground, the same treatment --cerio-blue gets */
  --div-ups:        #B69BEE;  --div-ups-tint:  rgba(139,111,208,0.18);
  --div-switchgear: #5CD9A0;  --div-switchgear-tint: rgba(92,217,160,0.14);
  --div-mechanical: #EDC85C;  --div-mechanical-tint: rgba(237,200,92,0.14);
  --surface-dark: #0E1116;        /* dark-mode chrome surface */
  --text-primary:        #E9EBF2;
  --text-secondary:    #A2A7B4;
  --text-tertiary:  #838999;         /* 06m: was #7A8090 — 3.98:1 on cards, below AA. 4.50:1 at the tightest now. */
  --text-faint:  #575D6B;
  --surface-page:          #111318;
  --surface-card:     #181B22;
  --surface-subtle:   #1F232C;
  --border-default:      #262B35;
  --border-soft: #20242D;
  --border-strong:#39404E;
  --green:       #5CD9A0;
  --red:         #FF8B8B;
  --amber:       #EDC85C;
  --cerio-blue-tint-dark: #222B47;
  --shadow:      0 1px 2px rgba(0,0,0,.35), 0 12px 32px rgba(0,0,0,.4);
  --shadow-lg:   0 16px 48px rgba(0,0,0,.55);
}

/* Sidebar icon strokes follow text in dark */
[data-theme="dark"] .nav-item svg { stroke: var(--text-secondary); }

/* Toast (uses --slate which flips light in dark) */
[data-theme="dark"] .toast { background:#2E2E36; color:var(--text-primary); border:0.5px solid var(--border-default); }

/* Base badges */
[data-theme="dark"] .badge-slate  { background:#2A2A2E; color:#B8B7B0; }
[data-theme="dark"] .badge-yellow { background:#423a09; color:#facc15; }

/* Status badges */
[data-theme="dark"] .status-delivered { background:#163B36; color:#7FD3C4; }
[data-theme="dark"] .status-lost      { background:#3A2020; color:#E88C8C; }
[data-theme="dark"] .status-expired   { background:#33290F; color:#E0B15C; }
[data-theme="dark"] .status-active    { background:#23293F; color:#A9B8FF; }
[data-theme="dark"] .status-execution { background:#3A2E14; color:#E0B15C; }

/* Severity / alert tints — Open Projects rows */


/* Legend swatches */
[data-theme="dark"] .sw-pastdue     { background:#3A2020; border-color:#5A2E2E; }
[data-theme="dark"] .sw-conflict    { background:#33290F; border-color:#524015; }
[data-theme="dark"] .sw-unallocated { background:#2A2A2E; border-color:#42424C; }
/* Scheduled View rows */
[data-theme="dark"] .schedule-table tbody tr.sched-alert-pastdue td,
[data-theme="dark"] .schedule-table tbody tr.sched-alert-pastdue th.sched-proj      { background:#3A2020; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-pastdue td.has-units       { background:#4E2A2A; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-conflict td,
[data-theme="dark"] .schedule-table tbody tr.sched-alert-conflict th.sched-proj     { background:#33290F; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-conflict td.has-units      { background:#473814; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-unallocated td,
[data-theme="dark"] .schedule-table tbody tr.sched-alert-unallocated th.sched-proj  { background:#2A2A2E; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-unallocated td.has-units   { background:#35353B; }
[data-theme="dark"] .sched-cell.sched-current { background:#33290F; }
[data-theme="dark"] .schedule-table thead th.sched-current { background:#33290F; color:var(--text-primary); }

/* Availability summary pill */
[data-theme="dark"] .avail-summary.ok      { background:#1F3320; color:#8FD3A5; }
[data-theme="dark"] .avail-summary.warn    { background:#33290F; color:#E0B15C; }
[data-theme="dark"] .avail-summary.over    { background:#3A2020; color:#E88C8C; }

/* Preview banner */
[data-theme="dark"] .preview-banner { background:#33290F; color:#E6C877; border-color:#524015; }
[data-theme="dark"] .preview-banner strong { color:#E6C877; }
[data-theme="dark"] .preview-banner button { background:#E6C877; color:#1A1A1A; }
[data-theme="dark"] .preview-banner button:hover { background:#f0d488; }

/* "Disabled" user badge */
[data-theme="dark"] .badge[style*="fee2e2"] { background:#3A2020 !important; color:#E88C8C !important; }

/* ═══════════════════════════════════════════════════════════
   TWEAKS — muted chart tints, clearer alert colors, section headers
═══════════════════════════════════════════════════════════ */

/* Probability chart row tints → guide tints (light + dark) */

/* Alert palette — SINGLE SOURCE for pills AND legend swatches (2026-07-30e).
   Previously .alert-pill-orange and .alert-pill-yellow both used --sev-med-*,
   so conflict and unallocated pills rendered identically while the legend
   showed the intended distinct colors. Pills and swatches now share these
   vars, so they cannot drift apart again. */
:root {
  --alert-conflict-bg:#FBDCC0; --alert-conflict-border:#F0B27E; --alert-conflict-text:#8C4B10;
  --alert-qtymm-bg:#E4EBFB; --alert-qtymm-border:#B9CCF0; --alert-qtymm-text:#23407F;
}
[data-theme="dark"] {
  --alert-conflict-bg:#3E2E17; --alert-conflict-border:#5C4620; --alert-conflict-text:#F0B27E;
  --alert-qtymm-bg:#1B2437; --alert-qtymm-border:#2F4670; --alert-qtymm-text:#A3BCF0;
}
/* Chris 2026-07-30: ONLY the conflict pill changes color. Past-due (red) and
   unallocated (the original --sev-med yellow) keep their original appearance;
   30e recolored unallocated too, reverted in 30k. Swatches track the pills. */
.sw-conflict    { background:var(--alert-conflict-bg); border-color:var(--alert-conflict-border); }
.sw-unallocated { background:var(--sev-med-bg); border-color:var(--sev-med-border); }

/* Open Projects rows */


/* Scheduled View rows + has-units */
.schedule-table tbody tr.sched-alert-conflict td,
.schedule-table tbody tr.sched-alert-conflict th.sched-proj     { background:#FBDCC0; }
.schedule-table tbody tr.sched-alert-conflict td.has-units      { background:#F5C193; }
.schedule-table tbody tr.sched-alert-unallocated td,
.schedule-table tbody tr.sched-alert-unallocated th.sched-proj  { background:#FCF3B8; }
.schedule-table tbody tr.sched-alert-unallocated td.has-units   { background:#F6E98A; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-conflict td,
[data-theme="dark"] .schedule-table tbody tr.sched-alert-conflict th.sched-proj     { background:#3E2E17; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-conflict td.has-units      { background:#54401D; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-unallocated td,
[data-theme="dark"] .schedule-table tbody tr.sched-alert-unallocated th.sched-proj  { background:#3A3410; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-unallocated td.has-units   { background:#4E4718; }

/* Scheduled View: stage section headers slightly larger than rows */
.sched-section-cell { font-size: 0.92rem; font-weight: 500; letter-spacing: 0.01em; }


/* ═══════════════════════════════════════════════════════════
   Alerts tab — color-code cards + chips by TYPE
   (matches scheduled view / KPI palette; proper dark variants)
   past-due = red · timing conflict = orange · no inventory = yellow
═══════════════════════════════════════════════════════════ */
.conflict-item.alert-type-pastdue     { background:#FCEBEB; border-color:#F0BABA; }
.conflict-item.alert-type-conflict    { background:#FBDCC0; border-color:#F0B27E; }
.conflict-item.alert-type-unallocated { background:#FCF3B8; border-color:#ECDC6E; }
.conflict-item.alert-type-qtymismatch { background:#E4EBFB; border-color:#B9CCF0; }
/* 27b - the item kind borrows the qty-delivery blue: same arithmetic, one level up. */
.conflict-item.alert-type-inv_qtymismatch { background:#E4EBFB; border-color:#B9CCF0; }
[data-theme="dark"] .conflict-item.alert-type-pastdue     { background:#2E1A1A; border-color:#5A2E2E; }
[data-theme="dark"] .conflict-item.alert-type-conflict    { background:#3E2E17; border-color:#5C4620; }
[data-theme="dark"] .conflict-item.alert-type-unallocated { background:#2E2810; border-color:#524015; }
[data-theme="dark"] .conflict-item.alert-type-qtymismatch { background:#1B2437; border-color:#2F4670; }
[data-theme="dark"] .conflict-item.alert-type-inv_qtymismatch { background:#1B2437; border-color:#2F4670; }   /* 27b */

.alert-summary-chip.chip-pastdue     { background:#FCEBEB;border-color:#F0BABA;color:#791F1F; }
.alert-summary-chip.chip-conflict    { background:#FBDCC0;border-color:#F0B27E;color:#7A3D0F; }
.alert-summary-chip.chip-unallocated { background:#FCF3B8;border-color:#ECDC6E;color:#7A5F0A; }
.alert-summary-chip.chip-qtymismatch { background:#E4EBFB;border-color:#B9CCF0;color:#23407F; }
.alert-summary-chip.chip-inv_qtymismatch { background:#E4EBFB;border-color:#B9CCF0;color:#23407F; }   /* 27b */
[data-theme="dark"] .alert-summary-chip.chip-pastdue     { background:#2E1A1A;border-color:#5A2E2E;color:#F0B4B4; }
[data-theme="dark"] .alert-summary-chip.chip-conflict    { background:#3E2E17;border-color:#5C4620;color:#E8B583; }
[data-theme="dark"] .alert-summary-chip.chip-unallocated { background:#2E2810;border-color:#524015;color:#E6C877; }
[data-theme="dark"] .alert-summary-chip.chip-qtymismatch { background:#1B2437;border-color:#2F4670;color:#A3BCF0; }
[data-theme="dark"] .alert-summary-chip.chip-inv_qtymismatch { background:#1B2437;border-color:#2F4670;color:#A3BCF0; }   /* 27b */

/* Dark-mode fix for the dashboard Conflicts / No-Inventory cards (shared too-light bug) */

/* ═══════════════════════════════════════════════════════════
   Inventory split section headers + mobile fixes
═══════════════════════════════════════════════════════════ */
.inv-section-cell {
  background: var(--surface-subtle);
  color: var(--text-primary);
  border-left: 3px solid var(--cerio-blue);
  border-top: 2px solid var(--border-default);
  font-size: 0.92rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  padding: 10px 16px;
  text-align: left;
}

/* Mobile hamburger + close button: legible in light AND dark (was hardcoded white) */
/* H1b: hamburger lives on the gradient bar — white in both themes (base rule). */

/* Contain Leaflet's internal z-index so the map can't cover the nav/menus */
.map-wrap { position: relative; z-index: 1; }


/* ══ 08j — THE --chart-* TOKEN FAMILY IS DELETED ════════════════════════
   Was: --chart-blue #5E77B8 / --chart-green #6E9E82 / --chart-amber #BA7517,
   with dark steps, under the heading "Dashboard chart colors".
   NONE OF THE THREE RENDERED ANYWHERE. --chart-amber had no consumer at all;
   --chart-blue and --chart-green reached the screen only through `.bar-fill`
   and `.prob-high .bar-fill`, whose markup came from two functions nothing
   called. So the app carried a named chart palette that no chart used, which is
   worse than no palette: it invites the next person (it invited me, in 08i) to
   "align with the site's chart colours" by matching values nobody can see.
   The live chart colours are --cerio-blue for the availability strip, the alerts
   meter and row-expand unallocated; #94a3b8 for row-expand allocated; and
   --mix-demand / --mix-supply for the paired tiles. Those are the real palette
   and they are defined next to what they colour. */

/* Admin hub: section cards (slightly larger than dashboard KPI cards) */
.adm-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 12px;
}
/* 31g: hub group headers. 07i — their styling MOVED to the shared admin-family
   block further down this file (searched as "ONE ADMIN CARD FAMILY"), where it
   is defined once alongside .settings-grp. Two rules for one selector is how
   the Settings/User Admin divergence happened in the first place. */
.adm-card {
  display: flex; flex-direction: column; gap: 6px; text-align: left;
  padding: 16px 16px 18px;
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-left: 3px solid var(--cerio-blue);
  border-radius: var(--radius);
  cursor: pointer; font-family: inherit;
  box-shadow: var(--shadow);   /* H2a */
  transition: border-color var(--transition), background var(--transition), transform 0.12s, box-shadow 0.12s;
}
.adm-card:hover { background: var(--surface-subtle); border-color: var(--cerio-blue); transform: translateY(-1px); box-shadow: var(--shadow-lg); }
@media (prefers-reduced-motion: reduce) { .adm-card, .adm-card:hover { transform: none; transition: border-color var(--transition), background var(--transition); } }
/* 07i: .adm-card-title and .adm-card-sub are now defined ONCE, together with
   their Settings counterparts, in the shared admin-family block below. */
@media (max-width: 700px) { .adm-card-grid { grid-template-columns: 1fr; } }

/* 08h: the supply/demand grouped bar charts became single-series meter rows
   (.mix-row, in the D2 block). Every rule for the grouped chart, its two bar
   colours, its value labels and its six filter buttons is deleted rather than
   left orphaned — .sd-ph/.sd-ow/.sd-grp/.sd-bar/.sd-charts-row/.sd-chart-col/
   .sd-grouped-chart/.sd-bar-val/.sd-grp-label had no other consumer in the app.
   .sd-empty survives: drawMixMeters still uses it for the no-categories state. */
.sd-empty { color: var(--text-tertiary); font-size: 0.82rem; padding: 20px 0; }

/* ── Revenue Metrics tab ─────────────────────────────── */
.rev-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:16px; margin-top:16px; }
@media (max-width:900px){ .rev-grid { grid-template-columns:1fr; } }
.rev-table { width:100%; border-collapse:collapse; font-size:0.85rem; }
.rev-table th { text-align:left; font-family:var(--font-display); font-weight:500; font-size:0.72rem; text-transform:uppercase; letter-spacing:0.04em; color:var(--text-secondary); padding:6px 8px; border-bottom:0.5px solid var(--border-default); }
.rev-table td { padding:7px 8px; border-bottom:0.5px solid var(--border-default); color:var(--text-primary); }
.rev-table tbody tr:last-child td { border-bottom:none; }
.rev-table .rev-total td { font-weight:500; border-top:0.5px solid var(--border-default); border-bottom:none; }
.rev-num { text-align:right; font-family:var(--font-body); white-space:nowrap; }

/* ═══════════════════════════════════════════════════════
   TRAINING VIEW
═══════════════════════════════════════════════════════ */
.training-doc {
  max-width: 860px;
  color: var(--text-primary);
  line-height: 1.6;
  font-size: 0.95rem;
}
.training-lead {
  font-size: 1.02rem;
  color: var(--text-tertiary);
  margin: 0 0 8px;
}
.training-h {
  /* 07l: Horizon display scale, and the 2px rule drops to the app's hairline. */
  font-family: var(--font-display);
  font-size: 1.12rem;
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--text-primary);
  margin: 30px 0 10px;
  padding-bottom: 7px;
  border-bottom: 0.5px solid var(--border-default);
}
.training-h4 {
  font-family: var(--font-display);
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  margin: 18px 0 6px;
}
.training-doc p { margin: 0 0 12px; }
.training-list, .training-steps { margin: 0 0 12px; padding-left: 22px; }
.training-list li, .training-steps li { margin-bottom: 6px; }
.training-note-inline { color: var(--text-tertiary); font-size: 0.9rem; }

/* Definition lists */
.training-defs { margin: 0 0 14px; }
.training-defs dt {
  font-weight: 700;
  color: var(--cerio-blue);
  margin-top: 10px;
}
.training-defs dd {
  margin: 2px 0 0;
  padding-left: 0;
}

/* Callouts */
.training-callout {
  border-radius: var(--radius-sm);
  padding: 12px 16px;
  margin: 6px 0 16px;
  font-size: 0.92rem;
  border-left: 4px solid var(--border-strong);
  background: var(--surface-subtle);
}
/* 07l: all three carried hardcoded LIGHT-theme tints, so on dark they were an
   almost invisible wash in the wrong hue. They now use the same theme-aware
   tokens the alert pills use, which means they follow any future palette change
   instead of having to be found and edited again. */
.training-callout-important { border-left-color: var(--amber); background: var(--sev-med-bg); }
.training-callout-tip       { border-left-color: var(--cerio-blue); background: var(--cerio-blue-tint); }
.training-callout-key       { border-left-color: var(--cerio-blue); background: var(--cerio-blue-tint); }

/* Screenshot placeholders (replace inner div with <img> later) */
.training-shot { margin: 4px 0 16px; }
.training-shot img { max-width: 100%; height: auto; border: 0.5px solid var(--border-default); border-radius: var(--radius-sm); display: block; }
.training-shot-ph {
  border: 2px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-subtle);
  color: var(--text-tertiary);
  font-size: 0.85rem;
  padding: 22px 18px;
  text-align: center;
  font-style: italic;
}

/* Quick-reference table (wrapper scrolls horizontally if the table can't fit —
   dormant on desktop where it always fits) */
.training-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.training-table {
  width: 100%;
  border-collapse: collapse;
  margin: 4px 0 16px;
  font-size: 0.9rem;
}
.training-table th, .training-table td {
  border: 0.5px solid var(--border-default);
  padding: 8px 12px;
  text-align: left;
  vertical-align: top;
}
/* 07l: the same micro-caps header strip the project and inventory tables use,
   so a table in the guide reads like a table in the app. */
.training-table th { background: var(--surface-subtle); font-size: 0.7rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-tertiary); }

/* Allocations Chart legend */
.flow-legend { display:flex; align-items:center; flex-wrap:wrap; gap:14px; margin:2px 0 10px; font-size:0.8rem; color:var(--text-tertiary); }
.flow-legend-item { display:inline-flex; align-items:center; gap:6px; }
.flow-legend-swatch { width:14px; height:14px; border-radius:3px; display:inline-block; flex-shrink:0; }
.flow-legend-open { background: var(--surface-subtle); border:0.5px solid var(--border-strong); }
.flow-legend-done { background: rgba(95,158,121,0.14); border:1.75px solid #5f9e79; }
.flow-legend-note { font-style: italic; opacity:0.85; }

/* Dropdown Options manager (User Admin) */
.dd-group { margin-bottom: 14px; }
.dd-group-title { font-size: 0.82rem; font-weight: 500; color: var(--text-primary); margin-bottom: 6px; }
.dd-options { display: flex; flex-wrap: wrap; gap: 8px; }
.dd-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px 4px 10px;
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-radius: 999px;
  font-size: 0.83rem;
}
.dd-chip-val { color: var(--text-primary); }
.dd-chip-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border: none;
  border-radius: 50%;
  background: var(--surface-subtle);
  color: var(--text-tertiary);
  cursor: pointer;
  font-size: 0.8rem;
  line-height: 1;
  padding: 0;
}
.dd-chip-btn:hover { background: var(--cerio-blue); color: #fff; }
.dd-chip-del:hover { background: var(--red, #d64545); color: #fff; }

/* Feedback */
.fb-type-toggle { display: flex; gap: 8px; }
#feedback-modal-overlay .modal-body { display: flex; flex-direction: column; gap: 16px; }
/* feedback-modal footer uses the base .modal-footer padding. */
.fb-type-btn {
  flex: 1; padding: 9px 12px; border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm); background: var(--surface-card);
  color: var(--text-primary); cursor: pointer; font-size: 0.88rem;
}
.fb-type-btn.active { border-color: var(--cerio-blue); background: var(--cerio-blue-tint); color: var(--cerio-blue); font-weight: 600; }
.fb-list { display: flex; flex-direction: column; gap: 10px; }
/* 07l: feedback entries are objects in a list, so they take the Horizon card
   language like every other card. */
.fb-card { border: 0.5px solid var(--border-default); border-radius: var(--radius); padding: 13px 15px;
  background: var(--surface-card); box-shadow: var(--shadow); }
.fb-card-top { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; flex-wrap: wrap; }
.fb-type { font-size: 0.82rem; font-weight: 700; color: var(--text-primary); }
.fb-date { font-size: 0.76rem; color: var(--text-tertiary); }
.fb-msg { font-size: 0.9rem; color: var(--text-primary); white-space: pre-wrap; }
.fb-page, .fb-meta { font-size: 0.76rem; color: var(--text-tertiary); margin-top: 6px; }
.fb-actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }

/* Quote tool — multi cost lines */
.q-cost-lines { display: flex; flex-direction: column; gap: 6px; }
.q-cost-line { display: flex; gap: 6px; align-items: center; }
.q-cost-line .q-cost-desc { flex: 1 1 auto; min-width: 0; }
.q-cost-line .q-cost-amt { width: 140px; flex-shrink: 0; text-align: right; }
.q-cost-remove {
  flex-shrink: 0; width: 30px; height: 30px;
  border: 0.5px solid var(--border-default); border-radius: 6px;
  background: var(--surface-card); color: var(--text-tertiary);
  cursor: pointer; font-size: 0.8rem; line-height: 1;
}
.q-cost-remove:hover { background: var(--red, #d64545); color: #fff; border-color: transparent; }
.q-add-cost-btn {
  align-self: flex-start; margin-top: 8px;
  background: none; border: 0.5px dashed var(--border-default); border-radius: 6px;
  color: var(--cerio-blue); cursor: pointer; font-size: 0.8rem; padding: 6px 12px;
}
.q-add-cost-btn:hover { background: var(--surface-subtle); border-color: var(--cerio-blue); }
.q-cost-total-row {
  display: flex; justify-content: space-between; align-items: center;
  margin-top: 12px; padding-top: 12px; border-top: 0.5px solid var(--border-default);
}
.q-cost-total-label { font-size: 0.85rem; font-weight: 500; color: var(--text-tertiary); }
.q-cost-total-val { font-family: var(--font-body); font-weight: 500; color: var(--text-primary); }

/* ═══════════════════════════════════════════════════════
   SANDBOX BANNER (shown only when config.js sets IS_SANDBOX)
═══════════════════════════════════════════════════════ */
/* 21z — INCOMPLETE DATA. Raised by js/main.js when db.js latches its legacy project select.
   Full width along the bottom rather than the sandbox pill’s centred tab: this one is a sentence
   that has to be read, not a label that has to be noticed, and it stays until the page reloads. */
.data-degraded-banner {
  position: fixed;
  left: 0; right: 0;
  bottom: 0;
  z-index: var(--z-page-chrome);
  /* Fixed, not var(--amber): that token flips between themes and this sentence has to
     stay legible in both. One colour, both themes, like the sandbox banner above. */
  background: #9A4B06;
  color: #fff;
  font-size: 0.82rem;
  line-height: 1.45;
  text-align: center;
  padding: 9px 18px;
  padding-bottom: calc(9px + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -2px 10px rgba(0,0,0,0.18);
}
.data-degraded-banner strong { font-weight: 600; }
.data-degraded-banner .ddb-line { font-weight: 500; }
.data-degraded-banner .ddb-line + .ddb-line { margin-top: 4px; }
.data-degraded-banner .ddb-tail { margin-top: 4px; opacity: 0.85; font-weight: 400; }
@media (max-width: 720px) {
  .data-degraded-banner { font-size: 0.74rem; text-align: left; padding: 8px 12px; }
}

.sandbox-banner {
  position: fixed;
  top: 64px;   /* H1b: below the 56px horizon bar, still unmissable */
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-page-chrome);
  background: var(--cerio-blue);
  color: #fff;
  font-size: 0.74rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 5px 14px;
  border-radius: 99px;
  box-shadow: none;
  pointer-events: none; /* never blocks clicks on content beneath */
}
@media (max-width: 700px) {
  .sandbox-banner { top: auto; bottom: 10px; } /* clear the mobile topbar */
}

/* ═══════════════════════════════════════════════════════
   STAGE QUICK-FILTER BUTTONS (Open / Closed tabs)
═══════════════════════════════════════════════════════ */
/* Filter-preset buttons reuse .col-preset-btn; count badge on stage presets */
.stage-preset-count { margin-left: 6px; opacity: 0.7; font-weight: 500; }
.col-preset-btn.active .stage-preset-count { opacity: 0.95; }
.col-preset-row:empty { display: none; }

/* Pending-approval allocation badge */
.badge-pending {
  background: #f3e8ff;
  color: #7c3aed;
  border: 0.5px solid #d8b4fe;
}
.dark .badge-pending {
  background: rgba(124, 58, 237, 0.22);
  color: #c4b5fd;
  border-color: rgba(124, 58, 237, 0.5);
}

/* ═══ Pending Approvals ═══ */
#approvals-list { display: flex; flex-direction: column; gap: 10px; }
.approval-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 18px;
  border: 0.5px solid var(--border-default);
  border-left: 4px solid #7c3aed;
  border-radius: var(--radius);
  background: var(--surface-card);
  box-shadow: var(--shadow);          /* H3b */
  transition: box-shadow 0.12s, transform 0.12s;
}
.approval-card:hover { box-shadow: var(--shadow-lg); transform: translateY(-1px); }
@media (prefers-reduced-motion: reduce) { .approval-card, .approval-card:hover { transform: none; transition: none; } }
.approval-main { min-width: 0; }
.approval-proj { font-weight: 500; font-size: 0.95rem; margin-bottom: 3px; }
.approval-detail { font-size: 0.84rem; color: var(--text-secondary); }
.approval-actions { display: flex; gap: 8px; flex-shrink: 0; }
.approval-actions .btn-primary, .approval-actions .btn-ghost { padding: 6px 14px; font-size: 0.82rem; white-space: nowrap; }
@media (max-width: 700px) {
  .approval-card { flex-direction: column; align-items: stretch; }
  .approval-actions { justify-content: flex-end; }
}

/* Chart bubble click menu (Open / Filter) */
.flow-menu {
  position: fixed;
  z-index: var(--z-app-dialog);
  background: var(--surface-card);
  border: 0.5px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: none;
  padding: 5px;
  display: flex;
  flex-direction: column;
  min-width: 180px;
}
.flow-menu button {
  text-align: left;
  background: none;
  border: none;
  padding: 9px 12px;
  border-radius: 6px;
  font-size: 0.86rem;
  color: var(--text-primary);
  cursor: pointer;
  font-family: var(--font-body);
}
.flow-menu button:hover { background: var(--surface-subtle); }

/* Login info note (e.g. password-reset spam heads-up) */
.login-note {
  margin-top: 14px;
  padding: 12px 14px;
  border-radius: 10px;
  background: rgba(50, 130, 246, 0.10);
  border: 0.5px solid rgba(50, 130, 246, 0.35);
  color: var(--text-primary);
  font-size: 0.82rem;
  line-height: 1.45;
  text-align: left;
}
.col-preset-row { display: flex; gap: 6px; flex-wrap: wrap; }
.col-preset-btn {
  padding: 5px 14px;
  font-size: 0.82rem;
  border: 0.5px solid var(--border-strong);
  border-radius: 8px;
  background: var(--surface-card);
  color: var(--text-secondary);
  cursor: pointer;
  font-family: var(--font-body);
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.col-preset-btn:hover { background: var(--surface-subtle); }
.col-preset-btn.active {
  background: var(--cerio-blue);
  border-color: var(--cerio-blue);
  color: #fff;
  font-weight: 500;
}
.col-choose-wrap { position: relative; }
.col-choose-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: var(--z-dropdown-inline);
  background: var(--surface-card);
  border: 0.5px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: none;
  padding: 8px;
  min-width: 220px;
  max-height: 360px;
  overflow-y: auto;
  display: grid;
  gap: 2px;
}

/* Export dropdown options */
.col-export-opt {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 8px 12px;
  border-radius: 6px;
  font-size: 0.85rem;
  color: var(--text-primary);
  cursor: pointer;
  font-family: var(--font-body);
}
.col-export-opt:hover { background: var(--surface-subtle); }
#proj-export-menu { min-width: 170px; }

/* Export dropdown rows with per-format buttons */
.export-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 8px;
}
.export-row-label {
  flex: 1;
  font-size: 0.85rem;
  color: var(--text-primary);
}
.export-fmt-btn {
  padding: 3px 10px;
  font-size: 0.78rem;
  border: 0.5px solid var(--border-strong);
  border-radius: 6px;
  background: var(--surface-card);
  color: var(--text-secondary);
  cursor: pointer;
  font-family: var(--font-body);
}
.export-fmt-btn:hover { background: var(--cerio-blue); border-color: var(--cerio-blue); color: #fff; }
#proj-export-menu { min-width: 230px; }
#flow-export-menu { left: auto; right: 0; }

/* Pending Approvals — lite submitter view */
.approvals-group-title {
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-tertiary);
  margin: 14px 0 8px;
}
.approvals-group-title:first-child { margin-top: 0; }
.approval-denied { border-left: 3px solid var(--red); }
.approval-comment {
  margin-top: 6px;
  font-size: 0.85rem;
  color: var(--text-primary);
  background: var(--surface-subtle);
  border-radius: 6px;
  padding: 7px 10px;
}

.approval-approved { border-left: 3px solid var(--green); }

/* Icon-only button (e.g. copy chart), matched to .col-preset-btn */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0.5px solid var(--border-strong);
  border-radius: 8px;
  background: var(--surface-card);
  color: var(--text-secondary);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.icon-btn:hover { background: var(--surface-subtle); color: var(--text-primary); }

/* Section headers inside the project modal form */
.form-section-title {
  grid-column: 1 / -1;
  font-size: 0.95rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--cerio-blue);
  padding: 6px 0 4px;
  border-bottom: 0.5px solid var(--border-default);
  margin-top: 8px;
}
.form-section-title:first-child { margin-top: 0; }

/* Wider project modal on desktop, laid out 4-up so the form is shorter (less scrolling).
   Scoped to the project form; the inventory modal and small screens are unaffected. */
#modal-overlay .modal { max-width: 1080px; }
@media (min-width: 1000px) {
  #project-form .form-grid { grid-template-columns: repeat(4, 1fr); }
  #project-form .form-grid .span-3 { grid-column: 1 / -1; }
  #project-form .form-grid .span-2 { grid-column: span 2; }
}

/* Multi-column field flow inside view-modal sections (keeps Project/Sales/Technical/Execution compact) */
.view-fields.cols {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 10px 22px;
}

/* ══════════════════════════════════════════════════════
   PROPOSAL BUILDER
   ══════════════════════════════════════════════════════ */
.pb-wrap { max-width: 920px; }
.pb-steps { display:flex; flex-wrap:wrap; gap:6px; list-style:none; padding:0; margin:0 0 20px; }
.pb-step { display:flex; align-items:center; gap:7px; font-size:0.79rem; font-weight:600; color:var(--text-tertiary);
  padding:6px 13px; border:0.5px solid var(--border-default); border-radius:99px; background:var(--surface-subtle);
  transition:background var(--transition), color var(--transition), border-color var(--transition); }
/* H3b: the current step is the only filled one, so the rail reads as progress
   at a glance instead of as five similar chips. */
.pb-step.active { color:#fff; border-color:var(--cerio-blue); background:var(--cerio-blue); font-weight:700; }
.pb-step.active .pb-step-num { background:rgba(255,255,255,0.22); border-color:rgba(255,255,255,0.5); color:#fff; }
.pb-step.done { color:var(--green); border-color:var(--sev-low-border); background:var(--sev-low-bg); }
.pb-step-num { display:inline-flex; align-items:center; justify-content:center; width:20px; height:20px;
  border-radius:50%; background:var(--surface-card); border:0.5px solid currentColor; font-size:0.72rem; font-weight:500; }
.pb-panel { display:none; }
.pb-panel.active { display:block; }
/* H3b: box-shadow folded into the one .pb-card rule — wizard cards are objects
   like every other Horizon card. (It was briefly a second rule below the
   title; one selector, one rule.) */
.pb-card { background:var(--surface-card); border:0.5px solid var(--border-default); border-radius:var(--radius); padding:20px; margin-bottom:16px; box-shadow:var(--shadow); }
.pb-card-title { font-family:var(--font-display); font-weight:700; font-size:0.95rem; letter-spacing:-0.01em; color:var(--text-primary); margin-bottom:14px; }
.pb-field { margin-bottom:14px; display:flex; flex-direction:column; }
.pb-label { font-size:0.8rem; font-weight:500; color:var(--text-secondary); margin-bottom:5px; }
.pb-hint { font-size:0.76rem; color:var(--text-tertiary); margin-top:4px; }
.pb-grid-2 { display:grid; grid-template-columns:1fr 1fr; gap:0 18px; }
.pb-span-2 { grid-column:1 / -1; }
.pb-inline { display:flex; gap:8px; align-items:center; }
.pb-inline .form-input { flex:1; }
.pb-mini-btn { width:38px; height:38px; flex:0 0 auto; border:0.5px solid var(--border-default); background:var(--surface-subtle);
  border-radius:var(--radius-sm); font-size:1.1rem; line-height:1; cursor:pointer; color:var(--text-secondary); }
.pb-mini-btn:hover { border-color:var(--cerio-blue); color:var(--cerio-blue); }
.pb-add-btn { background:none; border:0.5px dashed var(--border-strong); color:var(--text-secondary);
  padding:8px 14px; border-radius:var(--radius-sm); font-size:0.82rem; cursor:pointer; font-family:var(--font-body); }
.pb-add-btn:hover { border-color:var(--cerio-blue); color:var(--cerio-blue); }
.pb-total-row { display:flex; justify-content:space-between; align-items:center; margin-top:16px;
  padding-top:14px; border-top:0.5px solid var(--border-default); }
.pb-total-label { font-weight:500; color:var(--text-secondary); }
.pb-total-val { font-family:var(--font-display); font-weight:500; font-size:1.15rem; color:var(--text-primary); }
.pb-nav { display:flex; justify-content:space-between; align-items:center; margin-top:4px; }
.pb-nav-right { display:flex; gap:10px; }
.pb-checkbox { display:flex; align-items:center; gap:9px; font-size:0.84rem; color:var(--text-secondary); margin-top:10px; cursor:pointer; }
.pb-checkbox input { width:16px; height:16px; }
.pb-check-list { display:flex; flex-direction:column; gap:8px; margin:10px 0; }
.pb-warning { background:#FDF6E9; border:0.5px solid var(--amber); color:#7a4d0e;
  padding:10px 14px; border-radius:var(--radius-sm); font-size:0.82rem; margin-top:12px; }
/* Line item / note / exclusion rows */
.pb-item { border:0.5px solid var(--border-default); border-radius:var(--radius-sm); padding:14px; margin-bottom:12px; background:var(--surface-subtle); }
.pb-item-head { display:flex; justify-content:space-between; align-items:center; margin-bottom:10px; }
.pb-item-title { font-weight:500; font-size:0.88rem; color:var(--text-primary); }
.pb-row { display:flex; gap:8px; align-items:flex-start; margin-bottom:8px; }
.pb-row .form-input { flex:1; }
.pb-del { background:none; border:none; color:var(--red); cursor:pointer; font-size:0.9rem; padding:4px 8px; border-radius:var(--radius-sm); }
.pb-del:hover { background:#F7EAEA; }
/* Review */
.pb-review-h { font-weight:500; font-size:0.82rem; text-transform:uppercase; letter-spacing:0.04em;
  color:var(--text-tertiary); margin-bottom:6px; }
/* 07n: the template manager's section editor, its {+} insert-field menu and its 👁 print preview
   are gone with the equipment templates. .pb-tpl-card / .pb-tpl-card-meta STAY: despite the name
   they style the Proposals page cards and the project-proposal card (main.js, proposal-drift.js),
   and four probes select by them. */
.pb-tpl-card { border:0.5px solid var(--border-default); border-radius:var(--radius-sm); padding:12px 14px; margin-bottom:8px;
  display:flex; justify-content:space-between; align-items:center; }
.pb-tpl-card-meta { font-size:0.78rem; color:var(--text-tertiary); margin-top:2px; }
@media (max-width:640px) { .pb-grid-2 { grid-template-columns:1fr; } }

/* ── Proposal Builder v2 additions (drafts bar, spec sections) ── */
.pb-draftbar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:16px;
  padding:10px 14px; background:var(--surface-card); border:0.5px solid var(--border-default); border-radius:var(--radius); }
.pb-draftbar .form-input { flex:1; min-width:220px; }
.pb-draft-status { font-size:0.78rem; color:var(--text-tertiary); margin-left:auto; }
.pb-sec { border:0.5px solid var(--border-default); border-radius:var(--radius-sm); margin-bottom:10px;
  background:var(--surface-subtle); padding:0 12px 10px; }
.pb-sec[open] { background:var(--surface-card); }
.pb-sec-head { cursor:pointer; font-weight:500; font-size:0.88rem; color:var(--text-primary);
  padding:10px 0; list-style-position:inside; }
.pb-sec-head::marker { color: var(--cerio-blue); }
.pb-sec-meta { font-weight:400; font-size:0.76rem; color:var(--text-tertiary); margin-left:6px; }
.pb-sec-bullets { margin:8px 0; padding-left:20px; font-size:0.8rem; color:var(--text-secondary); }
.pb-sec-bullets li { margin-bottom:3px; }
.pb-ov-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(180px, 1fr));
  gap:0 14px; padding:6px 0 2px; }
/* 30s alignment fix: fields WITH an import badge get a taller (wrapping) label,
   pushing their select down, while badge-less fields' selects floated high
   (e.g. Alternator kW next to Temp Rise). Grid cells in a row already stretch
   to equal height; anchoring the control row to the cell bottom puts every
   select in a row on one shared baseline regardless of badge presence. */
.pb-ov-grid .pb-field { display:flex; flex-direction:column; }
.pb-ov-grid .pb-field .pb-inline { margin-top:auto; }
.pb-cb-save { margin-top:0; font-size:0.74rem; white-space:nowrap; }
.pb-add-cb { margin-top:4px; font-size:0.76rem; padding:5px 10px; }
@media (max-width:640px) { .pb-ov-grid { grid-template-columns:1fr; } .pb-draftbar { flex-direction:column; align-items:stretch; } }

/* ── PB v2.1: removable bullets + payment terms ── */
/* 28l — ▲▼ line ordering (notes, bill-of-materials lines, items not included) */
.pb-move-pair { display:inline-flex; flex-direction:column; gap:0; margin-right:6px; vertical-align:middle; flex:0 0 auto; }
.pb-move { background:none; border:none; color:var(--text-faint); cursor:pointer; font-size:0.62rem; line-height:1; padding:1px 4px; border-radius:var(--radius-sm); }
.pb-move:hover:not([disabled]) { color:var(--cerio-blue); background:var(--cerio-blue-tint); }
.pb-move[disabled] { opacity:0.25; cursor:default; }
.pb-excl-row { display:flex; align-items:center; gap:8px; margin-bottom:6px; }
.pb-excl-row .form-input { flex:1; }
.pb-sec-bullets li.pb-li-custom { list-style:none; margin-left:-20px; }
.pb-sec-bullets li.pb-li-custom .pb-row { margin-bottom:4px; align-items:center; }
.pb-bullet-removed { text-decoration:line-through; color:var(--text-faint); }
.pb-bullet-x { background:none; border:none; color:var(--text-faint); cursor:pointer;
  font-size:0.75rem; padding:0 5px; border-radius:var(--radius-sm); vertical-align:middle; }
.pb-bullet-x:hover { color:var(--red); background:#F7EAEA; }
.pb-pay-grid { display:grid; grid-template-columns:130px 1fr 170px; gap:0 14px; }
@media (max-width:640px) { .pb-pay-grid { grid-template-columns:1fr; } }

/* 11y — cancellation terms. The date sits ABOVE the rows because one date feeds
   all of them, so it must not look like a per-row field. */
.pb-cancel-datebar { display:flex; align-items:flex-end; gap:12px; flex-wrap:wrap;
  padding:12px 14px; margin-bottom:14px; border-radius:8px;
  background:var(--cerio-blue-tint); border:0.5px solid var(--border-default); }
/* The finished sentence, with the date filled in — the only place a rep sees
   what the customer will actually read. */
.pb-cancel-resolved { margin-top:8px; padding:7px 10px; border-radius:6px;
  /* 12b — WAS var(--bg-tertiary), which does not exist. An undefined custom
     property is not an error and does not fall back: the declaration is simply
     invalid at computed-value time, so the strip shipped with NO background at
     all and nobody saw a console warning. The real token is --surface-subtle,
     and it is theme-aware. The invented name came from a mockup; see
     docs/*-mockup.html, which had the same bug for the same reason. */
  background:var(--surface-subtle); border-left:2px solid var(--cerio-blue);
  font-size:0.85rem; color:var(--text-secondary); }
.pb-cancel-resolved b { color:var(--text-primary); font-weight:600; }
/* 11y — the delivery tally when it does not match the quantity quoted. A
   warning, never a blocker: a split schedule is mid-edit most of the time. */
.pb-delivery-warn { color:var(--amber, #d29922); }

/* PB v2.2: clickable steps */
.pb-step { cursor:pointer; user-select:none; }
.pb-step:hover { border-color:var(--cerio-blue); color:var(--cerio-blue); }

/* ── Drag & drop zones ── */
.ai-dropzone { display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  border:1.5px dashed var(--border-strong); border-radius:var(--radius-sm);
  padding:10px 14px; transition:border-color .15s, background .15s; }
.ai-dropzone-hint { font-size:0.78rem; color:var(--text-tertiary); }
.ai-dropzone.drag-over, .files-section.drag-over {
  border-color:var(--cerio-blue) !important; background:var(--cerio-blue-tint); }
.files-section { border:1.5px dashed transparent; border-radius:var(--radius-sm); transition:border-color .15s, background .15s; }

/* ── Split button (Edit Project ▾ Clone) ── */
.btn-split { position:relative; display:inline-flex; }
.btn-split-main { border-top-right-radius:0; border-bottom-right-radius:0; }
.btn-split-caret { border-top-left-radius:0; border-bottom-left-radius:0;
  border-left:0.5px solid rgba(255,255,255,0.35); padding:0 10px; }
/* 09m: drops DOWN by default (Chris). toggleSplitMenu() adds .up only when the
   menu would be clipped by the viewport or by a scrolling ancestor — the record
   footer sits inside a scrolling modal body, where downward would be cut off. */
.btn-split-menu { position:absolute; top:calc(100% + 4px); left:0; z-index:var(--z-dropdown-inline);
  background:var(--surface-card); border:0.5px solid var(--border-default); border-radius:var(--radius-sm);
  box-shadow: none; min-width:100%; width:max-content; overflow:hidden; }
.btn-split-menu button { display:block; width:100%; text-align:left; background:none; border:none;
  padding:10px 14px; font-size:0.85rem; color:var(--text-primary); cursor:pointer; font-family:var(--font-body); }
.btn-split-menu.up { top:auto; bottom:calc(100% + 4px); }
/* 21zl — the horizontal twin of .up, added by toggleSplitMenu when the menu would run off the
   right of the window. left:auto + right:0 anchors it to the button's RIGHT edge so it grows
   leftwards, back into the page. max-width is the belt: width:max-content has no ceiling, so a
   long enough machine label ("Remove PowerGen · 3000kW from project") would otherwise be wider
   than the window whichever edge it is pinned to. With a ceiling the label wraps instead of
   disappearing, which matters most on the destructive item — the one that names what is about
   to be deleted. */
.btn-split-menu.leftward { left:auto; right:0; }
.btn-split-menu { max-width:min(92vw, 420px); }
.btn-split-menu button { white-space:normal; overflow-wrap:anywhere; }
.btn-split-menu button:hover { background:var(--surface-subtle); color:var(--cerio-blue); }
/* 09k — a destructive item in a split menu.
   --red is the TEXT ink and this IS text, so it is the correct token here (the
   09j fix was about --red being used as a FILL). Measured: #A32D2D on the light
   card 7.07:1, #FF8B8B on the dark card 7.65:1. The separator above it is what
   stops "Delete Project" reading as just another entry in the list — Chris's
   rule that dangerous and routine controls do not sit together, applied inside
   a menu that now holds both. */
.btn-split-menu button.danger { color:var(--red); border-top:0.5px solid var(--border-default); }
.btn-split-menu button.danger:first-child { border-top:0; }
.btn-split-menu button.danger:hover { background:var(--sev-high-bg); color:var(--sev-high-text); }

/* ── Alert/prompt modal footers: proper padding when footer is a direct
     child of .modal (header/body carry their own, footer base class doesn't) ── */
/* App-dialog footers use the base .modal-footer padding (all sides). */

/* Import status badges on proposal fields */
.pb-import-note { display:inline-block; font-size:0.66rem; font-weight:500; padding:1px 7px;
  border-radius:9px; margin-left:6px; vertical-align:middle; letter-spacing:0.02em; }
.pb-import-note-ok { background:#E7F3EA; color:var(--green); border:0.5px solid #BFE0C8; }
.pb-import-note-empty { background:var(--surface-subtle); color:var(--text-tertiary); border:0.5px solid var(--border-default); }

/* ── Review step: full document preview ── */
/* 06m: this card is a PAPER preview of the generated document — deliberately
   theme-independent (white sheet, near-black ink) in BOTH themes. The children
   below still used app theme tokens, which followed the theme while the ink
   stayed #1d1d1f: in dark mode --surface-subtle resolved to #1F232C, putting
   near-black header text on a near-black cell (1.07:1 — the Pricing header row
   was an unreadable dark bar). Re-declaring the tokens here, scoped to the
   sheet, pins every descendant to the light/paper values — including any
   .pb-prev-* rule added later, which is why this is done once here rather than
   by hardcoding each rule. */
.pb-doc-preview { background:#fff; border:0.5px solid var(--border-default); border-radius:var(--radius-sm);
  padding:26px 30px; box-shadow: none; font-size:0.86rem; color:#1d1d1f;
  --surface-subtle:#EFEEE8; --border-default:#E8E7E0; --text-tertiary:#6D6C64; }
.pb-prev-h1 { text-align:center; font-size:1.15rem; font-weight:500; margin-bottom:14px; }
.pb-prev-h2 { font-weight:500; margin:18px 0 6px; font-size:0.92rem; }
.pb-prev-meta { line-height:1.7; margin-bottom:10px; }
.pb-prev-sub { font-size:0.78rem; color:var(--text-tertiary); }
.pb-prev-kv { border-collapse:collapse; margin:8px 0; }
.pb-prev-kv td { padding:2px 14px 2px 0; }
.pb-prev-kv td:first-child { color:var(--text-tertiary); font-size:0.78rem; }
.pb-prev-table { width:100%; border-collapse:collapse; margin:6px 0 4px; }
.pb-prev-table th, .pb-prev-table td { border:0.5px solid var(--border-default); padding:6px 9px; vertical-align:top; }
.pb-prev-table th { background:var(--surface-subtle); text-align:left; font-size:0.78rem; }
.pb-prev-list { margin:6px 0; padding-left:22px; line-height:1.7; }
.pb-prev-sec-h { font-weight:500; text-decoration:underline; margin:10px 0 3px; }
.pb-prev-bullets { margin:0 0 6px; padding-left:22px; line-height:1.65; }
@media (max-width:640px) { .pb-doc-preview { padding:16px 14px; } }

/* App dialogs (alert/prompt/confirm) always sit above any base modal that
   opened them — e.g. the rename prompt over the dropdown-manager modal.
   Base overlays are z-index 200; these ride above at 400. */
#app-alert-overlay,
#app-prompt-overlay,
#app-confirm-overlay,
#pb-review-overlay { z-index: var(--z-app-dialog); }

/* Smart Allocate's multi-project planner opens FROM INSIDE the inventory modal,
   so it takes the overlay-popup tier to paint above it (app dialogs stay higher). */
#smart-alloc-overlay { z-index: var(--z-overlay-popup); }
/* 07f — SMART ALLOCATE ZONES.
   ─────────────────────────────────────────────────────────────────────────
   REMOVED: `#sa-divider ~ [data-sa-proj] { opacity: 0.45 }`. That rule dimmed
   every row below the divider, which was fine when only a few projects sat
   there — but 06i made ALL projects start below the line, so the modal opened
   with the entire list at 45%, the universal "disabled" convention, under a
   header reading NOT INCLUDED with nothing above it. (Chris, 2026-08-07.)
   Inclusion is now carried by WHICH ZONE a row is in and by a rank number,
   both of which are positive statements. Nothing is dimmed. */
.sa-zone {
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius);
  padding: 10px 12px 12px;
  margin-bottom: 12px;
}
/* Included is the target of the primary action, so it carries the brand edge
   and the card surface; Not-included is a holding area on the page tone. */
.sa-zone-in  { border-left: 3px solid var(--cerio-blue); background: var(--surface-subtle); }
.sa-zone-out { background: var(--surface-page); }
/* The modal body is already --surface-card, so the ROWS take the card tone and
   the ZONES are the tinted trays behind them. --surface-subtle rows on a
   --surface-page tray were only 9 RGB steps apart and the row edges vanished. */
.sa-zone .sa-row { background: var(--surface-card); }
.sa-zone-head {
  display: flex; align-items: center; gap: 8px; margin-bottom: 9px;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-tertiary);
}
.sa-zone-in .sa-zone-head { color: var(--cerio-blue); }
.sa-zone-count {
  margin-left: auto; min-width: 22px; padding: 1px 7px; border-radius: 99px;
  background: var(--surface-subtle); border: 0.5px solid var(--border-default);
  color: var(--text-secondary); font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0; text-align: center;
}
.sa-zone-in .sa-zone-count { background: var(--cerio-blue); border-color: var(--cerio-blue); color: #fff; }
.sa-zone-empty {
  font-size: 0.82rem; color: var(--text-tertiary); padding: 6px 2px 4px; line-height: 1.5;
}
.sa-row { margin-bottom: 6px; }
.sa-row:last-child { margin-bottom: 0; }
/* Rank = position in the Included zone, so it is renumbered on every move and
   blank for rows that are out. Fixed width, so names still start on one line. */
.sa-rank {
  flex-shrink: 0; width: 22px; text-align: right;
  font-size: 0.82rem; font-weight: 700; color: var(--cerio-blue);
}
/* An arrow with nowhere to go says so, instead of looking live and doing
   nothing (the first Included row, the last Not-included row). */
.sa-up:disabled, .sa-down:disabled { opacity: 0.3; cursor: default; }
/* :hover still matches a disabled button, so the global .action-btn hover has
   to be cancelled or a dead arrow lights up blue when you point at it. */
.sa-up:disabled:hover, .sa-down:disabled:hover { background: transparent; color: var(--text-tertiary); }

/* 07g — the tick box includes; the arrows only rank. A Not-included row has
   nothing to rank, so it carries no arrows at all: display:none keeps them out
   of the tab order too, which a disabled attribute would not do as cleanly. */
.sa-zone-out .sa-row .file-actions { display: none; }
.sa-check {
  flex-shrink: 0; width: 16px; height: 16px; margin: 0; cursor: pointer;
  accent-color: var(--cerio-blue);
}
/* 07h: the scoped `[data-theme="dark"] .sa-check { color-scheme: dark }` that
   07g added here is GONE — color-scheme is now declared once on :root and once
   on [data-theme="dark"], so every native control in the app inherits it. A
   local copy would have been a second place to maintain the same fact. */
/* The name and detail line ARE the checkbox's label, so the whole row body is a
   click target — one click to include, from anywhere along the row. */
.sa-row-label { cursor: pointer; }

/* ── Inline popups opened from INSIDE a modal must clear the modal.
   When anchored within a .modal, escalate above the overlay tier so the
   multi-select panel, column chooser, or split menu isn't hidden behind
   the modal body (the same bug class as the rename prompt). ── */
.modal .ms-panel,
.modal .col-choose-menu,
.modal .btn-split-menu { z-index: var(--z-overlay-popup); }

/* Consistency review findings */
.pb-finding { display:block; width:100%; text-align:left; background:var(--surface-subtle);
  border:0.5px solid var(--border-default); border-radius:var(--radius-sm); padding:12px 14px;
  margin-bottom:10px; cursor:pointer; transition:border-color .12s, background .12s;
  /* 06c: these cards are <button>s, and buttons DON'T inherit font/color — without
     these two lines they render in the browser's default button font with BLACK
     text in both themes (invisible on light, broken on dark). */
  font:inherit; color:var(--text-primary); }
.pb-finding:hover { border-color:var(--cerio-blue); background:var(--surface-card); }
.pb-finding-top { display:flex; align-items:center; gap:10px; margin-bottom:4px; }
.pb-finding-sev { font-size:0.68rem; font-weight:500; text-transform:uppercase; letter-spacing:0.04em; }
.pb-finding-title { font-weight:500; font-size:0.9rem; flex:1; }
.pb-finding-go { font-size:0.78rem; color:var(--cerio-blue); font-weight:500; }
.pb-finding-detail { font-size:0.82rem; color:var(--text-secondary); line-height:1.55; }

/* Omitted BOM section */
.pb-sec-omitted > summary { opacity: 0.55; }
.pb-sec-omitted > summary .pb-sec-head, .pb-sec-omitted .pb-sec-meta { text-decoration: line-through; }
.pb-sec-omit { margin-left: auto; background: none; border: 0.5px solid var(--border-default); border-radius: var(--radius-sm);
  padding: 2px 10px; font-size: 0.72rem; cursor: pointer; color: var(--text-secondary); }
.pb-sec-omit:hover { border-color: var(--red); color: var(--red); }
.pb-sec-omitted .pb-sec-omit:hover { border-color: var(--green); color: var(--green); }
.pb-sec-head { display: flex; align-items: center; gap: 8px; }

/* Wizard nav buttons: single-line labels, consistent height; row may wrap as a unit */
.pb-nav-right { flex-wrap: wrap; justify-content: flex-end; }
.pb-nav-right .btn-primary, .pb-nav-right .btn-ghost { white-space: nowrap; }

/* House-rule violation cards get a distinct left edge */
.pb-finding-rule { border-left: 3px solid var(--cerio-blue); }
.pb-review-h { font-weight: 500; font-size: 0.85rem; }

/* Feedback admin note.
   07l: .fb-note-row was declared TWICE — once here for its margin and again
   six lines below for its flex layout, with three unrelated rules between
   them. It worked only because neither set the same property, which is luck,
   not design: the next person to add `margin` to the lower one would have
   silently killed the upper. One rule now. */
.fb-note-row { display: flex; gap: 8px; align-items: center; margin: 8px 0 4px; }
.fb-note-row .fb-note-input { flex: 1; }
.fb-note-input { font-size: 0.82rem; }
.fb-note-existing { font-size: 0.8rem; color: var(--text-secondary); margin-top: 6px; font-style: italic; }
.fb-admin-note { margin-top: 8px; padding: 8px 12px; background: var(--surface-subtle); border-left: 3px solid var(--cerio-blue);
  border-radius: var(--radius-sm); font-size: 0.84rem; }
.fb-admin-note-label { font-weight: 700; }

/* Global search */
.gs-group { font-size:0.72rem; font-weight:500; text-transform:uppercase; letter-spacing:0.05em;
  color:var(--text-tertiary); margin:12px 0 6px; }
.gs-hit { display:flex; flex-direction:column; align-items:flex-start; gap:2px; width:100%;
  text-align:left; background:var(--surface-subtle); border:0.5px solid var(--border-default);
  border-radius:var(--radius-sm); padding:8px 12px; margin-bottom:6px; cursor:pointer;
  /* 31c: buttons do NOT inherit text color — the UA default (near-black) made
     result titles unreadable on the dark theme. Theme the color explicitly. */
  color:var(--text-primary); font:inherit; }
.gs-hit:hover { border-color:var(--cerio-blue); }
.gs-hit-main { font-weight:500; font-size:0.88rem; }
.gs-hit-sub { font-size:0.76rem; color:var(--text-secondary); }
/* 27a: an attachment hit says so */
.gs-tag { display:inline-block; font-size:0.62rem; font-weight:600; text-transform:uppercase; letter-spacing:0.06em;
  color:var(--cerio-blue); border:0.5px solid var(--cerio-blue); border-radius:999px; padding:1px 7px; margin-right:8px; vertical-align:middle; }

/* Help assistant */
/* 11zd: moved to the LEFT (Chris). Bottom-right is where a fixed action bar
   ends up on every screen that has one — the visualizer's Save button was
   directly underneath it. Left is empty on every view in the app. */
/* 17p (Chris): no longer a floating FAB — a small circle beside Sign out in the
   avatar menu. Same id, same click wiring, same assistant. */
#help-fab { flex:0 0 auto; width:24px; height:24px; border-radius:50%; margin-right:12px;
  background:var(--surface-card); color:var(--cerio-blue); border:1.5px solid var(--cerio-blue);
  font-size:0.85rem; font-weight:600; display:flex; align-items:center; justify-content:center;
  line-height:1; cursor:pointer; font-family:inherit; }
#help-fab:hover { background:var(--cerio-blue); color:#fff; }
.help-msg { padding:9px 13px; border-radius:var(--radius-sm); font-size:0.86rem; line-height:1.55;
  white-space:pre-line; max-width:88%; }
.help-msg-ai { background:var(--surface-subtle); border:0.5px solid var(--border-default); align-self:flex-start; }
.help-msg-user { background:var(--cerio-blue); color:#fff; align-self:flex-end; }
#help-overlay .modal-footer { display:flex; }

/* AI rules panel */
.airule-row { display:flex; align-items:center; gap:8px; padding:6px 10px; border:0.5px solid var(--border-default);
  border-radius:var(--radius-sm); margin-bottom:6px; background:var(--surface-subtle); }
.airule-text { flex:1; font-size:0.84rem; line-height:1.45; }

/* Allocation row: project click-through */
.alloc-proj-link { background:none; border:none; padding:0; font:inherit; font-weight:500;
  color:var(--cerio-blue); cursor:pointer; text-align:left; }
.alloc-proj-link:hover { text-decoration:underline; }

/* Wheel/touch scrolling over any modal must not scroll the page behind it.
   The overlay becomes its own scroll context and contains the chain; inner
   scrollers (assistant messages, search results, modal bodies) contain at
   their bounds instead of bleeding through. */
.modal-overlay { overflow-y: auto; overscroll-behavior: contain; }
#help-messages, #gsearch-results, .modal-body { overscroll-behavior: contain; }

/* Starting System optional-line quick buttons */
.pb-starting-opts { display:flex; flex-wrap:wrap; gap:6px; margin:4px 0; }

/* Page-style records: the four big windows get near-full viewport room,
   giving space for future features without a modal-to-page rewrite. */
#view-proj-overlay .modal, #view-inv-overlay .modal,
#modal-overlay .modal, #inv-modal-overlay .modal {
  max-width: min(1500px, 96vw);
  width: 96vw;
  max-height: 94vh;
  max-height: 94dvh;
}


/* ── Full-page record layout (CerioHub pattern) ───────────────────── */
#view-project-page, #view-inventory-page { max-width: min(1500px, 100%); margin: 0 auto; }
/* Grid children default to min-width:auto, letting long nowrap content
   (allocation labels) push the page wider than the browser. Allow the
   sections to shrink and long values to wrap instead. */
#view-project-page .view-only-grid, #view-inventory-page .view-only-grid,
#view-project-page .view-section, #view-inventory-page .view-section { min-width: 0; }
#view-project-page .record-page-body, #view-inventory-page .record-page-body { min-width: 0; }
/* break-word, NOT anywhere: `anywhere` permits mid-number breaks and shrank
   min-content to a single character, splitting "24" into 2/4 and
   "$40,800,000.00" across lines. Long labels now wrap instead. */
#view-project-page .view-field-val, #view-inventory-page .view-field-val { overflow-wrap: break-word; }
#view-project-page .delivery-item, #view-inventory-page .delivery-item { min-width: 0; }
/* Same overflow protection for the EDIT pages: the form's allocation and
   delivery rows carry the same long nowrap labels, so keep them contained. */
#view-project-edit-page .modal, #view-inventory-edit-page .modal,
#view-project-edit-page .form-grid, #view-inventory-edit-page .form-grid,
#view-project-edit-page .delivery-item, #view-inventory-edit-page .delivery-item,
#view-project-edit-page .delivery-add-row, #view-inventory-edit-page .delivery-add-row { min-width: 0; }
#view-project-edit-page .field-val, #view-inventory-edit-page .field-val { overflow-wrap: break-word; }
.record-page-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-bottom: 20px; }
.record-breadcrumb { font-size: 0.85rem; color: var(--text-secondary); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.record-breadcrumb a { color: var(--cerio-blue); text-decoration: none; }
.record-breadcrumb a:hover { text-decoration: underline; }
.record-breadcrumb span { color: var(--text-tertiary); }
.record-page-actions { display: flex; gap: 8px; align-items: center; }
/* Title + status pills on the left, record actions pushed to the right and
   bottom-aligned so they sit level with the status pills (per design). */
.record-title-row { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; flex-wrap: wrap; margin-bottom: 18px; }
.record-title-main { flex: 1 1 auto; min-width: 0; }
.record-title-row .record-title { margin: 4px 0 10px; }
.record-title-row .alert-pill-row { margin: 0; }
.record-title-row .record-page-actions { flex: 0 0 auto; }
.record-page-body .view-only-grid { margin-top: 0; }


/* ── TRUE full-window edit pages: the edit form DOM is MOVED into these
   views (portal pattern), so the sidebar + app frame stay visible and
   every getElementById wire keeps working on the same nodes. ── */
#view-project-edit-page .modal, #view-inventory-edit-page .modal {
  position: static;
  max-width: min(1500px, 100%);
  width: 100%;
  margin: 0 auto;
  max-height: none;
  box-shadow: none;
  border: 0.5px solid var(--border-default);
}
#view-project-edit-page .modal-close, #view-inventory-edit-page .modal-close { display: none; }
/* The big blue .record-title is the page's name; the modal's generic
   header title is redundant on the page, so drop the whole header bar. */
#view-project-edit-page .modal-header, #view-inventory-edit-page .modal-header { display: none; }
#view-project-edit-page .edit-page-breadcrumb, #view-inventory-edit-page .edit-page-breadcrumb { padding: 0 0 10px; max-width: min(1500px,100%); margin: 0 auto; }


/* ── Consistent record name title (view + edit, project + inventory) ── */
.record-title {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 700;             /* H3a: display weight, tighter set */
  letter-spacing: -0.02em;
  color: var(--cerio-blue);
  line-height: 1.15;
  margin: 4px 0 18px;
  word-break: break-word;
}


/* H1b: the sidebar collapse mechanism is retired (no sidebar to collapse). */


/* ── Header color unification (Chris 2026-07-23): tab titles and card/
   section titles use the deep brand blue, matching the record pages. ── */
.view-title { color: var(--cerio-blue); }
.chart-title { color: var(--cerio-blue); }

/* Top action bar on full-window edit pages */
/* ── 11x — THE EDIT ACTION BAR ──────────────────────────────────────────
   Fixed to the VIEWPORT, not to the page: the whole point is that it does not
   scroll away. Safe to place inside the edit-page view because nothing in that
   ancestor chain sets transform / filter / perspective, which are what would
   turn `fixed` into `absolute` against a parent. Checked, not assumed.
   z-index sits at page-chrome, the same tier as the top bar, so it is above the
   form and below every modal overlay — a confirm dialog must never open behind
   the bar that triggered it. */
.edit-bar {
  position: fixed; left: 0; right: 0; bottom: 0;
  z-index: var(--z-page-chrome);
  display: flex; align-items: center; gap: 14px;
  padding: 12px 36px;
  background: var(--surface-card);
  border-top: 0.5px solid var(--border-strong);
  box-shadow: 0 -6px 20px rgba(0,0,0,0.28);
}
.edit-bar-what { display: flex; align-items: center; gap: 9px; min-width: 0; flex: 1 1 auto; }
.edit-bar-what .lbl { font-size: 0.82rem; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.edit-bar-what .lbl b { color: var(--text-primary); font-weight: 500; }
.edit-bar-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--amber, #BE8A3C); flex-shrink: 0; }
.edit-bar-actions { display: flex; gap: 10px; flex-shrink: 0; }
.edit-bar .btn-primary, .edit-bar .btn-ghost { padding: 7px 20px; font-size: 0.85rem; }
.edit-bar .btn-primary:disabled { opacity: 0.45; cursor: default; }
/* The bar floats over the page, so the page has to end above it or the last
   field is unreachable. Applied to the whole shell rather than the edit view,
   because the scrolling element is the document. */
body.has-edit-bar .main-content { padding-bottom: 96px; }
/* What the bar replaces: the header pair is gone from the markup, the footer
   pair is hidden while it is mounted. */
.edit-bar-hides { display: none !important; }
/* Narrow: the label goes and the two buttons take the width. A phone keyboard
   also pushes fixed elements around, so this wants a real handset, not a
   narrow desktop window. */
@media (max-width: 620px) {
  .edit-bar { padding: 10px 14px; }
  .edit-bar-what { display: none; }
  .edit-bar-actions { flex: 1 1 auto; }
  .edit-bar-actions .btn-primary, .edit-bar-actions .btn-ghost { flex: 1 1 0; }
}
/* Edit-mode: title on the left, Save/Cancel pushed to the right (mirrors view mode). */
.edit-title-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; max-width: min(1500px,100%); margin: 0 auto 12px; }
.edit-title-row .record-title { margin: 4px 0 0; }


/* ── Project record-page alert pills (all users) ────────────────────── */
.alert-pill-row { display:flex; flex-wrap:wrap; gap:8px; margin:-6px 0 18px; }

/* 11u — the project alert pills on an INVENTORY record's "Allocated To Projects"
   list. A row of its own under the deliveries line, and absent entirely when the
   project is clean (Chris's quiet variant), so a healthy item costs nothing.
   Its own class rather than .alert-pill-row because that one carries a -6px top
   and an 18px bottom margin sized for the record header, which here would pull
   the pills into the deliveries line and push the next project a third of a row
   away. Slightly smaller than the base pill, and the reason is measured, not
   assumed: in a 700px column all four default labels come to 606px of pills at
   0.68rem and 638px at the base 0.72rem. Both fit on one line today, but 0.72
   leaves only ~44px of slack once the gaps are counted, and these labels are
   admin-editable — one rename to something longer and the row wraps. 0.68
   leaves ~76px. (The header's 0.85rem treatment needs two lines here already.) */
.alloc-alert-row { display:flex; flex-wrap:wrap; gap:6px; margin-top:5px; }
.alloc-alert-row .alert-pill { font-size:0.68rem; padding:2px 8px; }

/* 09p — ONE PILL SIZE AT THE TOP OF A RECORD (Chris, 2026-08-09).
   Two base classes sit side by side in that row and were never the same size:
       .alert-pill   0.72rem, padding 2px 8px   (past-due, conflict, not fully
                                                 allocated, qty mismatch)
       .status-pill  0.85rem, padding 4px 14px  (locked, complete, lost,
                                                 expired, submittal)
   Family, weight, uppercase and tracking already matched — both inherit
   --font-body — so the mismatch was size and padding alone. Chris picked the
   .status-pill treatment ("LOCKED FOR EXTERNAL"), so the alert pills come up to
   meet it rather than the lock pill coming down.

   SCOPED TO THE RECORD HEADER ON PURPOSE. .alert-pill is also the table's ALERTS
   column (via .table-alert-pill, whose hidden width sizer must keep matching the
   visible pills exactly — see 07-26p) and the F3 preview panel, which is a 340px
   column where 0.85rem pills would wrap two to a line. Density is a decision per
   surface; this is the decision for the record header. Widening the base class
   would have changed three surfaces to fix one. */
.record-title-row .alert-pill-row .alert-pill {
  font-size: 0.85rem;
  padding: 4px 14px;
}
.alert-pill {
  display:inline-flex; align-items:center;
  font-size:0.72rem; font-weight:500; padding:2px 8px; border-radius:99px;
  text-transform:uppercase; letter-spacing:0.04em; white-space:nowrap;
}
.alert-pill-green  { background:var(--sev-low-bg); color:var(--sev-low-text); border:0.5px solid var(--sev-low-border); }
/* 07a — "not checked": neither an alert nor an all-clear (project-alerts.js allClearHtml) */
.alert-pill-muted  { background:var(--surface-subtle); color:var(--text-tertiary); border:0.5px solid var(--border-default); }
.alert-pill-red    { background:var(--sev-high-bg); color:var(--sev-high-text); border:0.5px solid var(--sev-high-border); }   /* Past-due delivery = threshold crossed */
.alert-pill-orange { background:var(--alert-conflict-bg); color:var(--alert-conflict-text); border:0.5px solid var(--alert-conflict-border); }   /* Inventory timing conflict = orange */
.alert-pill-yellow { background:var(--sev-med-bg); color:var(--sev-med-text); border:0.5px solid var(--sev-med-border); }   /* No inventory allocated = ORIGINAL colors (Chris) */
.alert-pill-blue   { background:var(--alert-qtymm-bg); color:var(--alert-qtymm-text); border:0.5px solid var(--alert-qtymm-border); }   /* Qty-Delivery Mismatch = blue (2026-08-05k) */
/* Terminal-status pill shown on closed record pages (one at a time). */
.status-pill {
  display:inline-flex; align-items:center;
  font-size:0.85rem; font-weight:500; padding:4px 14px; border-radius:99px;
  text-transform:uppercase; letter-spacing:0.04em; white-space:nowrap;
}
.status-pill-complete { background:var(--sev-low-bg); color:var(--sev-low-text); border:0.5px solid var(--sev-low-border); }
.status-pill-lost     { background:var(--sev-high-bg); color:var(--sev-high-text); border:0.5px solid var(--sev-high-border); }
/* 11k: named by INV_STAGE_META since 11g and never defined, which was inert only
   because an Active item is deliberately not pilled. A registry entry naming a
   class that does not exist is a trap for whoever renders it first. Neutral, so
   that if it ever IS shown it reads as "nothing to report" rather than a state. */
.status-pill-active   { background:var(--surface-subtle); color:var(--text-secondary); border:0.5px solid var(--border-default); }
.status-pill-expired  { background:rgba(147,51,234,0.16); color:#9333ea; border:0.5px solid rgba(147,51,234,0.45); }
.status-pill-locked   { background:rgba(147,51,234,0.16); color:#9333ea; border:0.5px solid rgba(147,51,234,0.45); }
/* 02c: record change history */
.hist-entry { padding:8px 0; border-bottom:0.5px solid var(--border-default); }
.hist-entry:last-child { border-bottom:none; }
.hist-meta { font-size:0.74rem; color:var(--text-tertiary); margin-bottom:3px; }
.hist-line { font-size:0.82rem; padding-left:10px; }
/* 01y — the per-entry history fold. summary is the "More (N)" control; opening is one-way
   (the control hides once the lines are out) — reading history is a fan-out, not a toggle. */
.hist-fold > summary.hist-fold-more { list-style: none; cursor: pointer; display: inline-block;
  font-size: 0.76rem; color: var(--cerio-blue); padding: 3px 10px 1px; user-select: none; }
.hist-fold > summary.hist-fold-more::-webkit-details-marker { display: none; }
.hist-fold[open] > summary.hist-fold-more { display: none; }
.hist-old { color:var(--text-tertiary); text-decoration:line-through; }

/* 31k: Exclusive owned status */
.badge-purple { background:rgba(147,51,234,0.16); color:#9333ea; border:0.5px solid rgba(147,51,234,0.45); }
.badge-blue   { background:rgba(59,109,246,0.14); color:#3B6DF6; border:0.5px solid rgba(59,109,246,0.45); }
.status-pill-submittal-incomplete { background:rgba(59,130,246,0.16); color:#3b82f6; border:0.5px solid rgba(59,130,246,0.45); }
.status-pill-submittal-complete   { background:var(--sev-low-bg); color:var(--sev-low-text); border:0.5px solid var(--sev-low-border); }

/* ── External Lite "Fully Allocated" — enlarged to stand out ────────── */
.lite-alloc-label { font-size:0.75rem; font-weight:500; }
.lite-alloc-pill {
  display:inline-flex; align-items:center; justify-content:center;
  font-weight:500; font-size:0.72rem; text-transform:uppercase; letter-spacing:0.04em;
  min-width:72px; padding:2px 8px; border-radius:99px;
}
.lite-alloc-yes { background:var(--sev-low-bg); color:var(--sev-low-text); border:0.5px solid var(--sev-low-border); }
.lite-alloc-no  { background:var(--sev-high-bg); color:var(--sev-high-text); border:0.5px solid var(--sev-high-border); }

/* ══════════════════════════════════════════════════════
   RECENTLY VIEWED (2026-08-05l, re-homed by H1b) — two surfaces:
   the top-bar clock popout (.hz-rv-menu, shared .hz-menu chrome) and
   the dashboard chip row. Same per-account data, same .rv-item rows.
═══════════════════════════════════════════════════════ */
.rv-title {
  font-size:0.68rem; font-weight:600; letter-spacing:0.08em; text-transform:uppercase;
  color:var(--text-tertiary); padding:4px 12px 6px;
}
.rv-item {
  display:flex; align-items:center; gap:9px; width:100%;
  padding:6px 12px; border-radius:8px; cursor:pointer;
  color:var(--text-secondary); font-size:0.83rem; font-weight:500;
  background:transparent; border:none; text-align:left; font-family:inherit;
  transition:all var(--transition);
}
.rv-item:hover { background:var(--surface-subtle); color:var(--text-primary); }
.rv-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }


/* ── Settings group headers + rows (2026-08-06d reorganization) ──
   Rows use a class (not inline display) so role-gating code that toggles
   style.display '' / 'none' restores the flex layout correctly. */
/* ══ H4a (07i) — ONE ADMIN CARD FAMILY ═══════════════════════════════════
   Settings and User Admin were built at different times and looked it. Both
   drew "a bordered panel with a blue left edge", but Settings did it with the
   SAME inline style repeated on eight panels (border-left:4px, margin:20px) on
   top of .admin-info, while User Admin used the real .adm-card class
   (border-left:3px, its own shadow and hover). Even the group headers differed —
   0.08em letter-spacing with a rule under it in Settings, 0.06em and no rule in
   User Admin. Two apps, one product.
   The family now has ONE look and TWO variants, because the two things are not
   the same object: .adm-card is a NAVIGATION TILE (clickable, hover-lifts) and
   .set-panel is a SETTINGS PANEL (holds live controls — a slider, a dropdown,
   toggle buttons — and must not read as a link). Everything they share lives
   here; only the interaction differs.
   .admin-info is deliberately NOT part of this: it is the flat inline
   explainer used 19 times inside User Admin, and it stays flat. Elevation is
   for objects, not for notes. (One of those .admin-info panels is also what
   js/main.js:919 walks up to with .closest() to hide Recently Deleted Inventory
   from externals — the Settings rewrite stays clear of it.) */
.set-panel {
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-left: 3px solid var(--cerio-blue);   /* was 4px inline; .adm-card is 3px */
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 16px 18px 18px;
  margin-bottom: 14px;
  font-size: 0.88rem;
  color: var(--text-secondary);
  line-height: 1.6;
}
/* Titles read identically in both variants. Both move to the display weight
   H3a set for record and section titles; .adm-card-title was still at 500,
   left over from before Horizon. A bare <strong> cannot be used for these —
   the Theme-Guide rule near the top of this file pins every <strong> to 500. */
.set-panel-title, .adm-card-title {
  font-family: var(--font-display);
  font-size: 0.98rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--cerio-blue);
}
.set-sub, .adm-card-sub { font-size: 0.8rem; color: var(--text-secondary); line-height: 1.45; }
.set-panel-title + .set-sub, .set-row-title + .set-sub { margin-top: 3px; }
/* A row inside a panel (Training Guide / Welcome Tour / Diagnostics, the two
   saved-view rows): secondary to the panel title, so text weight, not brand. */
.set-row-title { font-size: 0.9rem; font-weight: 600; color: var(--text-primary); }

/* ── Settings group headers + rows (2026-08-06d reorganization) ──
   Rows use a class (not inline display) so role-gating code that toggles
   style.display '' / 'none' restores the flex layout correctly. */
.settings-grp, .adm-group-label {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-tertiary);
  padding-bottom: 6px; border-bottom: 0.5px solid var(--border-default);
}
.settings-grp { margin: 24px 0 12px; }

/* ── 07j: Settings laid out as a CARD GRID, matching User Admin (Chris) ──
   07i made the two screens share a look; this makes them share a LAYOUT. Done
   entirely in CSS — not one line of the Settings markup changes, so every id,
   handler and role-gating hook is untouched by construction rather than by
   careful editing. (Same reasoning as the bento dashboard in 07c.)
   The `!important` is not decoration: `.view.active` carries
   `display: block !important` so that a view always beats the hide rule, and
   only a MORE SPECIFIC !important can restack it. #id + .class (1,1,0) beats
   .class + .class (0,2,0) deterministically, and the inactive case is
   untouched — `.view { display:none !important }` still hides this view when
   .active is absent, which the audit pins. */
#view-settings.active {
  display: grid !important;
  /* TWO columns, not User Admin's auto-fill. Measured, not guessed: at the app's
     1608px content width User Admin's `minmax(260px,1fr)` yields FIVE columns,
     which suits it because its card subs are short phrases ("Light or dark
     interface"). Settings subs are full sentences, and at five columns they
     wrap to four cramped lines. Two also divides this page's groups cleanly —
     Appearance & Display has four panels (2×2) and Account has two (1×2) — so
     no card is left orphaned on a row of its own, which is what an auto-filling
     grid does to a group of four at three columns. */
  grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
  max-width: 1240px;         /* keeps the two cards a readable width on a wide screen */
  gap: 12px;
  align-content: start;      /* rows keep their height; no stretching down the page */
}
/* The page title and the group headers are full-width rules across the grid. */
#view-settings > .view-header,
#view-settings > .settings-grp { grid-column: 1 / -1; }
/* Grid gap replaces the stacked margins, or the spacing doubles up. */
#view-settings > .set-panel { margin-bottom: 0; }
#view-settings > .settings-grp { margin: 14px 0 0; }
#view-settings > .settings-grp:first-of-type { margin-top: 0; }
/* 07k (Chris): EVERY panel is ONE COLUMN WIDE — no exceptions, no spanning.
   07j widened the row-built panels (My Saved Views, Help) on the theory that a
   list of rows needs the room. Rendering it at column width proved the theory
   wrong: at 614px those rows sit perfectly well, and a card twice the size of
   its neighbours is the opposite of "one card family". The only cost is that a
   group holding a single panel leaves the other half of its row empty — which
   reads as a section break, not a mistake, because the group header already
   rules the full width above it.
   There is deliberately NO rule here. The audit asserts that no
   `grid-column: 1 / -1` is applied to a .set-panel, so a future "just this one
   should be wider" change has to be argued for rather than slipped in. */
/* One column below tablet width; a 420px minimum would otherwise overflow. */
@media (max-width: 900px) {
  #view-settings.active { grid-template-columns: 1fr; }
}
/* The admin one is a full-width row inside the card grid, so it keeps its
   grid placement and a rhythm that suits sitting between rows of tiles. */
.adm-group-label { grid-column: 1 / -1; margin: 14px 0 2px; }
.settings-grp:first-of-type, .adm-group-label:first-child { margin-top: 0; }
.settings-row { display:flex; align-items:center; gap:12px; }


/* ── Table density (2026-08-06f): per-user pref, body class ──
   Compact tightens the data tables (projects, inventory, admin lists share
   .proj-table). Comfortable = the unchanged default. */
body.density-compact .proj-table td { padding:5px 8px; }
body.density-compact .proj-table th { padding:6px 8px; }


/* ── Allocations Chart motion (2026-08-06g · F1) ──
   Motion only where it carries meaning: hovered/isolated ribbons animate their
   dashes in the direction units flow (inventory → project); nodes with a
   past-due delivery pulse once per cycle. Both fall back to static under
   prefers-reduced-motion — highlight and dot remain, movement stops. */
.flow-ribbon { transition: opacity .18s ease; }
.flow-ribbon-hot { stroke-dasharray: 10 7; animation: flow-dash 1.1s linear infinite; }
@keyframes flow-dash { to { stroke-dashoffset: -34; } }
.flow-pastdue-ring { transform-box: fill-box; transform-origin: center; animation: flow-pulse 1.8s ease-out infinite; }
@keyframes flow-pulse {
  0%   { transform: scale(1);   opacity: .75; }
  70%  { transform: scale(2.4); opacity: 0; }
  100% { transform: scale(2.4); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .flow-ribbon { transition: none; }
  .flow-ribbon-hot { animation: none; stroke-dasharray: none; }
  .flow-pastdue-ring { animation: none; opacity: 0; }
}

/* ══ LABELS & THRESHOLDS — display-first editor (2026-08-06j) ═══════════
   Every configurable item renders as it does in the app and opens an edit
   dialog on click. Tokens only, so both themes come free. The ✎ badge on
   hover is the discoverability cue for click-to-edit (mockup-approved). */
.cfg-hint { font-size: 0.75rem; color: var(--text-tertiary); margin: 0 0 12px; }
.cfg-grp-label { font-size: 0.68rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-tertiary); margin: 10px 0 6px; }
.cfg-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 4px; }
.cfg-edit { cursor: pointer; position: relative; transition: transform 0.12s, box-shadow 0.12s; }
.cfg-edit:hover { transform: translateY(-1px); box-shadow: 0 2px 8px rgba(0,0,0,0.14); }
.cfg-edit:hover::after { content: "✎"; position: absolute; top: -7px; right: -7px; width: 16px; height: 16px; border-radius: 50%; background: var(--cerio-blue); color: #fff; font-size: 0.6rem; display: flex; align-items: center; justify-content: center; pointer-events: none; }
/* Stage labels render as plain text in the app; this neutral chip is only the
   click target on the config page. */
.cfg-pill-plain { display: inline-flex; align-items: center; padding: 4px 12px; border-radius: 999px; font-size: 0.78rem; font-weight: 600; background: var(--surface-subtle); color: var(--text-secondary); border: 0.5px solid var(--border-strong); }
.cfg-chip { display: inline-flex; align-items: center; gap: 7px; padding: 6px 14px; border-radius: 999px; font-size: 0.82rem; font-weight: 600; background: var(--cerio-blue-tint); color: var(--cerio-blue); border: 1px solid var(--cerio-blue); }
.cfg-chip-off { background: transparent; color: var(--text-tertiary); border: 1px dashed var(--border-strong); font-weight: 500; }
.cfg-chip-sub { font-size: 0.68rem; font-weight: 500; opacity: 0.75; }
.cfg-stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 12px; }
.cfg-stat { display: flex; flex-direction: column; align-items: flex-start; text-align: left; gap: 2px; border: 0.5px solid var(--border-default); border-radius: var(--radius-sm); padding: 14px 16px; background: var(--surface-page); }
.cfg-stat-num { font-size: 1.6rem; font-weight: 700; color: var(--cerio-blue); line-height: 1.1; }
.cfg-stat-num small { font-size: 0.85rem; font-weight: 600; color: var(--text-tertiary); margin-left: 3px; }
.cfg-stat-lbl { font-size: 0.82rem; font-weight: 600; color: var(--text-primary); }
.cfg-stat-drv { font-size: 0.72rem; color: var(--text-tertiary); line-height: 1.45; }
.cfg-prob-row { display: flex; gap: 12px; align-items: baseline; width: 100%; text-align: left; padding: 7px 10px; border: 0; border-radius: var(--radius-sm); background: transparent; font-size: 0.82rem; }
.cfg-prob-row:hover { background: var(--surface-subtle); }
.cfg-prob-row b { flex-shrink: 0; width: 44px; text-align: right; color: var(--cerio-blue); }
.cfg-prob-row span { color: var(--text-secondary); }
.cfg-prob-row.cfg-edit:hover { transform: none; box-shadow: none; }
.cfg-prob-row.cfg-edit:hover::after { top: 6px; right: 8px; }
.cfg-edit-where { font-size: 0.78rem; color: var(--text-secondary); margin: 0 0 12px; }
.cfg-edit-prevlbl { font-size: 0.68rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 6px; }
.cfg-edit-preview { padding: 14px; background: var(--surface-page); border: 0.5px solid var(--border-default); border-radius: var(--radius-sm); margin-bottom: 12px; }
.cfg-edit-def { font-size: 0.73rem; color: var(--text-tertiary); margin-top: 8px; }
.cfg-edit-def a { color: var(--cerio-blue); cursor: pointer; text-decoration: underline; }
.cfg-edit-check { display: flex; gap: 8px; align-items: center; font-size: 0.85rem; margin-bottom: 8px; cursor: pointer; }
/* 06k (Chris): on the CONFIG page the pills browse at one uniform size —
   .status-pill (0.85rem / 4px 14px) and .alert-pill (0.72rem / 2px 8px)
   genuinely differ in the app, which read as clutter here. Scoped to the
   editor rows only; the edit dialog's live preview keeps the REAL size, so
   what the preview promises is still exactly what the app renders. */
#labels-editor .cfg-row .alert-pill,
#labels-editor .cfg-row .status-pill {
  font-size: 0.78rem; padding: 4px 12px;
}

/* ══ FAVORITES (2026-08-06o) ═══════════════════════════════════════════
   Heart-in-a-circle in the horizon bar (hover opens the list) plus the
   toggle beside a project's name on its record page. Pink is a dedicated
   token so the fill reads the same in both themes and never collides with
   the alert/severity families. */
:root            { --fav-pink: #E4478E; }
[data-theme="dark"] { --fav-pink: #FF7EB6; }
.hz-fav { position: relative; display: flex; flex-shrink: 0; }
.hz-fav-menu { min-width: 258px; }
.fav-empty { font-size: 0.8rem; color: var(--text-tertiary); padding: 4px 12px 8px; line-height: 1.5; }

/* Record-page toggle: outline heart in a circle, filling pink when on. */
.fav-btn {
  width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0.5px solid var(--border-strong); background: transparent;
  color: var(--text-tertiary); cursor: pointer;
  transition: color var(--transition), border-color var(--transition), background var(--transition), transform 0.12s;
}
.fav-btn:hover { color: var(--fav-pink); border-color: var(--fav-pink); }
/* In a table Actions cell the heart is a bare icon like edit/delete — no
   circle outline — but keeps the pink fill when the row is a favorite. */
.action-btn.fav-btn { width: auto; height: auto; border: none; border-radius: var(--radius-sm); background: transparent; }
.action-btn.fav-btn:hover { border: none; background: var(--surface-subtle); color: var(--fav-pink); }
.action-btn.fav-btn.is-fav { color: var(--fav-pink); border: none; background: transparent; }
.action-btn.fav-btn.is-fav:hover { background: var(--surface-subtle); }
.fav-btn.is-fav { color: var(--fav-pink); border-color: var(--fav-pink); background: color-mix(in srgb, var(--fav-pink) 12%, transparent); }
.fav-btn:active { transform: scale(0.92); }
@media (prefers-reduced-motion: reduce) { .fav-btn { transition: none; } .fav-btn:active { transform: none; } }
/* .record-title is a block div, so the heart would drop onto the pills line.
   Where a heart IS present, put the title inline so the two sit side by side
   and the alert pills still wrap onto their own line below. Scoped with :has()
   so inventory record pages (no heart) keep their existing block title. */
/* 11d — THE HEART IS CENTRED BY THE LINE BOX, NOT BY A NUDGE. Chris reported it
   sitting high against the title. It was inline-block with `vertical-align:
   middle` and a hand-tuned `margin-bottom: 10px`: middle aligns to the parent's
   x-height midpoint, not to the visual centre of a 2rem display heading, and the
   10px was a correction measured against one font size that stops being right the
   moment the heading wraps or the type scale changes.
   A wrapping flex row centres a 30px circle against whatever the title's line box
   actually is, at any size. The pill row keeps its own line via flex-basis:100%,
   which is what the old inline layout achieved by accident — the comment below
   used to say inventory records had no heart; they have had one since 06s, and
   Chris's screenshot is of an inventory page. */
.record-title-main:has(> .fav-btn) {
  display: flex; flex-wrap: wrap; align-items: center; column-gap: 6px;
}
.record-title-main:has(> .fav-btn) > .record-title { display: block; margin: 0; }
.record-title-main:has(> .fav-btn) > .fav-btn { margin: 0; }
.record-title-main:has(> .fav-btn) > .alert-pill-row { flex-basis: 100%; margin-top: 2px; }

/* ══ ATTENTION CHIP + BENTO DASHBOARD (2026-08-07c) ═══════════════════
   The chip: LETTER = kind of record, COLOUR = state. Solid fills (not the pale pill
   backgrounds) because it is an 18px mark that has to read at a glance.
   01e (Chris): green for a project with no alerts, purple for an inventory item. The
   alert colours are untouched. Grey no longer means "fine" — it means UNKNOWN, and is
   kept for a record not in the cache and for a closed project, neither of which can
   honestly claim an all-clear. */
:root {
  --attn-none: #8B8A82;
  --attn-clear: #1F7A4D; --attn-inventory: #6B3FA0;
  --attn-pastdue: #E5484D; --attn-conflict: #D97B29;
  --attn-unallocated: #C79A1A; --attn-qtymismatch: #3B62E5;
}
[data-theme="dark"] {
  --attn-none: #5A6070;
  /* lifted for the dark ground, the same treatment --cerio-blue gets */
  --attn-clear: #2E9C68; --attn-inventory: #8B62C9;
  --attn-pastdue: #E5484D; --attn-conflict: #D97B29;
  --attn-unallocated: #C79A1A; --attn-qtymismatch: #3B62E5;
}
.attn-chip {
  width: 18px; height: 18px; border-radius: 4px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.62rem; font-weight: 700; color: #fff; line-height: 1;
}
.attn-none        { background: var(--attn-none); }
.attn-unknown     { background: var(--attn-none); }   /* 01e: not in the cache, or closed */
.attn-clear       { background: var(--attn-clear); }
.attn-inventory   { background: var(--attn-inventory); }
.attn-pastdue     { background: var(--attn-pastdue); }
.attn-conflict    { background: var(--attn-conflict); }
.attn-unallocated { background: var(--attn-unallocated); }
.attn-qtymismatch { background: var(--attn-qtymismatch); }

/* Bento: the hero spans two rows on the left, the existing KPI cards flow
   beside and beneath it. .kpi-grid is display:contents so its cards become
   items of THIS grid — no KPI markup, id or handler changes. */
/* 01x — 24 columns, because Chris cut variant B's cards slimmer than a 12-grid can express:
   hero 14/24 (58% — "slightly less wide" than the old 8/12), the four stat cards 5/24 each in a
   2×2 beside it, and the freed left half of the next row is the Tasks tile beside Alerts. */
.dash-bento { display: grid; grid-template-columns: repeat(24, 1fr); gap: 14px; margin-bottom: 18px; }
.dash-bento > .kpi-grid { display: contents; }
/* 08g (D1) — SPANS TAKEN FROM THE APPROVED MOCKUP, NOT INVENTED.
   Rows 1-2: hero (8 wide, 2 tall) with the two SHORT stat tiles stacked beside
   it. Row 3: the two TALL stat tiles — the ones carrying context strips — then
   the Alerts tile at 6. Alerts is deliberately NOT full-width: at 12 it strands
   each count ~1400px from its own label, and it forces the two remaining stat
   tiles up beside the hero, which then holds two rows of empty blue whenever
   fewer than six items need triage — the normal case.
   Targeted by id, never by :nth-child — position must not decide layout, the
   same rule applyExternalDashboard() now follows for visibility. */
.dash-bento > .kpi-grid > .kpi-card { grid-column: span 12; }
.dash-bento > .dash-alerts { grid-column: span 24; }
.dash-bento > .dash-tasks { grid-column: span 24; }
@media (min-width: 1100px) {
  .dash-bento > .kpi-grid > #kpi-active-card,
  .dash-bento > .kpi-grid > #kpi-units-card,
  .dash-bento > .kpi-grid > #kpi-highprob-card,
  .dash-bento > .kpi-grid > #kpi-deliveries-card { grid-column: span 5; }
  .dash-bento > .dash-tasks           { grid-column: span 12; }
  .dash-bento > .dash-alerts          { grid-column: span 12; }

  /* SHORT-HERO VARIANT (0–3 items needing triage) — set by renderDashHero.
     The mockup drew the hero with a full six rows. At one row that same
     two-row span leaves the tile 71% empty blue, and at zero rows the reduced
     hero freed half a grid row, so auto-flow scattered Total Units to column 1
     and stranded the Alerts tile beside six dead columns. Measured, not
     assumed: 0/1/2/3 rows gave 42/71/58/44% empty.
     So below four items the hero drops to ONE row and the two short stat tiles
     sit side by side instead of stacking — 8 + 2 + 2 = 12, still a full
     rectangle, with the hero sized to what it actually has to say. */
  /* hero-short (0–3 triage items): the hero takes the full band at one row and the four
     cards line up beneath it — the 2×2 has nothing to stand beside. */
  .dash-bento.hero-short > .dash-hero { grid-column: span 24; grid-row: auto; }
  .dash-bento.hero-short > .kpi-grid > #kpi-active-card,
  .dash-bento.hero-short > .kpi-grid > #kpi-units-card,
  .dash-bento.hero-short > .kpi-grid > #kpi-highprob-card,
  .dash-bento.hero-short > .kpi-grid > #kpi-deliveries-card { grid-column: span 6; }
}

/* ══ D1 — STAT TILES ═══════════════════════════════════════════════════
   The accent notch replaces the old kpi-tint-* background washes: a 3px edge
   states the tile's character without tinting the whole card, which is the
   same language .adm-card, .set-panel and the record KPI cards already use. */
.kpi-card { padding-left: 26px; display: flex; flex-direction: column; }
.kpi-card::before {
  content: ''; position: absolute; left: 0; top: 14px; bottom: 14px;
  width: 3px; border-radius: 0 3px 3px 0; background: var(--cerio-blue);
}
.kpi-card.kpi-good::before { background: var(--green); }
/* Context strip: a tall tile earns its height with detail rather than air. */
.kpi-ctx { margin-top: auto; padding-top: 12px; font-size: 0.72rem; color: var(--text-tertiary); line-height: 1.5; }
.kpi-ctx:empty { display: none; }
.kpi-segs { display: flex; gap: 3px; margin: 6px 0 4px; }
.kpi-segs i { height: 5px; border-radius: 99px; display: block; }

/* ══ D1 — ALERTS BREAKDOWN TILE ════════════════════════════════════════
   Counts come from updateAlertKPIs, the same function that feeds the nav badge
   and the Alerts-tab chips, so the tile cannot disagree with them. */
.dash-alerts {
  background: var(--surface-card); border: 0.5px solid var(--border-default);
  border-left: 3px solid var(--red); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 16px 18px 17px;
}
/* 01x — the Tasks tile wears the same chrome as Alerts beside it, blue-edged. Its rows are
   the Tasks page's own row classes, so the two surfaces cannot drift apart visually. */
.dash-tasks {
  background: var(--surface-card); border: 0.5px solid var(--border-default);
  border-left: 3px solid var(--cerio-blue); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 16px 18px 17px;
}
.dash-tasks .task-row { padding: 7px 0; }
.dash-tasks-more { font-size: 0.76rem; color: var(--text-tertiary); padding-top: 8px; }
.dash-alerts.is-clear { border-left-color: var(--green); }
.dash-alerts-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.dash-alerts-head h3 {
  font-family: var(--font-display); font-size: 0.86rem; font-weight: 700;
  letter-spacing: -0.01em; color: var(--text-primary);
}
.dash-alerts-link {
  margin-left: auto; background: none; border: none; cursor: pointer;
  font-size: 0.72rem; font-weight: 600; color: var(--cerio-blue); font-family: inherit;
}
.dash-alerts-link:hover { text-decoration: underline; }
.dash-alert-row {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 7px 2px; border: none; border-top: 0.5px solid var(--border-default);
  background: transparent; cursor: pointer; font-family: inherit; text-align: left;
  transition: background var(--transition);
}
.dash-alert-row:first-child { border-top: none; }
.dash-alert-row:hover { background: var(--surface-subtle); }
.dash-alert-row b {
  margin-left: auto; font-family: var(--font-display); font-weight: 500;
  font-size: 0.95rem; color: var(--text-primary);
}
.dash-alerts-total {
  margin-top: 12px; font-size: 0.75rem; color: var(--text-tertiary);
}
.dash-alerts-total span:first-child {
  font-family: var(--font-display); font-weight: 500; font-size: 0.95rem;
  color: var(--text-primary);
}
.dash-alerts-meter {
  height: 5px; border-radius: 99px; background: var(--surface-subtle);
  overflow: hidden; margin-top: 7px;
}
.dash-alerts-meter b { display: block; height: 100%; background: var(--cerio-blue); transition: width 0.3s ease; }
@media (prefers-reduced-motion: reduce) { .dash-alerts-meter b { transition: none; } }
.dash-alerts-foot { margin-top: 5px; font-size: 0.72rem; color: var(--text-tertiary); }
.dash-hero {
  grid-column: span 14; grid-row: span 2;
  /* nothing to triage: the tile shrinks to its message instead of holding
     two rows of empty blue. */
  background: linear-gradient(150deg, #011C7E, #012BBF 62%, #0A3BE0);
  color: #fff; border-radius: var(--radius); padding: 16px 18px;
  box-shadow: var(--shadow); overflow: hidden;
}
[data-theme="dark"] .dash-hero { background: linear-gradient(150deg, #0B1230, #13205C 60%, #1B2E86); }
.dash-hero.is-empty { grid-row: auto; }
.hero-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.hero-head h3 { font-size: 0.95rem; font-weight: 700; color: #fff; }
.hero-count { margin-left: auto; font-size: 0.72rem; color: rgba(255,255,255,0.7); }
.hero-row {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 0; border: none; border-top: 1px solid rgba(255,255,255,0.14);
  background: transparent; color: #fff; font-size: 0.83rem; cursor: pointer;
  font-family: inherit;
}
.hero-row:first-child { border-top: none; }
.hero-row:hover .hero-nm { text-decoration: underline; }
.hero-row:focus-visible { outline: 2px solid rgba(255,255,255,0.85); outline-offset: -2px; }
.hero-nm { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 04l — which PRODUCT raised the alert (division, plus the machine after the first). It sits
   between the name and the reason, reads quieter than the name and louder than the reason,
   and never shrinks: an ellipsised "PowerGen…" would leave the machine number out, which is
   the one part of it that disambiguates two rows of the same project. */
.hero-prod { font-size: 0.74rem; font-weight: 600; letter-spacing: 0.02em; color: rgba(255,255,255,0.9);
             background: rgba(255,255,255,0.12); border-radius: 4px; padding: 1px 6px; white-space: nowrap; flex-shrink: 0; }
.hero-why { color: rgba(255,255,255,0.72); font-size: 0.78rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hero-when { margin-left: auto; font-size: 0.72rem; color: rgba(255,255,255,0.65); white-space: nowrap; flex-shrink: 0; }
.hero-empty { font-size: 0.83rem; color: rgba(255,255,255,0.78); padding: 10px 0 4px; line-height: 1.5; }
/* 1099, not 1000: the bento spans above switch on at 1100, so a 1000–1099
   window left the hero spanning 8 columns and TWO rows while the stat tiles
   were back at span 6 and could not fit beside it — they dropped below and the
   hero's entire second row rendered as void blue. Measured at 1050px before
   this was corrected. One breakpoint, two regimes, no band in between. */
@media (max-width: 1099px) {
  .dash-hero { grid-column: span 24; grid-row: auto; }
  .dash-bento > .kpi-grid > #kpi-active-card,
  .dash-bento > .kpi-grid > #kpi-units-card,
  .dash-bento > .kpi-grid > #kpi-highprob-card,
  .dash-bento > .kpi-grid > #kpi-deliveries-card { grid-column: span 12; }
}
@media (max-width: 700px) {
  .dash-bento > .kpi-grid > .kpi-card { grid-column: span 12; }
  .hero-why { display: none; }   /* keep name + date readable on a phone */
}

/* ══ D2 (08h) — THE FOUR PANEL TILES ═══════════════════════════════════
   Upcoming Deliveries, the condensed availability strip and the two demand
   tiles. One shared shell (.dash-panel) so the four agree with each other and
   with .dash-alerts, which already sets this pattern: card surface, hairline,
   3px brand edge, the elevation token. Same family as .set-panel and .rec-kpi —
   a tile's character is stated by its left edge, never by tinting the card. */
.dash-panel {
  /* 01ze — 24-column units since 01x; these two rules were written for the 12-column bento
     (6/12 and 12/12) and were MISSED in that migration, so all four tiles rendered at QUARTER
     width — Chris caught it ("make upcoming delivery dates as wide as the tasks", which is
     exactly the half-band width they were designed to have). */
  grid-column: span 24;
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-left: 3px solid var(--cerio-blue);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 16px 18px 17px;
  min-width: 0;   /* a grid item defaults to min-content; without this a long
                     project name would push the tile past its column */
  /* Paired tiles are equal height because they share a grid row, so the shorter
     one's content must be able to GROW into that height or the tile renders as
     a band of white — the same void the hero had in D1, measured at ~130px on
     the availability tile beside the taller Upcoming list. */
  display: flex; flex-direction: column;
}
@media (min-width: 1100px) { .dash-panel { grid-column: span 12; } }
.dash-panel-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px; }
.dash-panel-head h3 {
  font-family: var(--font-display); font-size: 0.95rem; font-weight: 700;
  letter-spacing: -0.01em; color: var(--text-primary);
}
.dash-panel-link {
  margin-left: auto; border: none; background: none; padding: 0;
  font-family: inherit; font-size: 0.76rem; font-weight: 500;
  color: var(--cerio-blue); cursor: pointer; white-space: nowrap;
}
.dash-panel-link:hover { text-decoration: underline; }
.dash-panel-link:focus-visible { outline: 2px solid var(--cerio-blue); outline-offset: 2px; border-radius: 3px; }

/* ── Upcoming Delivery Dates: one row per delivery ── */
.up-row {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  padding: 9px 0; border: none; border-top: 0.5px solid var(--border-default);
  background: transparent; font-family: inherit; font-size: 0.84rem;
  color: var(--text-primary); cursor: pointer;
}
.up-row:first-child { border-top: none; }
.up-row:hover .up-nm { text-decoration: underline; }
.up-row:focus-visible { outline: 2px solid var(--cerio-blue); outline-offset: -2px; }
/* 12f: these four rules were the app's tidiest argument FOR a mono face — a
   stacked column of dates that lined up because every digit was one width.
   They are now the body face like everything else, and the dates in this
   column no longer align to each other. min-width below is what is left
   holding the column together; it was already there for "Sep 02" vs "Oct 05"
   and now carries more weight than it used to. This is the known cost of
   dropping the family (see the note beside --font-display) and it is not a
   bug to be fixed by re-adding `monospace` here. */
.up-when {
  font-family: var(--font-body); font-size: 0.76rem; color: var(--text-secondary); white-space: nowrap; flex-shrink: 0;
  min-width: 52px;   /* dates align down the column even at "Sep 02" vs "Oct 05" */
}
.up-row.is-overdue .up-when { color: var(--red); font-weight: 500; }
.up-nm {
  font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;   /* the only element allowed to give way when the tile narrows */
}
.up-row .alert-pill { flex-shrink: 0; }
.up-units {
  margin-left: auto; font-family: var(--font-body); font-size: 0.82rem; flex-shrink: 0; padding-left: 8px;
}
.up-foot { margin-top: 9px; font-size: 0.72rem; color: var(--text-tertiary); }
.upcoming-empty { font-size: 0.83rem; color: var(--text-tertiary); padding: 10px 0 4px; line-height: 1.5; }

/* ── Condensed availability strip ── */
#dash-availability > .inv-avail-chart { flex: 1; display: flex; flex-direction: column; }
.avail-strip { flex: 1; min-height: 108px; display: flex; align-items: flex-end; gap: 6px; }
.avail-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; height: 100%; min-width: 0; }
.avail-num {
  font-family: var(--font-body); font-size: 0.72rem; font-style: normal;
  color: var(--text-secondary);
  line-height: 1; margin-bottom: 4px; flex-shrink: 0;
}
/* Column, bottom-justified: the count is the last thing above the bar, so it
   rides up and down with it instead of sitting in a detached row of numbers. */
.avail-bar-wrap {
  flex: 1; width: 100%; min-height: 0;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
}
.avail-bar-wrap i { display: block; width: 100%; background: var(--cerio-blue); border-radius: 3px 3px 0 0; flex-shrink: 0; }
.avail-bar-wrap i.is-zero { height: 3px; background: var(--border-default); border-radius: 2px; }
.avail-lbl { font-size: 0.7rem; color: var(--text-tertiary); white-space: nowrap; }
.avail-lbl.is-now { color: var(--text-primary); font-weight: 500; }
.avail-foot { margin-top: 9px; font-size: 0.72rem; color: var(--text-tertiary); }

/* ── Demand vs supply paired meter rows (08i) ──
   Two series, so two thin bars per category on ONE shared scale, stacked with a
   2px gap so adjacent fills never touch. Series colour lives ONLY in the bar and
   the legend swatch; the value and the category name stay in text ink, so nobody
   has to read colour to read a number.

   THE DEMAND BLUE IS NOT THE RAW BRAND TOKEN, deliberately. --cerio-blue is
   #012BBF (OKLCH L 0.40), too dark to sit in the same lightness band as any
   readable warm colour: paired against one, the demand bar reads as heavier
   before a single number is compared, which biases a chart whose entire job is
   relative length. --mix-demand is the nearest in-band step of that same hue.

   08l/08m — THE SUPPLY COLOUR IS ORANGE, NOT AMBER. Chris: "can we replace this
   mustard colour with orange? we used to use orange on this site." 08i had used
   #BA7517, a dark yellow-orange that reads as mustard at bar size. 08l shipped a
   mid orange; 08m took Chris's pick of the brighter one, #DB6C22.
   ON THE HISTORICAL ORANGE: I could not recover it. This repo's history begins
   2026-07-15 and --orange was ALREADY #012BBF in every commit in it — the token
   was repurposed to the blue before the history starts (see the note at the
   --cerio-blue definition). Both logo files sample as pure blue. So these values
   are a chosen orange, not the original one; if the real brand hex turns up,
   swap the two lines below and nothing else — every consumer reads the tokens.

   Both pairs were checked with the palette validator rather than by eye, in
   their own mode, against their own surface: #DB6C22 clears the lightness band,
   chroma floor, CVD separation, normal-vision floor and 3:1 contrast against
   BOTH the light and the dark surface, and against both blues. Orange is a
   STRICT IMPROVEMENT on the mustard for colour-blind readers — worst-adjacent
   tritan separation goes from ΔE 21 to ΔE 31, because orange sits further from
   blue in hue than a yellow does.

   ONE SUPPLY VALUE FOR BOTH THEMES, which is unusual here and deserves a note:
   every other themed pair in this file steps separately, because a light-mode
   value normally sits outside the dark band (0.48–0.67) or vice versa. #DB6C22
   lands inside BOTH bands, so a dark step would be a different colour for no
   measured reason. The dark override is kept as an explicit line rather than
   dropped, so the pairing stays visible and a future dark-only adjustment has an
   obvious home. --mix-demand still steps per theme; it has to. */
:root            { --mix-demand: #2E52D6; --mix-supply: #DB6C22; }
[data-theme="dark"] { --mix-demand: #5B79F5; --mix-supply: #DB6C22; }
.mix-row { display: flex; align-items: center; gap: 10px; padding: 6px 0; font-size: 0.82rem; }
.mix-nm {
  width: 118px; flex-shrink: 0; color: var(--text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mix-bars { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.mix-line { display: flex; align-items: center; gap: 10px; }
.mix-meter {
  flex: 1; min-width: 0; height: 7px; border-radius: 99px;
  background: var(--surface-subtle); overflow: hidden;
}
.mix-meter b { display: block; height: 100%; border-radius: 99px; }
.mix-meter.d b { background: var(--mix-demand); }
.mix-meter.s b { background: var(--mix-supply); }
.mix-val {
  font-family: var(--font-body); font-size: 0.78rem;
  flex-shrink: 0; min-width: 34px; text-align: right; color: var(--text-secondary);
}
.mix-foot { margin-top: 9px; font-size: 0.72rem; color: var(--text-tertiary); }

/* Legend — always present, because two series must never be colour-only.
   margin-left:auto mirrors .dash-panel-link so it sits at the right edge of the
   head. The head is allowed to wrap ONLY on these two tiles: at half-width,
   "Demand vs Supply by Power Node (kW)" plus two keys can exceed one line, and
   the legend dropping to a second row is better than a squeezed title. Scoped by
   id so no other dash-panel head changes behaviour. */
#dash-mix-tier .dash-panel-head,
#dash-mix-node .dash-panel-head { flex-wrap: wrap; row-gap: 4px; }
.mix-legend { display: flex; align-items: center; gap: 12px; flex-shrink: 0; margin-left: auto; }
.mix-key {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 0.72rem; color: var(--text-tertiary); white-space: nowrap;
}
.mix-key::before {
  content: ''; width: 9px; height: 9px; border-radius: 2px; flex-shrink: 0;
}
.mix-key.d::before { background: var(--mix-demand); }
.mix-key.s::before { background: var(--mix-supply); }

@media (max-width: 520px) {
  .mix-nm { width: 88px; }
  .up-when { min-width: 46px; }
  .mix-legend { gap: 8px; }
}

/* ══ 09c — PARTNER COMPANIES LIST + MANAGE DIALOG ═══════════════════════
   The user table's header and its rows now render from ONE grid template.
   They were two independent flex rows that agreed on five columns and not the
   sixth — the header's Actions cell was min-width:230px, the row's was an
   unsized nowrap of about 80px — and since flexbox divides what is left after
   the fixed cells, the two remainders put every flexible column in a different
   place. Alignment could not be tuned into working; it needed one source.
   Actions is gone entirely: the per-user Manage button moved into the company
   dialog, so the table is five columns of pure information. */
.pc-grid {
  display: grid;
  grid-template-columns: minmax(150px, 1.3fr) minmax(180px, 1.7fr) 110px 96px 118px;
  align-items: center; gap: 12px; padding: 9px 12px 9px 18px;
}
.pc-head {
  font-size: 0.68rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--text-tertiary);
  border-bottom: 0.5px solid var(--border-strong);
}
.pc-row { border-bottom: 0.5px solid var(--border-default); font-size: 0.86rem; }
.pc-row:last-child { border-bottom: 0; }
.pc-row.is-off { opacity: 0.6; }
.pc-sub  { font-size: 0.75rem; color: var(--text-tertiary); }
.pc-dim  { font-size: 0.82rem; color: var(--text-secondary); word-break: break-all; }
.pc-tier { font-size: 0.82rem; color: var(--text-primary); }

/* Company header row: what you READ. Everything editable is behind Manage. */
.pc-co {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; background: var(--surface-subtle); border-radius: 10px;
}
.pc-co-name { font-size: 0.95rem; font-weight: 700; color: var(--text-primary); }
.pc-co-sum  { font-size: 0.8rem; color: var(--text-secondary); }
.pc-co-sum b { font-weight: 500; color: var(--text-primary); }
.pc-manage {
  padding: 5px 14px; font-size: 0.78rem; color: var(--cerio-blue);
  border: 0.5px solid var(--border-strong); border-radius: 7px;
  background: var(--surface-card); white-space: nowrap; flex-shrink: 0;
}
.pc-manage:hover { border-color: var(--cerio-blue); }

/* Manage company dialog */
.cm-field { margin-bottom: 13px; }
.cm-label { display: block; font-size: 0.75rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 4px; }
.cm-hint  { font-size: 0.72rem; color: var(--text-tertiary); margin-top: 3px; }
.cm-sect  {
  font-size: 0.8rem; font-weight: 700; color: var(--text-primary);
  margin: 20px 0 4px; padding-top: 14px; border-top: 0.5px solid var(--border-default);
}
.cm-urow {
  display: grid; grid-template-columns: 1fr 150px auto; gap: 10px; align-items: center;
  padding: 9px 0; border-bottom: 0.5px solid var(--border-default);
}
.cm-urow:last-child { border-bottom: 0; }
.cm-more { font-size: 0.75rem; color: var(--cerio-blue); padding: 4px 8px; white-space: nowrap; }

@media (max-width: 760px) {
  /* Below this the five columns stop fitting; Type and Last Sign-in are the
     two a narrow screen can most afford to lose from the grid. */
  .pc-grid { grid-template-columns: minmax(120px, 1.4fr) minmax(150px, 1.6fr) 112px; }
  .pc-grid > :nth-child(3), .pc-grid > :nth-child(4) { display: none; }
  .cm-urow { grid-template-columns: 1fr 130px auto; }
}

/* ══ 09d (F6) — REVIEW QUEUE SECTIONS ═══════════════════════════════════
   One view holding meeting recommendations (F5), allocation requests and
   project requests. Sections need to read as siblings of equal weight, so the
   heading is deliberately lighter than the view title above them — a second
   h2-sized line would compete with "Review Queue" rather than sit under it. */
.rq-sec { margin-bottom: 26px; }
.rq-sec:last-child { margin-bottom: 0; }
.rq-head {
  display: flex; align-items: baseline; gap: 9px;
  padding-bottom: 7px; margin-bottom: 10px;
  border-bottom: 0.5px solid var(--border-default);
}
.rq-title {
  font-family: var(--font-display); font-size: 1rem; font-weight: 700;
  letter-spacing: -0.01em; color: var(--text-primary); margin: 0;
}
/* The count is a plain figure, not a badge: a coloured pill here would read as
   an alert, and a queue length is information, not a warning. */
.rq-count {
  font-family: var(--font-body); font-size: 0.78rem; color: var(--text-tertiary);
}
.rq-sub { color: var(--text-secondary); font-size: 0.86rem; margin: 0 0 12px; }

/* The intake explainer, collapsed by default (Chris, 09d) — reference
   material, not something you read on every visit. */
.rq-collapse { margin: 0 0 12px; }
.rq-collapse > summary {
  cursor: pointer; font-size: 0.8rem; color: var(--cerio-blue);
  padding: 4px 0; list-style: none; display: inline-flex; align-items: center; gap: 6px;
}
.rq-collapse > summary::-webkit-details-marker { display: none; }
.rq-collapse > summary::before {
  content: '▸'; font-size: 0.7rem; transition: transform 0.15s;
}
.rq-collapse[open] > summary::before { transform: rotate(90deg); }
.rq-collapse > summary:hover { text-decoration: underline; }

/* ══ 09f (F5) — MEETING ASSISTANT ══════════════════════════════════════
   The module renders its own cards; these are the two classes it needs that
   the app did not already have. .ma-group wraps one meeting's proposals in the
   Review Queue — the handed-over module asked for `.admin-card`, which does not
   exist here (.adm-card is the admin hub card, a different thing), so the
   feature owns its own class rather than borrowing one with other semantics. */
.ma-group {
  background: var(--surface-subtle);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius);
  padding: 14px 16px;
}
/* Add Meeting sits on the section heading row, pushed to the right so the
   count stays beside the title where the other two sections have theirs. */
.rq-add {
  margin-left: auto; padding: 4px 12px; font-size: 0.78rem;
  color: var(--cerio-blue); border: 0.5px solid var(--border-strong);
  border-radius: 7px; background: var(--surface-card); white-space: nowrap;
}
.rq-add:hover { border-color: var(--cerio-blue); }

/* ══ 09g — AI MODELS & AVAILABILITY ════════════════════════════════════ */
.aim-row {
  border: 0.5px solid var(--border-default); border-radius: 10px;
  padding: 12px 14px; margin-bottom: 10px; background: var(--surface-card);
}
.aim-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.aim-toggle { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 0.9rem; }
/* The spend figure is muted and monospaced: it is reference, not a headline,
   and tabular figures let two rows be compared down the column. */
.aim-spend {
  margin-left: auto; font-family: var(--font-body);
  font-size: 0.74rem; color: var(--text-tertiary);
}
.aim-hint { font-size: 0.78rem; color: var(--text-secondary); margin: 4px 0 8px; }
.aim-model { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.aim-model .form-input {
  flex: 1; min-width: 220px; font-family: var(--font-body); font-size: 0.8rem;
}
.aim-test-out { font-size: 0.78rem; white-space: nowrap; }
.aim-rate {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 7px 0; border-bottom: 0.5px solid var(--border-default);
}
.aim-rate:last-of-type { border-bottom: 0; }
.aim-rate-name { font-size: 0.78rem; flex: 1; min-width: 180px; color: var(--text-primary); }
.aim-rate label { font-size: 0.74rem; color: var(--text-tertiary); display: inline-flex; align-items: center; gap: 5px; }
.aim-rate .form-input { width: 84px; padding: 4px 8px; font-size: 0.78rem; }
@media (max-width: 700px) {
  .aim-spend { margin-left: 0; width: 100%; }
  .aim-rate-name { width: 100%; flex: none; }
}

/* ═══════════════════════════════════════════════════════
   MEETING ASSISTANT — FIELD SELECTOR (2026-08-09h)

   The settings panel used to print all 54 field checkboxes inline, which
   buried the one fact an admin wants at a glance: how much of a record the
   assistant is allowed to touch. The panel now says that in a sentence and
   the list opens behind Manage fields.

   Chips, not checkboxes: a checkbox row is one column of dead space per item
   and reads as a form to fill in. A chip is the state itself, wraps densely,
   and lets fifty-four of them sit in a space that held twelve.

   Colour carries no meaning alone. "On" is a tint PLUS a ✓ glyph; sensitive
   is a dashed border PLUS a tooltip. Both survive greyscale and CVD.
═══════════════════════════════════════════════════════ */
.maf-summary {
  display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap;
  padding: 12px 14px; margin-top: 6px;
  background: var(--surface-subtle); border: 0.5px solid var(--border-default);
  border-radius: 8px;
}
.maf-summary-main { flex: 1; min-width: 240px; }
.maf-summary-title { font-size: 0.86rem; font-weight: 600; color: var(--text-primary); }
.maf-summary-counts { font-size: 0.84rem; color: var(--text-secondary); margin-top: 3px; }
.maf-summary-counts strong { color: var(--text-primary); }
.maf-dot { color: var(--text-faint); margin: 0 4px; }
.maf-summary-sens,
.maf-summary-empty { font-size: 0.78rem; color: var(--text-tertiary); margin-top: 4px; line-height: 1.45; }
/* The empty state is the DEFAULT state — a fresh install has no fields on —
   so it gets the amber ink rather than reading as a rendering fault. */
.maf-summary-empty { color: var(--amber); }

.maf-search { margin: 10px 0 4px; }
.maf-search input {
  width: 100%; padding: 7px 10px; font-size: 0.84rem;
  border: 0.5px solid var(--border-strong); border-radius: 6px;
  background: var(--surface-card); color: var(--text-primary);
}
.maf-search input:focus { outline: 2px solid var(--cerio-blue); outline-offset: -1px; }

.maf-grp { padding: 10px 0 4px; border-top: 0.5px solid var(--border-default); }
.maf-grp:first-child { border-top: 0; }
.maf-grp-head { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; }
.maf-grp-name { font-size: 0.82rem; font-weight: 600; color: var(--text-primary); }
.maf-grp-count {
  font-size: 0.74rem; color: var(--text-tertiary); font-family: var(--font-body);
}
.maf-grp-all {
  margin-left: auto; background: none; border: 0; cursor: pointer;
  font-size: 0.74rem; color: var(--cerio-blue); font-family: inherit; padding: 2px 4px;
}
.maf-grp-all:hover { text-decoration: underline; }

.maf-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.maf-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: inherit; font-size: 0.78rem; padding: 4px 10px; border-radius: 99px;
  border: 0.5px solid var(--border-strong); background: var(--surface-card);
  color: var(--text-secondary); cursor: pointer;
}
.maf-chip:hover { border-color: var(--cerio-blue); color: var(--text-primary); }
.maf-chip:focus-visible { outline: 2px solid var(--cerio-blue); outline-offset: 1px; }
.maf-chip.on {
  background: var(--cerio-blue-tint); border-color: var(--cerio-blue);
  color: var(--cerio-blue); font-weight: 500;
}
.maf-chip.on::before { content: '✓'; font-size: 0.7rem; }
/* Sensitive = pricing or access control. Dashed in BOTH states so the marking
   is visible whether the field is on or off. */
.maf-chip.warn { border-style: dashed; border-color: var(--sev-med-border); }
.maf-chip.warn.on {
  background: var(--sev-med-bg); border-color: var(--sev-med-border); color: var(--sev-med-text);
}
.maf-legend {
  font-size: 0.76rem; color: var(--text-tertiary); line-height: 1.5;
  margin-top: 12px; padding-top: 10px; border-top: 0.5px solid var(--border-default);
}
.maf-total {
  font-size: 0.76rem; color: var(--text-tertiary); font-family: var(--font-body);
}
@media (max-width: 700px) {
  .maf-summary { flex-direction: column; }
  .maf-total { display: none; }
}


/* ═══════════════════════════════════════════════════════
   F3 — LIST INSPECTOR (2026-08-09n)

   A preview panel beside the projects table. It appears only when a row is
   clicked: a panel that is always populated has to pick a record nobody chose,
   and its empty state would be the state the page ARRIVES in.

   The table is not rebuilt to make room — .ins-main simply becomes a grid
   column, so every id, every handler and both role gates are untouched. That is
   the same technique H2 used for the bento dashboard, and for the same reason:
   rebuilding the markup is where the risk would have been.
═══════════════════════════════════════════════════════ */
.ins-layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; align-items: start; }
.ins-layout.has-panel { grid-template-columns: minmax(0, 1fr) 340px; }
.ins-main { min-width: 0; }   /* without this the table refuses to shrink and overflows the grid */

.ins-panel {
  position: sticky; top: 78px; align-self: start;
  max-height: calc(100vh - 104px); overflow: auto;
  background: var(--surface-card); border: 0.5px solid var(--border-default);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 15px 16px 16px;
}
.ins-top { display: flex; align-items: flex-start; gap: 8px; }
.ins-top .t { flex: 1; min-width: 0; }
.ins-name { font-family: var(--font-display); font-size: 0.95rem; font-weight: 600; line-height: 1.3; overflow-wrap: anywhere; }
.ins-sub { font-size: 0.74rem; color: var(--text-tertiary); margin-top: 3px; }
.ins-open {
  background: transparent; color: var(--cerio-blue); border: 0.5px solid var(--cerio-blue);
  border-radius: var(--radius-sm); padding: 5px 11px; font-size: 0.72rem; font-weight: 500;
  cursor: pointer; white-space: nowrap; font-family: var(--font-body);
}
.ins-open:hover { background: var(--cerio-blue-tint); }
.ins-x {
  background: transparent; border: 0.5px solid var(--border-default); color: var(--text-tertiary);
  border-radius: var(--radius-sm); padding: 5px 9px; font-size: 0.78rem; cursor: pointer;
  line-height: 1; font-family: var(--font-body);
}
.ins-x:hover { border-color: var(--text-secondary); color: var(--text-primary); }
.ins-sec { border-top: 0.5px solid var(--border-default); padding-top: 11px; margin-top: 12px; }
.ins-sec-title {
  font-size: 0.63rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-tertiary); margin-bottom: 7px;
}
.ins-dl { display: flex; justify-content: space-between; gap: 10px; font-size: 0.79rem; color: var(--text-secondary); padding: 3px 0; }
.ins-dl b {
  color: var(--text-primary); font-weight: 500; font-size: 0.75rem;
  font-family: var(--font-body); text-align: right;
}
.ins-dl b.ok  { color: var(--green); }
.ins-dl b.bad { color: var(--red); }
.ins-acts { display: flex; gap: 8px; margin-top: 14px; }
.ins-acts > button { flex: 1; padding: 9px 0; font-size: 0.78rem; }
/* The alert pills come from buildProjectAlertPills, so they carry the row's own
   margins; reset them for the panel rather than styling a second set. */
.ins-panel .alert-pill-row { margin: 9px 0 0; }
/* 28b — ONE PILL SIZE IN THE PREVIEW PANEL (Chris, 2026-08-28: "why are these alert
   pills three different sizes?"). Same mismatch 09p fixed on the record header: the lock
   pill is a .status-pill (0.85rem, 4px 14px, 29px tall) and the alert pills under it are
   .alert-pill (0.72rem, 2px 8px, 22px tall). 09p was scoped to .record-title-row so this
   340px panel kept the small alerts. Measured 2026-08-28 (pill-measure.mjs): the alert
   pills already stack one per line here and the longest label at 0.85rem is ~240px, so
   they come up to the lock pill's size exactly as the header's did — Chris's 09p choice,
   applied to the second surface that shows both families together. */
.ins-panel .alert-pill-row .alert-pill { font-size: 0.85rem; padding: 4px 14px; }

/* Selected row. Marked on the ROW, not the cells, so a preset with a different
   column set cannot lose the marker. */
.proj-table tbody tr.ins-sel > td { background: var(--cerio-blue-tint); }
.proj-table tbody tr.ins-sel > td:first-child { box-shadow: inset 3px 0 0 var(--cerio-blue); }

/* Below 1000px the panel and a table cannot share a line usefully, so the
   inspector stands down entirely and a row click opens the record as it always
   did. inspectorActive() reads the SAME number, so the CSS and the behaviour
   cannot disagree about where the cutover is. */
@media (max-width: 999px) {
  .ins-layout.has-panel { grid-template-columns: minmax(0, 1fr); }
  .ins-panel { display: none; }
}

.ins-switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 0.86rem; white-space: nowrap; }
.ins-switch input { accent-color: var(--cerio-blue); cursor: pointer; width: 16px; height: 16px; }
.ins-locked { font-size: 0.78rem; color: var(--text-tertiary); line-height: 1.4; }

/* ── 11a · F4 re-route — the freeable-units block inside Smart Allocate ──
   Present ONLY when units could actually be freed (Chris chose "hide it
   entirely" over a permanent empty-state line), so its appearance is itself
   the signal that an option exists. Deliberately quieter than the plan blocks
   above it: this is an offer, not part of the plan, and nothing here is
   ticked by default. */
.sa-free {
  margin: 4px 0 14px; padding: 12px 14px; border-radius: var(--radius);
  border: 0.5px solid var(--border-default); background: var(--surface-subtle);
}
.sa-free-head {
  font-size: 0.82rem; font-weight: 600; color: var(--text-primary); margin-bottom: 4px;
}
.sa-free-sub { margin: 0 0 10px; font-size: 0.82rem; color: var(--text-secondary); line-height: 1.5; }
.sa-free-row {
  display: flex; align-items: flex-start; gap: 9px; padding: 7px 0;
  border-top: 0.5px solid var(--border-default); cursor: pointer;
}
.sa-free-row:first-of-type { border-top: none; }
.sa-free-check { margin-top: 2px; flex-shrink: 0; }
.sa-free-txt { font-size: 0.82rem; color: var(--text-primary); line-height: 1.5; }
.sa-free-meta { color: var(--text-tertiary); margin-left: 8px; }
/* The cost of the move, stated on every row — the point Chris asked for is
   that you can see what a move takes away, not only what it gives. */
.sa-free-cost { display: block; font-size: 0.78rem; margin-top: 1px; }
.sa-free-cost.ok  { color: var(--text-tertiary); }
.sa-free-cost.bad { color: var(--amber); }

/* ── 11d · Scheduled View — the projects this view cannot show ──────────
   Projects with no delivery dates are excluded from both the Grid and the
   Timeline, and for months nothing said so; Chris reported users confused and
   frustrated by it. Styled as information, not an error: this is a normal state
   with a clear fix, and an amber warning band for "you have not filled this in
   yet" would read as something being broken. Hidden entirely at zero. */
.sched-hidden-note {
  margin: 0 0 10px; padding: 9px 13px; border-radius: var(--radius);
  border: 0.5px solid var(--border-default); background: var(--surface-subtle);
  font-size: 0.82rem; color: var(--text-secondary); line-height: 1.5;
}
.sched-hidden-note strong { color: var(--text-primary); font-weight: 500; }

/* ── 11e · "N available" with no availability dates ─────────────────────
   The meter and the allocate row were each telling the truth and together they
   read as a contradiction. Amber, not red: nothing is broken and no data is
   wrong — a step has not been taken yet, and the note says which. */
.alloc-nodates-note {
  margin: 8px 0 0; padding: 8px 12px; border-radius: var(--radius);
  border: 0.5px solid var(--amber); background: color-mix(in srgb, var(--amber) 10%, transparent);
  font-size: 0.8rem; color: var(--text-secondary); line-height: 1.5;
}
.alloc-nodates-note strong { color: var(--text-primary); font-weight: 500; }

/* ── 11g · inventory stage ──────────────────────────────────────────────
   The note under the Stage select carries two different things: a suggestion
   when an item is ready to be marked Consumed, and — while the feature is
   phased — a plain statement that filing an item has not yet moved any figure.
   Neutral surface rather than amber: neither is a warning, and an alarm colour
   on "you could file this now" would be noise on a screen people edit daily. */
.inv-stage-note {
  margin: 6px 0 0; padding: 8px 11px; border-radius: var(--radius);
  border: 0.5px solid var(--border-default); background: var(--surface-subtle);
  font-size: 0.8rem; color: var(--text-secondary); line-height: 1.5;
}
.inv-stage-note strong { color: var(--text-primary); font-weight: 500; }

/* ── 11s · the Create (+) popout ────────────────────────────────────────
   Same shape as .hz-fav and .hz-rv beside it: a relative wrapper so the menu
   anchors to the icon, and a min-width so three short labels do not produce a
   cramped 120px card. No new colours — the button is .hz-iconbtn, which is what
   makes it read as one family with the heart and the clock. */
.hz-create { position: relative; display: flex; flex-shrink: 0; }
.hz-create-menu { min-width: 232px; }

/* ══ 11y — THE ALLOCATION VISUALIZER ═══════════════════════════════════
   Phase 1: the picker and the read-only layout. Its own block at the end of the
   file rather than threaded through, because the whole feature lives in one
   module and this is the stylesheet half of that module.

   The visual language is the allocations chart's on purpose (Chris) — inventory
   left, projects right, a cubic ribbon between them coloured by item and
   thickened by units. What differs is the mechanics: the chart is one svg sized
   to its taller column and capped at 20 a side, and this is two SCROLLING
   columns of HTML nodes with an svg overlay for the ribbons. Nodes have to be
   scrollable, hoverable and (phase 2) draggable, which is what HTML is for. */
.av-intro { max-width: 980px; font-size: 0.86rem; color: var(--text-secondary); line-height: 1.6; margin: 0 0 18px; }
.av-intro p { margin: 0 0 9px; }
.av-intro b { color: var(--text-primary); font-weight: 500; }

/* 04zh — the Visualizer's working area is ONE card with the division tabs on its shoulder,
   the same shape the record page uses (.div-card.rec-div-panel, 17d): square the top-left
   corner and drop the top border so the strip and the panel touch. The stage keeps its own
   layout but loses its border and background — it IS the panel's interior now, and two
   nested borders read as a box in a box. */
.av-panel { background: var(--surface-card); border: 0.5px solid var(--border-default);
  border-radius: 0 8px 8px 8px; border-top: none; padding: 12px 14px 14px; }
.av-panel .av-stage { border: none; background: transparent; border-radius: 0; padding: 0; }
#allocviz-division-tabs { margin-bottom: 0; }
#av-lens-lock { margin: 0 0 6px; }
.av-picker { display: flex; gap: 18px; flex-wrap: wrap; margin-bottom: 10px; }
.av-picker-col { flex: 1 1 320px; min-width: 260px; }
/* 11z: Select all / Clear sit BESIDE their label, not pushed to the far right
   of the column (Chris). space-between put them a screen away from the word
   they act on, which on a wide monitor reads as belonging to the next column. */
.av-picker-head { display: flex; align-items: center; justify-content: flex-start; gap: 10px; margin-bottom: 5px; }
.av-picker-lbl { font-size: 0.72rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-tertiary); }
.av-picker-acts { display: flex; gap: 6px; }
.btn-ghost.av-mini { padding: 3px 10px; font-size: 0.74rem; }
.av-count { font-size: 0.78rem; color: var(--text-tertiary); margin: 2px 0 12px; }
.av-pend-note { color: var(--amber, #BE8A3C); }

/* The stage. Fixed height so the two columns scroll INSIDE it rather than the
   page growing to whatever "select all" produced — the point of scrolling
   columns is that the two sides stay side by side however many rows there are. */
.av-stage {
  position: relative;
  display: grid;
  grid-template-columns: minmax(220px, 1fr) minmax(120px, 220px) minmax(220px, 1fr);
  gap: 0;
  /* 11z: the height is SET IN JS from the space actually left below the header
     (Chris: "the taller the browser, the taller the visualizer"). The 720px cap
     that used to be here wasted half a tall monitor. This is the floor and the
     value before the first measurement; the JS never goes below it. */
  height: 420px;
  background: var(--surface-card);
  border: 0.5px solid var(--border-default);
  border-radius: var(--radius);
  padding: 14px;
}
.av-col { overflow-y: auto; overflow-x: hidden; padding: 2px 4px; }
.av-col-projects { grid-column: 3; }
.av-col-empty { font-size: 0.82rem; color: var(--text-tertiary); padding: 10px 4px; }
/* The ribbon layer spans the whole stage and never eats a click — in phase 2 the
   drag has to reach the nodes underneath it. */
.av-ribbons { position: absolute; inset: 0; pointer-events: none; }
.av-ribbon.is-pending { }   /* dash pattern is set per-path, with the units */

.av-node {
  display: flex; gap: 9px; align-items: flex-start;
  padding: 9px 11px; margin-bottom: 8px;
  background: var(--surface-subtle);
  border: 0.5px solid var(--border-strong);
  border-radius: 8px;
}
.av-node-main { min-width: 0; flex: 1 1 auto; }
.av-node-name { font-size: 0.85rem; font-weight: 500; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.av-node-cust { color: var(--text-secondary); font-weight: 400; }
.av-node-sub { font-size: 0.75rem; color: var(--text-tertiary); margin-top: 2px; }
.av-node-sub b { color: var(--text-secondary); font-weight: 500; }
.av-node-warn { font-size: 0.72rem; color: var(--amber, #BE8A3C); margin-top: 3px; cursor: help; }
/* The item's colour, the same one its ribbons carry. */
.av-swatch { width: 4px; align-self: stretch; border-radius: 2px; background: var(--av-c, var(--cerio-blue)); flex-shrink: 0; }

.av-node-pills { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
.av-node-pills .alert-pill { font-size: 0.66rem; padding: 1px 7px; }
/* The two alerts that cannot answer to allocating — past-due and qty mismatch —
   are shown quieter rather than hidden (Chris). Dimming is the honest treatment:
   they are true, they matter, and dragging units will not move them. */
.av-pill-static { opacity: 0.5; cursor: help; }

.av-empty {
  position: absolute; inset: 14px;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.86rem; color: var(--text-tertiary);
  background: var(--surface-card); border-radius: var(--radius);
}

@media (max-width: 900px) {
  /* Below this the two columns cannot sit side by side with a ribbon gutter
     between them, and a ribbon that has nowhere to go is worse than no ribbon:
     the columns stack and the layer is hidden. */
  .av-stage { grid-template-columns: 1fr; height: auto; }
  .av-col { max-height: 46vh; }
  .av-col-projects { grid-column: 1; }
  .av-ribbons { display: none; }
}

/* ── 11z: phase 2 — dragging, staging, the drop prompt ───────────────────
   The drag line and the staged ribbons both use the allocations chart's own
   marching-dash animation (@keyframes flow-dash), because Chris asked for a line
   "animating like the allocations chart" and the chart already has exactly one
   answer to what that means. */
.av-node-item { cursor: grab; user-select: none; }
.av-stage.is-dragging .av-node-item { cursor: grabbing; }
.av-stage.is-dragging { user-select: none; }
.av-node-proj.is-drop-target { border-color: var(--cerio-blue); box-shadow: 0 0 0 1px var(--cerio-blue) inset; }
.av-dragline { stroke-dasharray: 10 7; animation: flow-dash 1.1s linear infinite; opacity: 0.95; }
.av-dragdot { filter: drop-shadow(0 0 4px rgba(0,0,0,0.5)); }
.av-ribbon.is-staged { stroke-dasharray: 10 7; animation: flow-dash 1.1s linear infinite; }
.av-node-staged { font-size: 0.72rem; color: var(--cerio-blue); margin-top: 2px; font-weight: 500; }
.av-staged-in { color: var(--cerio-blue); }
.av-node-proj.has-staged { border-color: var(--cerio-blue); }
@media (prefers-reduced-motion: reduce) {
  .av-dragline, .av-ribbon.is-staged { animation: none; }
}

/* The drop prompt: a popover over the stage, not a full modal. You are mid
   gesture, and dimming the whole screen for two fields loses the context the
   drag was aimed at. */
.av-drop { position: fixed; inset: 0; z-index: var(--z-overlay); display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.42); }
.av-drop.hidden { display: none; }
.av-drop-card { width: min(460px, 92vw); background: var(--surface-card); border: 0.5px solid var(--border-strong); border-radius: var(--radius); box-shadow: 0 18px 48px rgba(0,0,0,0.45); }
.av-drop-head { display: flex; align-items: flex-start; gap: 10px; padding: 14px 16px 8px; }
.av-drop-title { flex: 1 1 auto; font-size: 0.92rem; font-weight: 500; color: var(--text-primary); line-height: 1.4; }
.av-drop-arrow { color: var(--text-tertiary); }
.av-drop-body { padding: 4px 16px 14px; }
.av-drop-caps { font-size: 0.76rem; color: var(--text-tertiary); margin-top: 5px; }
.av-drop-caps b { color: var(--text-secondary); }
.av-drop-err { font-size: 0.8rem; color: var(--red); margin-top: 8px; }
.av-drop-err.hidden { display: none; }
.av-drop-foot { display: flex; justify-content: flex-end; gap: 10px; padding: 0 16px 14px; }

/* The staged list sits BELOW the stage: it must never cover a node you are
   dragging to, and it is the one place that says nothing is saved yet. */
.av-staged-panel { margin: 0 0 14px; border: 0.5px solid var(--cerio-blue); border-radius: var(--radius); background: var(--surface-card); }
.av-staged-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; padding: 11px 14px; font-size: 0.82rem; color: var(--text-secondary); }
.av-staged-head b { color: var(--text-primary); }
.av-staged-acts { display: flex; gap: 8px; }
.av-staged-lite { padding: 0 14px 10px; font-size: 0.78rem; color: var(--amber, #BE8A3C); }
.av-staged-rows { border-top: 0.5px solid var(--border-default); }
.av-staged-row { display: flex; align-items: center; gap: 9px; padding: 7px 14px; font-size: 0.8rem; color: var(--text-secondary); border-bottom: 0.5px solid var(--border-default); }
.av-staged-row:last-child { border-bottom: none; }
.av-staged-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.av-staged-txt { flex: 1 1 auto; min-width: 0; }
.av-staged-txt b { color: var(--text-primary); }
.av-staged-arrow, .av-staged-date { color: var(--text-tertiary); }
/* 11zg — was a bare ✕ at 0.9rem with 4px of padding: a 12px target that read as
   "dismiss". Now the same trash can that appears on the stage, on a 26px square
   hit area, so the two ways to throw a row away look like one act. */
.av-staged-rm {
  background: none; border: 0; color: var(--text-tertiary); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 6px; padding: 0; flex-shrink: 0;
  transition: color 0.15s, background 0.15s;
}
.av-staged-rm:hover { color: var(--red); background: var(--surface-hover, rgba(163,45,45,0.08)); }
.av-staged-rm:focus-visible { outline: 2px solid var(--cerio-blue); outline-offset: 1px; }

/* ── 11zg: THE TRASH. Present in the DOM always, laid out only while a LINE is
   in flight (.is-live), so it costs nothing on a screen whose job is showing
   lines. It sits at the foot of the stage, centred over the ribbon gutter —
   where the gesture already ends and where it overlaps neither column's nodes.
   pointer-events are ON: unlike the drag tooltip, this one IS the drop target. */
.av-trash {
  /* FIXED, not absolute inside the stage. Absolute put it at the stage's own
     foot, which on a tall stage or a scrolled page sits below the fold —
     elementFromPoint returned null there and the drop silently did nothing.
     A drop target you cannot reach is the same failure as no target at all.
     Fixed keeps it in view for the whole gesture however the page is scrolled.
     Same layer as the drag tooltip, which is the only other thing on screen
     during a drag; no modal can be open while one is in flight. */
  position: fixed; left: 50%; bottom: 26px; transform: translate(-50%, 14px);
  z-index: calc(var(--z-page-chrome) + 5); display: none; align-items: center; gap: 9px;
  padding: 9px 16px; border-radius: 10px;
  background: var(--surface-card); border: 1px dashed var(--border-default);
  color: var(--text-secondary); font-size: 0.8rem; line-height: 1.3;
  max-width: min(520px, 88%); box-shadow: var(--shadow-md, 0 6px 18px rgba(0,0,0,0.18));
  opacity: 0; transition: opacity 0.14s, transform 0.14s, border-color 0.14s, color 0.14s;
}
.av-trash.is-live { display: flex; opacity: 1; transform: translate(-50%, 0); }
.av-trash svg { flex-shrink: 0; }
.av-trash-t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Armed. Solid, red, and slightly larger — the same --red every destructive
   thing on this site uses, so the colour is not a new vocabulary. */
.av-trash.is-over {
  border-style: solid; border-color: var(--red); color: var(--red);
  transform: translate(-50%, 0) scale(1.04);
}
/* A target you may not use says so instead of accepting a drop that would be
   refused a moment later (11b). It also stops being a drop target in JS. */
.av-trash.is-refused { border-style: solid; opacity: 0.75; color: var(--text-tertiary); cursor: not-allowed; }

/* A staged line is grabbable, and says so. Saved lines already do (.av-ribbon
   [data-av-link]); this is the same rule for the other kind. */
.av-ribbon[data-av-staged-link] { pointer-events: stroke; cursor: grab; }
/* (the "no ribbon takes pointer events mid-drag" rule is stated once, with the
   11zd ribbon rules further down — not repeated here.) */
.av-save-report { padding: 10px 14px; border-top: 0.5px solid var(--border-default); font-size: 0.8rem; color: var(--red); line-height: 1.6; }
.av-save-report.hidden { display: none; }

/* 11za — the drop prompt's STOP state. It was a grey line under the units box
   with a disabled button beside it that looked identical to an enabled one, so
   the honest report was "I click it and nothing happens" (Chris). A refusal is
   not a hint. Amber rather than red: nothing is wrong, this route is simply
   closed, and the message says which other route is open. */
.av-blocked {
  margin-top: 8px; padding: 10px 12px;
  border-radius: var(--radius);
  border: 0.5px solid var(--sev-med-border, #BE8A3C);
  background: var(--sev-med-bg, rgba(190,138,60,0.14));
  color: var(--text-secondary); font-size: 0.8rem; line-height: 1.5;
}
.av-blocked-h { color: var(--sev-med-text, #E8B583); font-weight: 500; font-size: 0.86rem; margin-bottom: 3px; }
/* A disabled control must LOOK disabled. This is the whole bug. */
.av-drop-card .btn-primary:disabled,
.av-drop-card .form-input:disabled { opacity: 0.45; cursor: not-allowed; }

/* 11za — the specs you match on, on both node types. */
.av-chips { display: flex; flex-wrap: wrap; gap: 5px; margin: 4px 0 2px; }
.av-chip {
  font-size: 0.68rem; padding: 1px 7px; border-radius: 99px;
  background: var(--surface-card); border: 0.5px solid var(--border-default);
  color: var(--text-tertiary); white-space: nowrap;
}
.av-chip b { color: var(--text-secondary); font-weight: 500; }

/* ── 11zh: the specification-mismatch warning. ONE look for it, on every screen
   that can allocate — the project modal, both Smart Allocates, the partner
   request button and the visualizer — because Chris's rule 14 is that the
   site's alerting methodology is one methodology. --sev-med is the token the
   visualizer's blocked panel already uses: this is the same severity, something
   to look at rather than something forbidden. */
.alloc-fit-warn {
  margin: 0 0 12px; padding: 10px 12px; border-radius: 8px;
  background: var(--sev-med-bg, rgba(190,138,60,0.10));
  border: 0.5px solid var(--sev-med-border, rgba(190,138,60,0.45));
  color: var(--text-secondary); font-size: 0.84rem; line-height: 1.5;
}
.alloc-fit-h {
  display: flex; align-items: center; gap: 7px;
  color: var(--sev-med-text, #BE8A3C); font-weight: 600; font-size: 0.86rem;
}
.alloc-fit-h svg { flex-shrink: 0; }
.alloc-fit-list { margin: 6px 0 0; padding-left: 20px; }
.alloc-fit-list li { margin: 2px 0; }
.alloc-fit-foot { margin-top: 7px; font-size: 0.78rem; color: var(--text-tertiary); }
.alloc-fit-ask { margin-top: 10px; }

/* The visualizer's own compact form of the same warning. Narrower, because it
   sits inside the drop card rather than in a dialog. */
.av-fit {
  margin: 0 0 10px; padding: 8px 10px; border-radius: 7px;
  background: var(--sev-med-bg, rgba(190,138,60,0.10));
  border: 0.5px solid var(--sev-med-border, rgba(190,138,60,0.45));
  font-size: 0.78rem; line-height: 1.45; color: var(--text-secondary);
}
.av-fit-h {
  display: flex; align-items: center; gap: 6px;
  color: var(--sev-med-text, #BE8A3C); font-weight: 600;
}
.av-fit-h svg { flex-shrink: 0; }
.av-fit-list { margin: 5px 0 0; padding-left: 18px; }
.av-fit-list li { margin: 2px 0; }
.av-fit.is-modal { margin-bottom: 0; }
.av-fit-ask { margin-top: 10px; }

/* A chip the selected inventory cannot meet. Marked, not hidden: Chris's whole
   point is that you should be told, not quietly prevented. */
.av-chip.is-misfit {
  border-color: var(--sev-med-border, rgba(190,138,60,0.45));
  background: var(--sev-med-bg, rgba(190,138,60,0.10));
  color: var(--sev-med-text, #BE8A3C); cursor: help;
}
.av-chip.is-misfit b { color: var(--sev-med-text, #BE8A3C); }

/* ══ 12i — THE SAME MARK, OUTSIDE THE VISUALIZER ══════════════════════════
   Smart Allocate's candidate rows can now list a project this item does not
   match, so they need the mark the visualizer's chips already use. Same three
   --sev-med tokens and the same cursor:help, because the tooltip carries the
   REASON — a reader who sees amber twice on two screens must not have to learn
   it twice.
   NOT .av-chip.is-misfit reused: `av-` is the allocation-visualizer namespace
   and this sits in a modal that is not it. One extra class beats a class whose
   name lies about where it lives. */
.alloc-misfit {
  display: inline-block; margin-left: 7px; padding: 1px 7px; border-radius: 99px;
  font-size: 0.68rem; font-weight: 500; white-space: nowrap; cursor: help;
  border: 0.5px solid var(--sev-med-border, rgba(190,138,60,0.45));
  background: var(--sev-med-bg, rgba(190,138,60,0.10));
  color: var(--sev-med-text, #BE8A3C);
}

/* A staged row that would put units where they do not fit. */
.av-staged-misfit {
  margin-left: 7px; padding: 1px 7px; border-radius: 99px; cursor: help;
  font-size: 0.68rem; font-weight: 600; white-space: nowrap;
  background: var(--sev-med-bg, rgba(190,138,60,0.10));
  border: 0.5px solid var(--sev-med-border, rgba(190,138,60,0.45));
  color: var(--sev-med-text, #BE8A3C);
}

/* ── 2026-08-12a: the shared sort control. Sits in .fbar-trail beside the
   filter button, using .filter-select — the same element Activity and Alerts
   already use for their sorts — so this reads as the site's existing idiom
   rather than a new one. The direction button is deliberately the same height
   as the select so the pair reads as one control. */
.fbar-sorts { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.fbar-sortlbl {
  font-size: 0.78rem; color: var(--text-tertiary); font-weight: 500;
  white-space: nowrap; margin-right: 1px;
}
/* 12c: the second pair needs air in front of it, or "Inventory sort [x] [v]
   Project sort [y] [v]" reads as one run-on control instead of two. */
.fbar-sortlbl-2 { margin-left: 10px; }
.fbar-sortdir {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex-shrink: 0; padding: 0;
  border: 0.5px solid var(--border-default); border-radius: 6px;
  background: var(--surface-card); color: var(--text-secondary);
  font-size: 0.9rem; line-height: 1; cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.fbar-sortdir:hover { color: var(--text-primary); border-color: var(--border-strong); }
.fbar-sortdir:focus-visible { outline: 2px solid var(--cerio-blue); outline-offset: 1px; }
/* Ascending is the non-default direction, so it is marked rather than merely
   pointing the other way — the arrow alone is easy to misread at 12px. */
.fbar-sortdir.is-asc { color: var(--cerio-blue); border-color: var(--cerio-blue); }

/* 11zf — the project's delivery date on its node (Chris). One line, because the
   node already carries a name, a spec row, a count and up to four pills; the
   full schedule is the title attribute. Past due borrows --red, the same token
   every other past-due delivery on the site is drawn in, rather than a colour
   picked for this screen. */
.av-node-deliv {
  font-size: 0.72rem; color: var(--text-tertiary); margin-top: 3px; cursor: help;
}
.av-node-deliv b { color: var(--text-secondary); font-weight: 500; }
.av-node-deliv .av-deliv-k { color: var(--text-tertiary); }
.av-node-deliv .av-deliv-u,
.av-node-deliv .av-deliv-more { color: var(--text-tertiary); }
.av-node-deliv.is-late { color: var(--red); }
.av-node-deliv.is-late b,
.av-node-deliv.is-late .av-deliv-k { color: var(--red); font-weight: 600; }
.av-node-deliv.is-none { color: var(--text-tertiary); font-style: italic; cursor: default; }
.av-node-deliv.is-done { color: var(--green); }

/* 11zb — the Undo button, and the slim panel that carries it when there is
   nothing staged. Discarding everything used to take the panel away and the
   Undo button with it, so the action you would most want back was the one that
   removed the way back. */
.av-staged-panel.is-empty { border-color: var(--border-default); }
.av-staged-panel.is-empty .av-staged-head { color: var(--text-tertiary); }
#av-undo { }

/* 11zc — the window that follows the pointer during a drag. Body-level and
   fixed, so it is never clipped by the stage's own overflow; pointer-events off,
   so it can never become the thing elementFromPoint finds instead of the project
   you are aiming at. Above the page chrome, below every modal. */
.av-dragtip {
  position: fixed; z-index: calc(var(--z-page-chrome) + 5);
  min-width: 190px; max-width: 280px;
  padding: 9px 11px;
  background: var(--surface-card);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 10px 26px rgba(0,0,0,0.45);
  font-size: 0.78rem; color: var(--text-secondary);
  pointer-events: none;
  opacity: 0; transition: opacity .12s ease;
}
.av-dragtip.is-on { opacity: 1; }
.av-dragtip-h { font-size: 0.8rem; font-weight: 500; color: var(--text-primary); margin-bottom: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.av-dragtip-row { display: flex; justify-content: space-between; gap: 12px; line-height: 1.6; }
.av-dragtip-row b { color: var(--text-primary); font-weight: 500; white-space: nowrap; }
.av-dragtip-warn { color: var(--amber, #BE8A3C); }
.av-dragtip-to { margin-top: 5px; padding-top: 5px; border-top: 0.5px solid var(--border-default); color: var(--cerio-blue); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The node menu is the chart's own .flow-menu — one component, styled once. */
.av-node { cursor: pointer; }

/* 11zd — phase 3. A ribbon has to be clickable to be released or moved, but the
   LAYER must stay transparent to the pointer or it would swallow every drag.
   pointer-events:stroke on the path alone does exactly that: the line is
   clickable, the space around it is not. Off entirely while a drag is in
   flight, so a ribbon can never become the thing elementFromPoint finds instead
   of the project you are aiming at. */
.av-ribbon[data-av-link] { pointer-events: stroke; cursor: pointer; }
.av-stage.is-dragging .av-ribbon { pointer-events: none; }
.av-ribbon[data-av-link]:hover { opacity: 0.85 !important; }
/* Staged to leave: drawn as what it will become, not as what it is. */
.av-ribbon.is-leaving { opacity: 0.12 !important; stroke-dasharray: 3 6; }
.av-staged-row.is-release .av-staged-dot { background: var(--red) !important; }
.av-staged-row.is-move .av-staged-dot { background: var(--cerio-blue) !important; }

/* 11ze — the two fill buttons in the drop prompt. Full width and stacked so the
   longer label is never truncated: "Smart fill — match delivery dates (45-day
   buffer)" is the whole explanation of what the button does, and a button that
   has to be hovered to be understood is a button that gets ignored. */
.av-fill-row { display: flex; flex-direction: column; gap: 6px; margin-top: 9px; }
.av-fill-row .btn-ghost { width: 100%; text-align: center; }

/* 11zf — what the source project holds now, above the units field: "how many"
   should be a decision, not a guess. */
.av-move-note { font-size: 0.76rem; color: var(--text-tertiary); margin-top: 5px; line-height: 1.5; }
.av-move-note b { color: var(--text-secondary); font-weight: 500; }
/* A ribbon is grabbable, like an item node. */
.av-ribbon[data-av-link] { cursor: grab; }
.av-stage.is-dragging .av-ribbon[data-av-link] { cursor: grabbing; }

/* ── Divisions on the project record — step 1 (2026-08-16) ─────────────────
   Chips under the title, an add-division menu for full editors, and the shell
   of a section per non-PowerGen division. Partners never receive this markup
   at all (the builders return '' for externals), so no hiding rule is needed
   here — absence is the rule. */
.proj-div-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:10px 0 14px;padding:10px 13px;background:var(--surface-card);border:1px solid var(--border-default);border-radius:8px}
.proj-div-cap{font-size:0.7rem;font-weight:700;letter-spacing:0.07em;text-transform:uppercase;color:var(--text-tertiary)}
.proj-div-chip{display:inline-flex;align-items:center;gap:6px;font-size:0.82rem;font-weight:600;padding:4px 11px;border-radius:999px;background:var(--cerio-blue-tint);border:1px solid #C9D4F4;color:var(--text-primary)}
/* 2026-08-16h (Chris): division chips moved from their own banner to the end of the
   title pill row — pill-scale there so they sit level with the alert pills; the edit
   form's banner row keeps the full-size chip above. */
/* 2026-08-16l (Chris): ONE chip row under the project name — shared chips, then the
   viewer's own division's name chip as a label, then its chip-flagged fields. */
.rec-chip-div{background:var(--cerio-blue-tint);border-color:#C9D4F4;color:var(--text-primary);font-weight:700}

/* ── Division cards on the record (16l, from the approved mock + Chris's deltas):
   name · Division tag · Build proposal › · ✎ Edit · fold arrow. Primary opens
   expanded; the others start folded. ───────────────────────────────────────── */
/* 16n (Chris): an explicit box like the Project Total's — the inherited card chrome
   was not reading as one on the record page. The Division tag is gone. */
.div-card{padding:0;overflow:hidden;background:var(--surface-card);border:1px solid var(--border-default);border-radius:8px}
.div-card-head{display:flex;align-items:center;gap:9px;padding:11px 13px;border-bottom:1px solid var(--border-default);background:var(--surface-card)}
.div-card-name{font-size:0.9rem;font-weight:700}
.div-card-btns{margin-left:auto;display:inline-flex;gap:6px}
.div-card-btn{font-size:0.8rem;padding:5px 11px}
.div-card-alerts{display:inline-flex;gap:5px;align-items:center;flex-wrap:wrap}
/* 17b (Chris: the tab version) — the record's division tabs, same folder treatment as
   the registry's; the panel connects beneath. Each tab wears its division's color dot
   and its alert count, so a division off stage stays honest. */
/* 21d (Chris: "We still have a misalignment issue with the tabs and the border around the machine
   record. This has been an ongoing problem that hasn't quite been resolved in all the times we have
   addressed it.") — MEASURED on the deployed build rather than nudged again. The strip and the panel
   both start at the same x (50 on a 1600px window); the TABS started at 52, because .df-tabs carries
   padding: 0 2px. That 2px is the long-standing half of this, and it is why every previous attempt
   that adjusted the PANEL never quite landed — the panel was never wrong. The record strip drops the
   side padding so a tab's left edge is the panel's left edge, and takes the room a stack needs above
   it as padding-top on the STRIP, so every tab keeps one baseline. */
/* 21e: .df-tabs{padding:0 2px} is declared LATER in this file with the SAME specificity, so the single
   class above lost the cascade and the 2px survived — measured on the deployed 21d, tabs still at 52 to
   the panel s 50. Two classes (0,2,0) settle it regardless of order. This is the file s own recurring
   lesson: .div-card.rec-div-panel carries the same note for the same reason. */
.df-tabs.rec-div-tabs{margin-top:4px;padding-left:0;padding-right:0;padding-top:6px}
.rec-divtab-dot{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:7px;vertical-align:0}
.rec-divtab-alerts{display:inline-block;min-width:16px;text-align:center;background:var(--sev-high-bg);color:var(--sev-high-text);border-radius:99px;font-size:0.66rem;font-weight:700;padding:1px 5px;margin-left:7px}
/* Two classes, deliberately: .proj-div-section{margin-top:12px} sits LATER in this
   file and out-orders a single-class zero — this beat the 17d fix and left the gap
   Chris screenshotted twice. Specificity (0,2,0) settles it regardless of order. */
.div-card.rec-div-panel{border-radius:0 8px 8px 8px;margin-top:0;padding-top:0;border-top:none}
/* 17d (Chris, from the screenshot): the division NAME lives in the tab — bigger type
   there, no repeat in the panel head, and tab and panel touch (the gap was
   .files-section's own margin/border, killed above). */
.rec-div-tabs .df-tab{font-size:0.92rem;padding:8px 16px 7px;text-align:left}
/* 21f (Chris: "mechanical is highlighted, but it's not overly easy to see. Is there a way we can
   make the selected tabs stand out more?") — on dark the selected tab differed from its neighbours only by
   a slightly lighter ground and blue text, and --surface-card sits very close to --surface-subtle there.
   A 3px cap in Cerio blue across the top of the tab is the folder-tab idiom and reads at a glance in both
   themes; the ground lifts to the panel colour and the side borders strengthen so the selected tab reads
   as one piece with the panel it opens. The 1px overlap onto the panel border stays — that is the join. */
.rec-div-tabs .df-tab.on, .rec-div-tabs .rec-divtab-stack[data-rec-stack-on]{
  font-weight:700;
  box-shadow:inset 0 3px 0 0 var(--cerio-blue);
  border-color:var(--border-strong);
  border-bottom-color:transparent;
}
/* 17q (Chris, variant 1): each tab wears its division's STATUS on a second line under
   the name — colour-coded so the whole project reads at a glance. */
.rec-divtab-stline{display:block;margin-top:3px}
.rec-divtab-st{display:inline-block;font-size:0.6rem;font-weight:700;letter-spacing:0.05em;text-transform:uppercase;padding:1px 8px;border-radius:99px;background:var(--surface-subtle);color:var(--text-tertiary)}
.rec-divtab-st.st-active{background:var(--sev-low-bg);color:var(--sev-low-text)}
.rec-divtab-st.st-execution{background:var(--cerio-blue-tint);color:var(--cerio-blue)}
.rec-divtab-st.st-lost,.rec-divtab-st.st-expired{background:var(--sev-high-bg);color:var(--sev-high-text)}
.rec-divtab-st.st-delivered{background:var(--surface-subtle);color:var(--text-secondary)}
/* 19f (Chris): over five machine tabs, a division with two or more machines folds into ONE stack
   tab — js/rec-tab-stacks.js. The group holding the selected tab is open; a click on a stack slides
   it open (max-width) while the open group shuts. Members of a closed group are invisible and
   unfocusable; overflow is clipped only while sliding, so the .on tab's 1px overhang onto the panel
   is never cut at rest. Two offset outlines behind the stack tab draw the "stack of cards". */
/* 21d: the gap belongs to the MEMBERS, not the group — a collapsed group kept spending 6px between
   its stack and its zero-width member list, so the space after a stack was 12px where every other gap
   was 6px. Uniform now, open or closed. */
.rec-divtab-group{display:inline-flex;align-items:stretch;gap:0}
/* 21o (Chris, with a screenshot marking each gap: "the space between the first and second tab is too
   small. the space between the second and third tab is appropriate. make them all spaced the same as
   this one") — MEASURED FIRST, on the deployed strip: every gap in the DOM is EXACTLY 6px, the strip's
   own flex gap. Nothing about the layout is uneven, so nothing in the layout was the bug.
   What differs is INK. A stack draws its pile with ::before/::after at right:-3px and right:-6px, and
   an absolutely positioned pseudo-element costs no layout — so after a STACK the pile fills the whole
   6px and the two tabs read as welded together, while after a PLAIN tab the full 6px shows. That is
   exactly the pair Chris named: PowerGen is a stack of five (too small), UPS is a plain tab (right).
   The group now RESERVES the room its pile occupies, so the visible gap is 6px after every tab alike.
   Closed only: an open group hides the stack, and the pile with it, and its members carry their own
   margin — adding this there would open a second gap that is not wanted. */
.rec-div-tabs .rec-divtab-group:not(.open){margin-right:6px}
.rec-divtab-group .df-tab{white-space:nowrap;flex:0 0 auto}
.rec-divtab-members{display:inline-flex;gap:6px;max-width:0;opacity:0;transition:max-width .3s ease,opacity .2s ease}
.rec-divtab-group.open .rec-divtab-members{max-width:2000px;opacity:1;margin-left:6px}
.rec-divtab-group.is-sliding .rec-divtab-members{overflow:hidden}
.rec-divtab-group:not(.open) .rec-divtab-members .df-tab{visibility:hidden}
/* 21d: no margins — 19f indented and lowered every stack, which is what made the row ragged the
   moment more than one division stacked. The offset outlines behind it are drawn by ::before/::after
   and need no space of their own now the strip reserves it. */
.rec-div-tabs .rec-divtab-stack{position:relative;max-width:400px;transition:max-width .3s ease,opacity .2s ease,padding .3s ease,margin .3s ease}
.rec-divtab-stack::before,.rec-divtab-stack::after{content:"";position:absolute;left:3px;right:-3px;top:-3px;bottom:0;z-index:-1;border:1px solid var(--border-default);border-bottom:none;border-radius:6px 6px 0 0;background:var(--surface-subtle)}
.rec-divtab-stack::after{left:6px;right:-6px;top:-6px;z-index:-2}
.rec-div-tabs .rec-divtab-group.open .rec-divtab-stack{max-width:0;padding-left:0;padding-right:0;margin:0;border-width:0;opacity:0;overflow:hidden}
/* 21a: the stack holding the machine on stage is the CURRENT tab — same treatment .df-tab.on gets,
   because it is standing in for that machine while the group is closed. */
.rec-div-tabs .rec-divtab-stack[data-rec-stack-on]{background:var(--surface-card);color:var(--cerio-blue);font-weight:700;position:relative;top:1px}
.rec-divtab-count{display:inline-block;min-width:16px;text-align:center;background:var(--cerio-blue-tint);color:var(--cerio-blue);border-radius:99px;font-size:0.66rem;font-weight:700;padding:1px 6px;margin-left:7px}
.rec-divtab-stline .rec-divtab-st+.rec-divtab-st{margin-left:4px}
.df-tabs.rec-div-tabs{position:relative;z-index:0}
@media (prefers-reduced-motion:reduce){.rec-divtab-members,.rec-div-tabs .rec-divtab-stack{transition:none}}
/* 16y (Chris): the card's Edit is BLUE — the primary action of the card. */
.div-card-btn[data-div-editbtn]{background:var(--cerio-blue);border-color:var(--cerio-blue);color:#fff;font-weight:600}
.div-card-btn[data-div-editbtn]:hover{opacity:0.9;color:#fff}
.div-card-arr{min-width:32px;padding:5px 9px}
.div-card-body{padding:13px}
.div-card-body.closed{display:none}
/* 16q: the allocation block inside the PowerGen card breathes below the fields. */
.div-card-body .rec-pair{margin-top:12px}
.div-card-body .rec-kpis{margin-top:12px}
/* 16p (Chris): the in-card editor flows multi-column like the read grid — horizontal
   space is there to be used. proj-div-form had no rule at all, hence the stack. */
.proj-div-form{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:10px 22px}
/* 16o — the in-card editor: what the viewer cannot edit is greyed, not hidden. */
.div-card-body input:disabled,.div-card-body select:disabled,.div-card-body textarea:disabled{opacity:0.55;cursor:not-allowed;background:var(--surface-subtle)}

/* ── Division pricing (16m): the strip at the foot of a card, and the project total.
   Absence is the gate — a strip the viewer may not see is simply not rendered. ── */
.price-strip{margin-top:12px;padding-top:10px;border-top:1px dashed var(--border-default);display:flex;gap:26px;flex-wrap:wrap;align-items:baseline}
.price-cap{font-size:0.7rem;font-weight:700;letter-spacing:0.07em;text-transform:uppercase;color:var(--text-tertiary);width:100%;margin-bottom:-4px}
.total-row{margin-top:12px;padding:12px 14px;border-radius:8px;background:var(--cerio-blue-tint);border:1px solid #C9D4F4;display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
.total-cap{font-size:0.72rem;font-weight:700;letter-spacing:0.07em;text-transform:uppercase;color:var(--cerio-blue)}
.total-amt{font-size:1.15rem;font-weight:700;font-variant-numeric:tabular-nums;color:var(--cerio-blue)}
.total-why{font-size:0.78rem;color:var(--text-tertiary)}
/* 2026-08-16i (Chris, from the divisions mock item 3): Division Fields folder tabs.
   The selected tab sits 1px into the panel so the two read as one surface. */
.df-tabs{display:flex;gap:6px;flex-wrap:wrap;padding:0 2px}
.df-tab{font:inherit;font-size:0.8rem;font-weight:600;cursor:pointer;padding:6px 13px;border-radius:6px 6px 0 0;border:1px solid var(--border-default);border-bottom:none;background:var(--surface-subtle);color:var(--text-secondary)}
.df-tab:hover{color:var(--text-primary)}
.df-tab.on{background:var(--surface-card);color:var(--cerio-blue);font-weight:700;position:relative;top:1px}
.df-panel{background:var(--surface-card);border:1px solid var(--border-default);border-radius:0 8px 8px 8px;padding:13px}
/* 24m: the per-tab storage line that replaces a column of identical
   "record field" chips. Quiet by design — it is context, not a warning. */
.df-storage-note{font-size:0.78rem;color:var(--text-secondary);background:var(--surface-subtle);border:0.5px solid var(--border-default);border-radius:6px;padding:6px 10px;margin:0 0 10px}
.df-storage-note .badge{vertical-align:baseline}
.proj-div-x{border:none;background:none;color:var(--text-faint);cursor:pointer;font-size:0.75rem;padding:0 0 0 2px;line-height:1}
.proj-div-x:hover{color:var(--red)}
.proj-div-addwrap{position:relative}
.proj-div-add{font:inherit;font-size:0.8rem;font-weight:600;cursor:pointer;color:var(--cerio-blue);background:none;border:1px dashed var(--border-strong);border-radius:999px;padding:4px 11px}
.proj-div-add:focus-visible{outline:2px solid var(--cerio-blue);outline-offset:2px}
.proj-div-menu{position:absolute;top:calc(100% + 4px);left:0;z-index:var(--z-dropdown-inline);background:var(--surface-card);border:1px solid var(--border-strong);border-radius:8px;box-shadow:0 8px 22px rgba(0,0,0,0.12);display:flex;flex-direction:column;min-width:150px;overflow:hidden}
.proj-div-menu.hidden{display:none}
.proj-div-menu button{font:inherit;font-size:0.84rem;text-align:left;padding:8px 12px;border:none;background:none;cursor:pointer;color:var(--text-primary)}
.proj-div-menu button:hover{background:var(--surface-subtle)}
.proj-div-section{margin-top:12px}

/* ══ 19f — the report builder's searchable field picker ══ */
.rb-menu-item { display: block; width: 100%; text-align: left; padding: 6px 10px;
  border: none; background: none; cursor: pointer; font-size: 0.84rem;
  color: var(--text-primary); border-radius: 6px; font-family: inherit; }
.rb-menu-item:hover { background: var(--surface-subtle); }
/* Phase 3 step 3 — each picker row: the field's one name, then its muted "whose" line (drawn from data-whose,
   so the button's own text stays the name); a field already in the report is listed disabled (F6) */
.rb-menu-item[data-whose]::after { content: attr(data-whose); display: block; margin-top: 1px;
  font-size: 0.7rem; line-height: 1.3; color: var(--text-tertiary); }
.rb-menu-item.is-in { cursor: default; color: var(--text-tertiary); }
.rb-menu-item.is-in:hover { background: none; }

/* ══ 19h — the builder's Salesforce outline rail + drag-to-reorder ══ */
.rb-rail-title { font-size: 0.7rem; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 6px; }
.rb-col-item { display: flex; align-items: center; gap: 7px; padding: 5px 9px;
  margin-bottom: 4px; border: 0.5px solid var(--border-default); border-radius: 7px;
  background: var(--surface-card); font-size: 0.82rem; cursor: grab; }
.rb-col-item:active { cursor: grabbing; }
.rb-col-item.rb-dragging { opacity: 0.45; }
/* Phase 3 step 2 — "Report covers" (the scope select beside "Each row is"), its help line, a column the
   scope does not cover (kept, prints nothing), and the note / "Needs a decision" banner above the table */
.rb-scope-label { font-size: 0.8rem; color: var(--text-secondary); white-space: nowrap; }
.rb-scope-help { font-size: 0.75rem; color: var(--text-tertiary); }
/* Phase 3 step 3 — "Each row is" and "Report covers" each carry their one live line UNDER the select */
.rb-ctl-stack { display: inline-flex; flex-direction: column; gap: 2px; min-width: 0; max-width: 280px; }
.rb-ctl-stack .rb-scope-help { line-height: 1.3; }
.rb-col-kept { color: var(--text-tertiary); font-size: 0.72rem; }
.rb-col-item.rb-col-off { border-color: var(--red); color: var(--red); }
.rb-scope-note { margin-top: 10px; padding: 7px 10px; border: 1px solid var(--border-default); border-radius: 8px;
  font-size: 0.8rem; color: var(--text-secondary); background: var(--surface-subtle); }
.rb-scope-note.is-warn { border-color: var(--sev-med-border); background: var(--sev-med-bg); color: var(--sev-med-text); }
.rb-col-grip { color: var(--text-tertiary); font-size: 0.78rem; }

/* ══ 19i — visual separation, gridlines, resizable preview columns ══ */
/* The rail reads as its own pane; the preview carries its own shade so the
   builder stops being one flat color (Chris). */
.rb-rail { background: var(--surface-subtle); border: 0.5px solid var(--border-default);
  border-radius: 10px; padding: 12px; }
.rb-preview { flex: 1 1 auto; min-height: 420px; max-height: 78vh; overflow: auto;
  border: 0.5px solid var(--border-default); border-radius: 8px;
  background: var(--surface-subtle); }
.rb-preview-table { width: 100%; table-layout: fixed; border-collapse: collapse;
  font-size: 0.82rem; }
.rb-preview-table th, .rb-preview-table td { border: 0.5px solid var(--border-default);
  padding: 5px 9px; text-align: left; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.rb-preview-table thead th { position: sticky; top: 0; z-index: 2;
  background: var(--surface-card); font-weight: 600; }
.rb-preview-table tr.rb-row-group td { background: var(--surface-card); font-weight: 600; }
.rb-preview-table tr.rb-row-total td { font-weight: 600; }
/* the sticky header is the handle's positioning context (sticky creates one) */
.rb-th-resize { position: absolute; top: 0; right: 0; width: 8px; height: 100%;
  cursor: col-resize; user-select: none; z-index: 3; }
.rb-th-resize:hover { background: var(--border-default); }

/* ══ 19j — the AI-proposed report charts (rendered by the app) ══ */
.rb-chart-card { background: var(--surface-card); border: 0.5px solid var(--border-default);
  border-radius: 10px; padding: 12px; min-width: 0; }

/* ══ 19l — the AI document (report brief) modal + page ══ */
.rb-doc-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.45);
  z-index: 1000; display: flex; align-items: center; justify-content: center; padding: 18px; }
.rb-doc-modal { background: var(--surface-card); border-radius: 12px; max-width: 860px;
  width: 100%; max-height: 92vh; overflow: auto; padding: 20px 24px 24px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.3); }
.rb-doc-page { border: 0.5px solid var(--border-default); border-radius: 10px;
  padding: 26px 30px; background: var(--surface-card); }
.rb-doc-title { font-size: 1.4rem; font-weight: 700; }
.rb-doc-sub { color: var(--text-secondary); font-size: 0.9rem; margin: 3px 0 0;
  padding-bottom: 12px; border-bottom: 0.5px solid var(--border-default); }
.rb-doc-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px; margin: 16px 0 6px; }
.rb-doc-kpi { background: var(--surface-subtle); border: 0.5px solid var(--border-default);
  border-radius: 9px; padding: 12px 14px; }
.rb-doc-kpi-v { font-size: 1.35rem; font-weight: 700; }
.rb-doc-kpi-l { font-size: 0.78rem; color: var(--text-secondary); margin-top: 2px; }
.rb-doc-kpi-s { font-size: 0.66rem; color: var(--text-tertiary); margin-top: 2px; }
.rb-doc-chart { margin-top: 14px; border: 0.5px solid var(--border-default);
  border-radius: 9px; padding: 8px; }
.rb-doc-h { font-size: 0.95rem; font-weight: 700; }
.rb-doc-p { font-size: 0.86rem; color: var(--text-secondary); margin: 6px 0 0; }
.rb-doc-foot { margin-top: 18px; padding-top: 10px; border-top: 0.5px solid var(--border-default);
  font-size: 0.68rem; color: var(--text-tertiary); }

/* ══ 19n — conditional highlighting (preview cells + rule chips) ══ */
.rb-preview-table td.rb-hl-red   { background: rgba(225, 87, 89, 0.22); }
.rb-preview-table td.rb-hl-amber { background: rgba(242, 142, 43, 0.25); }
.rb-preview-table td.rb-hl-green { background: rgba(89, 161, 79, 0.22); }
.rb-hl-dot { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 10px; }
.rb-hl-dot.rb-hl-red   { background: #e15759; }
.rb-hl-dot.rb-hl-amber { background: #f28e2b; }
.rb-hl-dot.rb-hl-green { background: #59a14f; }

/* ══ 19k — the Phase 5 template gallery strip; 28k — condensed to name-only chips (Chris:
   "keep the presets, but remove the large icons… much more condensed"). The blurb is the
   hover title. Wraps instead of scrolling, so it costs one or two short rows. ══ */
.rb-tpl-strip { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 0 2px; }
.rb-tpl-chip { display: inline-flex; align-items: center; font: inherit; font-size: 0.78rem;
  padding: 4px 11px; border-radius: 99px; background: var(--surface-subtle);
  border: 0.5px solid var(--border-default); color: var(--text-primary); cursor: pointer;
  white-space: nowrap; line-height: 1.3; }
.rb-tpl-chip:hover { border-color: var(--text-tertiary); }
.rb-tpl-blank { border-style: dashed; color: var(--text-secondary); }

/* ══ TASKS (24e) ═══════════════════════════════════════════════════════════
   The Tasks tab's panel, and the New Task modal's assign box. Reuses the
   record page's existing vocabulary — .df-tab for the tab, .badge for the
   chips, .form-input for every field — so the tab looks like it belongs to
   the page it arrived on rather than like a bolted-on feature. */
.df-tab-tasks{padding-bottom:7px}
/* 24g: the count chip rides the SAME second line the division tabs use for
   their stage, so the Tasks tab is the same height as its neighbours and the
   panel edge closes the strip instead of cutting across PowerGen and UPS. */
.rec-divtab-tasks{display:inline-block;font-size:0.6rem;font-weight:700;letter-spacing:0.05em;text-transform:uppercase;padding:1px 8px;border-radius:99px;background:var(--cerio-blue-tint);color:var(--cerio-blue)}
.rec-divtab-tasks.is-none{background:var(--surface-subtle);color:var(--text-tertiary)}
/* 26a — ONE EDIT. While a division is being edited the strip locks: the other tabs
   dim and the click handler refuses with a toast; the hint sits at the end of the
   strip. The "+ Division" tab is dashed and accent-coloured so it reads as an
   action, not another division; its menu is the chips' menu, repositioned. */
.rec-div-tabs.locked .df-tab:not(.on){opacity:.38;cursor:not-allowed}
.rec-tabs-lockhint{align-self:center;font-size:0.78rem;color:var(--text-tertiary);margin-left:8px;padding-bottom:6px}
.rec-divtab-addwrap{position:relative;display:inline-flex;align-items:stretch}
.rec-div-tabs .rec-divtab-add{color:var(--cerio-blue);background:transparent;border-style:dashed;border-color:var(--border-strong);padding:8px 14px 7px}
.rec-div-tabs .rec-divtab-add:hover{color:var(--cerio-blue);border-color:var(--cerio-blue)}
.rec-divtab-addwrap .proj-div-menu{top:calc(100% + 2px)}
.rec-divtab-addwrap .proj-div-menu button{display:flex;align-items:center;gap:8px}
.rec-divtab-addwrap .proj-div-menu .rec-divtab-dot{margin-right:0}
/* the panel is a .div-card with a .div-card-body, exactly like a division
   panel — these only tune what is INSIDE that body. */
.tasks-panel .div-card-body{padding:13px 15px 15px}

.tasks-head{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;margin-bottom:13px}
.tasks-filters{display:flex;align-items:flex-end;gap:10px;flex-wrap:wrap;padding-bottom:13px;border-bottom:1px dashed var(--border-default);margin-bottom:4px}
.tasks-filter{display:flex;flex-direction:column;gap:4px;font-size:0.74rem;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-tertiary)}
.tasks-filter select{max-width:190px;font-size:0.84rem}
.tasks-showdone{display:inline-flex;align-items:center;gap:7px;font-size:0.82rem;color:var(--text-secondary);cursor:pointer;padding-bottom:8px}

.task-row{display:flex;align-items:flex-start;gap:11px;padding:12px 4px;border-top:0.5px solid var(--border-default)}
.task-row:first-child{border-top:none}
.task-cbx{width:17px;height:17px;flex-shrink:0;margin-top:2px;cursor:pointer;accent-color:var(--green)}
.task-cbx-ro{width:17px;flex-shrink:0;color:var(--green);font-weight:700;text-align:center}
.task-main{flex:1;min-width:0}
.task-desc{font-size:0.92rem;font-weight:500;color:var(--text-primary);line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}
.task-meta{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:5px;font-size:0.78rem;color:var(--text-tertiary)}
.task-meta strong{color:var(--text-primary);font-weight:600}
.task-sep{color:var(--text-faint)}
.task-del{background:none;border:none;color:var(--text-faint);font-size:0.95rem;padding:2px 5px;cursor:pointer;font-family:inherit;flex-shrink:0}
.task-del:hover{color:var(--red)}
.task-del-spacer{width:26px;flex-shrink:0}
/* 03b - Edit, beside Delete. Same ghost treatment as .task-del so the pair reads as one control
   group on all three task surfaces; it turns the accent colour rather than red because editing
   is not destructive. */
.task-edit{background:none;border:none;color:var(--text-faint);padding:2px 5px;cursor:pointer;font-family:inherit;flex-shrink:0;display:inline-flex;align-items:center}
.task-edit:hover{color:var(--accent,var(--text-primary))}
.task-row.task-done{opacity:0.6}
.task-row.task-done .task-desc{text-decoration:line-through;color:var(--text-secondary);font-weight:400}

.tasks-donehead{font-size:0.76rem;font-weight:700;text-transform:uppercase;letter-spacing:0.07em;color:var(--text-tertiary);margin:14px 0 2px;padding-top:12px;border-top:1px dashed var(--border-default)}
.tasks-donehint{font-size:0.78rem;color:var(--text-tertiary);margin-top:12px;padding-top:11px;border-top:1px dashed var(--border-default)}
.tasks-empty{border:1px dashed var(--border-strong);border-radius:12px;padding:26px 18px;text-align:center;margin-top:8px}
.tasks-empty-t{font-size:0.92rem;font-weight:600;color:var(--text-primary);margin-bottom:4px}
.tasks-empty-s{font-size:0.83rem;color:var(--text-tertiary);line-height:1.5}

/* the assign box */
.task-hint{font-size:0.76rem;color:var(--text-tertiary);margin-top:5px;line-height:1.45}
.task-chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}
.task-chips:empty{display:none}
.task-chip{display:inline-flex;align-items:center;gap:6px;background:var(--cerio-blue-tint);color:var(--cerio-blue);border:0.5px solid var(--cerio-blue);border-radius:99px;padding:3px 10px;font-size:0.82rem;font-weight:500}
.task-chip button{background:none;border:none;color:inherit;opacity:0.7;cursor:pointer;font-size:0.8rem;padding:0;font-family:inherit}
.task-chip button:hover{opacity:1}
.task-results{margin-top:3px;border:1px solid var(--border-strong);border-radius:8px;background:var(--surface-card);box-shadow:0 8px 22px rgba(0,0,0,0.12);overflow:hidden;max-height:220px;overflow-y:auto}
.task-results[hidden]{display:none}
.task-opt{display:flex;align-items:baseline;gap:9px;width:100%;text-align:left;background:none;border:none;border-top:0.5px solid var(--border-default);padding:8px 11px;font-family:inherit;font-size:0.86rem;color:var(--text-primary);cursor:pointer}
.task-opt:first-child{border-top:none}
.task-opt:hover{background:var(--cerio-blue-tint)}
.task-opt-n{font-weight:600}
.task-opt-e{font-size:0.78rem;color:var(--text-tertiary)}
.task-opt-none{padding:9px 11px;font-size:0.82rem;color:var(--text-tertiary)}
/* Private disables assignment — the field is visibly out of play, not just inert */
.task-disabled{opacity:0.5;pointer-events:none}
.task-disabled input{background:var(--surface-subtle)}
/* 24i: the inventory record's tab strip. The project record's strip follows a
   header block already spaced for it; this one follows the KPI cards, so it
   needs its own breathing room above. */
.inv-rec-tabs{margin-top:16px}
/* 2026-10-19 — THE INVENTORY TAB STRIP AND THE SECTION BELOW IT (Chris, screenshotted).
   The PROJECT record tucks a .div-card.rec-div-panel under its tabs — border-radius:0 8px 8px 8px,
   margin-top:0, no top border — so the active tab merges into the panel. The inventory record is
   now followed by a <details class="rec-sec"> instead, which has none of that tucking and brings
   its own margin, so the strip and the first section met with nothing deciding which paints on
   top: .df-tabs.rec-div-tabs is z-index:0, and a later sibling with a background wins that tie
   and clips the active tab's bottom edge. Explicit clearance, and the strip paints above.
   Three classes (0,3,0) so this cannot be out-ordered by the z-index:0 rule above, which is the
   mistake the 17d fix made and Chris screenshotted twice. */
.df-tabs.rec-div-tabs.inv-rec-tabs{margin-bottom:10px;z-index:1}
.df-tabs.rec-div-tabs.inv-rec-tabs + .rec-sec{margin-top:0}
/* 2026-10-19 — LABELS THAT SANK TO THE BOTTOM OF THEIR ROW (Chris screenshotted it).
   .form-group is display:flex with justify-content:flex-end app-wide, so inputs line up along
   the BOTTOM of a grid row. That is right while every group in the row is the same height, and
   wrong the moment one carries a helper line: Unit Cost's note made that row 107px tall and
   pushed Vendor Quote Number's and Extended Cost's LABELS 47px down the cell, measured.
   Inside the inventory editor's section boxes each group sizes to its own content instead.
   Scoped to .modal, not to #inv-modal-overlay, because openInvModal MOVES the .modal node into
   the page host — an id-based scope would stop applying on the full-page editor. */
.modal .rec-sec .form-grid{align-items:start}

/* ══ the toolbar Tasks page (24j) ═══════════════════════════════════════════ */
.tasks-page-controls{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin:16px 0 14px}
.tasks-segs{display:inline-flex;gap:7px;flex-wrap:wrap}
.tasks-seg{font:inherit;font-size:0.86rem;font-weight:600;padding:7px 15px;border-radius:8px;border:0.5px solid var(--border-default);background:var(--surface-card);color:var(--text-secondary);cursor:pointer}
.tasks-seg:hover{color:var(--text-primary)}
.tasks-seg.on{background:var(--cerio-blue);color:#fff;border-color:var(--cerio-blue)}
.tasks-divfilter{display:inline-flex;align-items:center;gap:12px;flex-wrap:wrap}
.tasks-divchk{display:inline-flex;align-items:center;gap:6px;font-size:0.83rem;color:var(--text-secondary);cursor:pointer}
.tasks-divall{font-size:0.8rem;padding:5px 12px}
.tasks-page-card{background:var(--surface-card);border:0.5px solid var(--border-default);border-radius:var(--radius);overflow:hidden}
.tasks-page-head{padding:11px 15px;background:var(--surface-subtle);border-bottom:0.5px solid var(--border-default);font-size:0.76rem;font-weight:700;text-transform:uppercase;letter-spacing:0.07em;color:var(--text-tertiary)}
.tasks-page-card .task-row{padding:12px 15px}
.tasks-page-card .tasks-donehead{margin:0;padding:11px 15px;border-top:0.5px solid var(--border-default);border-bottom:0.5px solid var(--border-default);background:var(--surface-subtle)}
/* a task you have not looked at yet — the same fact the toolbar badge counts */
.task-row.task-unseen{background:var(--cerio-blue-tint)}
.task-newdot{display:inline-block;width:7px;height:7px;border-radius:99px;background:var(--badge-red);margin-right:7px;vertical-align:1px}

/* .kpi-warn retired in 25e with the unsized-scope note it styled (Chris asked for
   the note to go; 25c prevents the state at source instead). Removed rather than
   left orphaned — an unused class invites a future reader to assume the warning
   is still somewhere. */

/* 25c: the new-project division block. Reads as part of the form, set apart just
   enough that "these fields belong to a division" is obvious. */
.newdiv-block{border:0.5px solid var(--border-default);border-left:3px solid var(--cerio-blue);border-radius:8px;padding:12px 14px;background:var(--surface-subtle)}
.newdiv-head{display:flex;align-items:center;gap:8px;font-size:0.78rem;font-weight:700;text-transform:uppercase;letter-spacing:0.08em;color:var(--cerio-blue);margin-bottom:10px}
.newdiv-dot{display:inline-block;width:9px;height:9px;border-radius:2px}

/* ── Quote Builder modal (26i) — reuses the Sales Calculator's line controls
   (.q-cost-remove, .q-add-cost-btn) and the app's modal chrome. ── */
.qb-modal { max-width: 900px; }
.qb-sub { font-size: 0.78rem; color: var(--text-tertiary); margin-top: 2px; }
.qb-body { display: flex; flex-direction: column; gap: 16px; }
.qb-label { display: block; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-tertiary); font-weight: 600; margin-bottom: 4px; }
.qb-facts { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px 14px; padding: 12px 14px; background: var(--surface-subtle); border-radius: var(--radius-sm); }
.qb-fact-v { font-size: 0.9rem; font-weight: 500; overflow-wrap: anywhere; }
.qb-two { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.qb-box { border: 0.5px solid var(--border-default); border-radius: var(--radius-sm); padding: 10px 12px; font-size: 0.86rem; }
.qb-row { display: flex; justify-content: space-between; gap: 10px; padding: 3px 0; }
.qb-num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.qb-shipped { font-size: 0.68rem; color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.06em; margin-left: 4px; }
.qb-empty { color: var(--text-tertiary); font-size: 0.84rem; }
.qb-lines { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.qb-lines th { text-align: left; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-tertiary); font-weight: 600; padding: 4px 6px; border-bottom: 0.5px solid var(--border-default); }
.qb-lines td { padding: 4px 6px; border-bottom: 0.5px solid var(--border-default); vertical-align: middle; }
.qb-lines .qb-th-r, .qb-lines .qb-cost, .qb-lines .qb-pct, .qb-lines .qb-lineprice { text-align: right; }
.qb-lines .qb-th-pct { width: 110px; }
.qb-lines .qb-th-x { width: 36px; }
.qb-lines .qb-cost { width: 150px; }
.qb-lines .qb-pct { width: 100px; }
.qb-lines .qb-pct.qb-bad { border-color: var(--red); }
.qb-in { font-size: 0.92rem !important; }
.qb-calc { display: grid; grid-template-columns: 1fr 1fr 1.4fr; gap: 14px; align-items: start; }
.qb-field { display: flex; flex-direction: column; gap: 4px; }
.qb-hint { font-size: 0.76rem; color: var(--text-tertiary); }
.qb-check { display: flex; align-items: center; gap: 8px; font-size: 0.84rem; color: var(--text-secondary); margin-top: 8px; }
.qb-results { background: var(--surface-subtle); border-radius: var(--radius-sm); padding: 10px 14px; font-size: 0.88rem; display: flex; flex-direction: column; gap: 4px; }
.qb-res { display: flex; justify-content: space-between; gap: 10px; }
.qb-res-big { font-size: 1.05rem; font-weight: 600; color: var(--cerio-blue); border-top: 0.5px solid var(--border-default); margin-top: 4px; padding-top: 8px; }
.qb-res-profit { color: var(--green); }
.qb-warn { color: var(--red); font-size: 0.8rem; }
.qb-foot { display: flex; justify-content: space-between; align-items: center; gap: 10px; padding: 12px 24px; border-top: 0.5px solid var(--border-default); background: var(--surface-subtle); }
.qb-foot-btns { display: flex; gap: 8px; }
@media (max-width: 760px) { .qb-facts { grid-template-columns: repeat(2, 1fr); } .qb-two, .qb-calc { grid-template-columns: 1fr; } }

.qb-btn-save { border-color: var(--cerio-blue); color: var(--cerio-blue); }
/* Quote Builder — supplemental documents (26k) */
.qb-drop { border: 1.5px dashed var(--border-strong); border-radius: var(--radius-sm); padding: 14px 16px; text-align: center; color: var(--text-tertiary); font-size: 0.86rem; cursor: pointer; transition: background 0.15s, border-color 0.15s; }
.qb-drop.qb-over { background: var(--surface-subtle); border-color: var(--cerio-blue); }
.qb-drop b { color: var(--text-secondary); font-weight: 500; }
.qb-drop.qb-has { text-align: left; border-style: solid; cursor: default; }
.qb-doc { display: flex; align-items: center; gap: 10px; padding: 5px 0; font-size: 0.86rem; color: var(--text-primary); }
.qb-doc-ic { width: 30px; height: 34px; border-radius: 4px; display: grid; place-items: center; font-size: 0.6rem; font-weight: 700; letter-spacing: 0.05em; color: #fff; flex-shrink: 0; }
.qb-doc-ic.pdf { background: #B93A3A; } .qb-doc-ic.docx { background: #2B5FB3; } .qb-doc-ic.xlsx { background: #2C7A57; }
.qb-doc-n { flex: 1; min-width: 0; overflow-wrap: anywhere; } .qb-doc-n small { display: block; color: var(--text-tertiary); font-size: 0.74rem; }
.qb-doc-more { display: block; padding-top: 6px; }
/* 27a — Previous quotes */
.qb-hist { width: 100%; border-collapse: collapse; font-size: 0.84rem; margin-top: 4px; }
.qb-hist th { text-align: left; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-tertiary); font-weight: 600; padding: 4px 6px; border-bottom: 0.5px solid var(--border-default); }
.qb-hist td { padding: 5px 6px; border-bottom: 0.5px solid var(--border-default); vertical-align: middle; }
.qb-hist tr:last-child td { border-bottom: 0; }
.qb-hist .qb-th-r, .qb-hist td.qb-num { text-align: right; }
.qb-hist .qb-th-x { width: 36px; }
.qb-hist-file { overflow-wrap: anywhere; }
.qb-hist-file a { color: var(--cerio-blue); text-decoration: none; }
.qb-hist-file a:hover { text-decoration: underline; }
.qb-hist-file small { color: var(--text-tertiary); margin-left: 6px; }
.qb-hist-missing { color: var(--text-tertiary); }
.qb-applied { font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--green); margin-left: 6px; }


/* ═══════════════════════════════════════════════════════════════════════════
   27h — MOBILE (Chris, 2026-08-27: "we need M1 and M2. Go")
   Measured with _tooling/mobile-layout-probe.mjs at 390px and 820px, every view.
   M1: the record page forced a 422px layout viewport (the division card's button
   row would not wrap) so phones zoomed the page out; ≤360px phones zoomed every
   page (the top bar's lockup + icons are 370px); chips 30px tall; filter rows
   stacked one per line; the Scheduled View timeline 2,320px wide; report template
   cards scrolling sideways with no hint; labels under 12px; the sandbox banner
   over content.  M2: Projects / Inventory rows become CARDS on phones — the
   1,270px table scrolled sideways with the row actions three screens away.
   Every cell already carries data-label (the column definitions were built for
   this); cells without one (actions, the expand toggle) get no caption.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 700px) {
  /* M1-1 · record page: the division card's head wraps; the buttons take their own
     full-width row; the tab strip scrolls instead of clipping "+ Division". */
  .div-card-head { flex-wrap: wrap; }
  .div-card-btns { margin-left: 0; width: 100%; display: flex; flex-wrap: wrap; gap: 8px; }
  .div-card-btn { flex: 1 1 auto; min-height: 40px; }
  .df-tabs.rec-div-tabs { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; padding-bottom: 2px; }
  .df-tabs.rec-div-tabs::-webkit-scrollbar { display: none; }
  .df-tabs.rec-div-tabs .df-tab { flex: 0 0 auto; }

  /* M1-4 · tap targets: 40px minimum (Apple 44 / Android 48; 40 keeps the density) */
  .col-preset-btn, .alert-chip-btn, .btn-ghost, .btn-primary, .filter-select, .ms-btn,
  .q-cost-remove, .qb-hist-rm, .proj-expand-btn { min-height: 40px; }
  .action-btn { min-width: 40px; min-height: 40px; justify-content: center; }
  [data-rb-colrm], [data-rb-hlrm], [data-rb-fxrm], [data-rb-fltrm], [data-rb-chart-rm] {
    min-width: 36px; min-height: 36px; display: inline-flex; align-items: center; justify-content: center; }
  .record-breadcrumb a, #gbc-home { display: inline-block; padding: 8px 2px; }

  /* M1-5 · filter / preset rows swipe sideways instead of stacking one per line */
  .col-preset-row { flex-wrap: nowrap; overflow-x: auto; gap: 8px; padding-bottom: 4px;
    -webkit-overflow-scrolling: touch; scrollbar-width: none; }
  .col-preset-row::-webkit-scrollbar { display: none; }
  .col-preset-row > * { flex: 0 0 auto; }

  /* M1-6 · Scheduled View: grid only (main.js forces the mode); the Grid/Timeline row goes */
  #sched-mode { display: none; }

  /* M1-7 · report template chips wrap on phones too (28k: chips, not cards); ≥40px targets */
  .rb-tpl-strip { flex-wrap: wrap; overflow-x: visible; }
  .rb-tpl-chip { min-height: 40px; }

  /* M1-8 · no text under 12px on a phone (labels, badges) */
  /* 27j: the root font is 15px (html { font-size: calc(15px * --ui-scale) }), so 0.75rem was
     11.25px — under the 12px floor this rule exists for. 0.8rem = 12px. */
  .kpi-label, .proj-kpi-label, .rec-kpi-label, .inv-kpi .kpi-label, .kpi-sub, .quote-card-title,
  .settings-grp, .av-picker-lbl, .alert-legend-label { font-size: 0.8rem; }
  .nav-badge, #user-avatar { font-size: 0.8rem; }

  /* 27j · shrinkable containers. Grid and flex items default to min-width:auto, so a child
     with a wide minimum content (a <select> whose longest option is 500px, a fixed strip)
     pushes its whole card past the screen instead of shrinking — the record page's card head
     measured 394px inside a 366px column and the edit form's .form-group 547px. Every
     container on the record / edit path may shrink; selects and inputs cap at their box. */
  .view-only-grid > *, .view-section, .div-card, .div-card-head, .proj-div-section, .form-group,
  .record-page-body, #view-project-edit-page .modal, #view-inventory-edit-page .modal { min-width: 0; max-width: 100%; }
  select.form-input, input.form-input, textarea.form-input, .form-input { max-width: 100%; min-width: 0; }
  .df-edit-grid > label > .form-input { min-width: 0; }
  .proj-div-form, .view-fields.cols { grid-template-columns: 1fr; }
  .div-card-alerts { min-width: 0; }
  /* 27k · an attachment row: its action icons (40px targets since M1-4) wrap under the name
     instead of running off the right edge (measured: .file-actions ending at 496px). */
  .file-item { flex-wrap: wrap; }
  .file-actions { flex-wrap: wrap; justify-content: flex-end; min-width: 0; max-width: 100%; }
  .file-actions .action-btn { min-width: 36px; }
  /* 27l · the row was still 484px: a long unbreakable file name ("1787821251460_Pricing-Sheet_…")
     is the row's minimum content. Names may break; the row and its parts may shrink. */
  .file-item { min-width: 0; }
  .file-info, .file-name, .file-meta { min-width: 0; overflow-wrap: anywhere; word-break: break-word; }
  .files-list, .files-list * { max-width: 100%; }
  /* 27m · the card editor's Delivery Schedule rows (.delivery-item, a non-wrapping flex row):
     date/units text plus three 36px action buttons ran to 484px inside a 336px row
     (measured: mobile-edit-spill-measure.mjs). Rows wrap; their text shrinks; the buttons
     keep to the right. */
  .delivery-item { flex-wrap: wrap; min-width: 0; }
  .delivery-item > * { min-width: 0; overflow-wrap: anywhere; }
  .delivery-item .file-actions { margin-left: auto; }
  /* 27n · the allocation rows (#proj-alloc-list, the record's Inventory Allocations) put ONE
     inline-styled flex div inside .delivery-item — label, "N units" badge, actions on a
     single non-wrapping line, 418–460px in a 298–336px row (measured by ancestor chain). That
     inner row wraps and shrinks too; its parts may break. */
  .delivery-item > div { flex-wrap: wrap; min-width: 0; max-width: 100%; }
  .delivery-item > div > * { min-width: 0; white-space: normal; overflow-wrap: anywhere; }
  .delivery-item > div > .file-actions { margin-left: auto; }
  /* 09c review · the Supporting Items editor list: the pool name takes a line of its own and wraps;
     units, Pending, notes and Remove go beneath it. On one shared line it shrank to "P…" at 400px
     (review-probe2 F3) — one request split over dates is told apart by these names and dates. */
  .delivery-item > div > .supp-list-label { flex: 1 1 100%; white-space: normal; overflow-wrap: anywhere; }

  /* 27o (Chris, from the phone screenshot): the record's KPI cards as a 2×2 of SQUARES on
     phones instead of one tall stack. .rec-kpis is minmax(200px,1fr), which is one column
     at 366px; two fixed columns, aspect-ratio 1, content centred, type stepped down. */
  .rec-kpis { grid-template-columns: 1fr 1fr; gap: 10px; }
  /* 27p (Chris: "those cards need to be that tall, they could probably be shorter") — no forced
     square: the tile takes its content's height, ~100–120px, two per row. */
  .rec-kpi { display: flex; flex-direction: column; justify-content: center;
    padding: 12px 14px; min-width: 0; min-height: 96px; }
  .rec-kpi-label { font-size: 0.72rem; letter-spacing: 0.06em; margin-bottom: 4px; }
  .rec-kpi-val { font-size: 1.7rem; line-height: 1.1; }
  .rec-kpi-sub { font-size: 0.78rem; line-height: 1.3; margin-top: 4px; }

  /* 27i · the layout viewport must not grow. mobile-m1m2-probe measured the record page at
     434px on a 390px phone in one run and 390px in the next (mobile-minwidth-record): a
     transient overflow during load — Chrome's mobile viewport widens to fit it and never
     shrinks back. Clip at the page so nothing can widen it; every phone view lays out inside
     the screen anyway (M1/M2 above), and the tables that need sideways room scroll inside
     their own .table-wrap. */
  html, body { overflow-x: hidden; max-width: 100vw; }
  /* 27i · the Inventory table carried min-width:1000px from the desktop rules — in card mode
     it kept the actions cell 1,000px out. Cards need no minimum. */
  #inv-table { min-width: 0; }

  /* M1-9 · the sandbox banner: small, bottom-left, out of the way */
  .sandbox-banner { top: auto; bottom: 6px; left: 8px; transform: none; font-size: 0.6rem;
    padding: 3px 8px; opacity: 0.85; }

  /* ── M2 · Projects and Inventory rows as CARDS ─────────────────────────── */
  .proj-table thead { display: none; }
  .proj-table, .proj-table tbody, .proj-table tr, .proj-table td { display: block; width: 100%; max-width: none; }
  /* 27q (Chris: "more contrast between the project card and the background") — the cards had
     been white on the white .table-wrap. On phones the wrap goes transparent so the cards sit on
     the grey page, with a firmer border and the app's card shadow. :has() scopes it to the two
     card tables; other .table-wrap tables keep their box. */
  /* 27r (Chris: contrast, "look at it for dark mode too") — measured card:page fill contrast was
     1.06:1 light / 1.08:1 dark with the wrap transparent: only the edge and shadow separated a
     card. Light: the cards sit in a tinted well (--surface-subtle #EFEEE8, white cards on it,
     ~1.15:1 plus edge + shadow). Dark: the card fill steps UP to --surface-subtle (#1F232C on the
     #111318 page, ~1.16:1) with the strong border; a black shadow is invisible there anyway. */
  .table-wrap:has(> .proj-table) { background: var(--surface-subtle); border: 0; box-shadow: none; border-radius: 12px; overflow: visible; padding: 2px 8px 8px; }
  [data-theme="dark"] .table-wrap:has(> .proj-table) { background: transparent; padding: 0; }
  [data-theme="dark"] .proj-table tbody tr { background: var(--surface-subtle); }
  [data-theme="dark"] .proj-table tbody tr:hover, [data-theme="dark"] .proj-table tbody tr:hover td, [data-theme="dark"] .proj-table tbody tr.row-zebra td { background: transparent; }
  .proj-table tbody tr { border: 1px solid #C8C5BA; border-radius: 10px; margin: 12px 0;
    padding: 10px 14px 12px; background: var(--surface-card); box-shadow: var(--shadow); }   /* 27t: edge 1.45:1 on the well (was --border-strong at 1.25) */
  [data-theme="dark"] .proj-table tbody tr { border-color: var(--border-strong); }
  .proj-table tbody tr:hover, .proj-table tbody tr:hover td, .proj-table tbody tr.row-zebra td { background: transparent; }
  .proj-table td { display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
    padding: 5px 0; border-bottom: 0; white-space: normal; overflow: visible; text-align: right; }
  .proj-table td::before { content: attr(data-label); flex: 0 0 44%; text-align: left;
    font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-tertiary); }
  .proj-table td:not([data-label])::before { content: none; }
  /* the name cell is the card's title */
  .proj-table td[data-label="Project"], .proj-table td[data-label="Supplier"] { display: block; text-align: left;
    font-size: 1rem; padding: 2px 0 8px; border-bottom: 0.5px solid var(--border-default); margin-bottom: 4px; }
  .proj-table td[data-label="Project"]::before, .proj-table td[data-label="Supplier"]::before { content: none; }
  .proj-table td.cell-alerts { text-align: right; padding-top: 5px; padding-bottom: 5px; }
  /* actions: a plain row at the foot of the card, no longer sticky to a right edge */
  .proj-table td.col-actions { position: static; width: 100%; justify-content: flex-end; gap: 4px;
    padding: 8px 0 0; margin-top: 6px; border-top: 0.5px solid var(--border-default); background: transparent !important; box-shadow: none; }
  /* the expand toggle cell (no caption) sits left, small */
  .proj-table td:not([data-label]):not(.col-actions) { justify-content: flex-start; padding: 2px 0; }
  /* section rows ("Active 24 · In progress") are headings, not cards */
  .proj-table tr.comp-section-row { border: 0; background: transparent; padding: 0; margin: 16px 0 4px; }
  .proj-table tr.comp-section-row td { display: block; text-align: left; padding: 0; }
  .proj-table tr.comp-section-row td::before { content: none; }
  /* the expand rows (allocations / availability) attach under their card */
  .proj-table tr.proj-alloc-expand-row, .proj-table tr.inv-expand-row { margin: -12px 0 10px; padding: 0;
    border-radius: 0 0 10px 10px; border-top: 0; }
  .proj-table tr.proj-alloc-expand-row td, .proj-table tr.inv-expand-row td { display: block; text-align: left; padding: 0 !important; }
  .proj-table tr.proj-alloc-expand-row td::before, .proj-table tr.inv-expand-row td::before { content: none; }
  .proj-table td.table-empty { display: block; text-align: center; }
  .proj-table td.table-empty::before { content: none; }
}

/* 27h · M1-2 — small phones (iPhone SE / mini, 360px Androids): the wordmark beside
   the logo goes for everyone (partners already lost it at 700px), so the bar fits and
   the page no longer zooms out. */
@media (max-width: 400px) {
  .hz-brand-text { display: none; }
  .hz-bar { gap: 8px; padding: 0 10px; }
}

/* 30b — TEAMS PHASE 2: the folding team sections on a division panel (Submittals,
   Project Management, Finance) and their lists. The summary IS the section header —
   same face as .view-section-title (Chris's blue titles) — with a one-line status on
   the right; the caret follows the Data Map registry groups (.dm-reg). Tokens only,
   so both themes hold. */
.rec-sec { margin-top: 12px; }
.rec-sec > summary.rec-sec-head { list-style: none; cursor: pointer; display: flex; align-items: center; gap: 10px; font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.09em; color: var(--cerio-blue); padding-bottom: 8px; border-bottom: 0.5px solid var(--border-default); margin-bottom: 12px; user-select: none; }
.rec-sec > summary.rec-sec-head::-webkit-details-marker { display: none; }
.rec-sec > summary.rec-sec-head::before { content: '▸'; font-size: 0.8rem; flex-shrink: 0; }
.rec-sec[open] > summary.rec-sec-head::before { content: '▾'; }
.rec-sec:not([open]) > summary.rec-sec-head { margin-bottom: 0; }
.rec-sec > summary.rec-sec-head:focus-visible { outline: 2px solid var(--cerio-blue); outline-offset: 2px; border-radius: 4px; }
.rec-sec-sum { margin-left: auto; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: flex-end; font-size: 0.74rem; font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--text-tertiary); text-align: right; }
.rec-sec-sum .badge { font-size: 0.64rem; }
.rec-sec-dot { color: var(--text-faint); }
.rec-list { margin-top: 14px; }
.rec-list-head { display: flex; align-items: center; gap: 8px; font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-secondary); margin-bottom: 6px; }
.rec-list-head .badge { font-size: 0.64rem; }
.rec-list-head .btn-ghost { margin-left: auto; padding: 4px 10px; font-size: 0.76rem; color: var(--cerio-blue); }
.rec-list table { width: 100%; border-collapse: collapse; font-size: 0.84rem; }
.rec-list th { text-align: left; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-tertiary); font-weight: 600; padding: 4px 6px; border-bottom: 0.5px solid var(--border-default); }
.rec-list td { padding: 5px 6px; border-bottom: 0.5px solid var(--border-default); vertical-align: middle; }
.rec-list tr:last-child td { border-bottom: 0; }
.rec-list th.num, .rec-list td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.rec-list td.rec-list-actions { text-align: right; white-space: nowrap; width: 64px; }
.rec-list-empty { font-size: 0.82rem; color: var(--text-tertiary); padding: 4px 0 2px; }
/* 2026-10-09 — the Sales box's two terms tables (docs/sales-section-terms-mockup.html): side by side when
   there is room, stacked below 1000px; the letter column, the NET column and a schedule line under its row. */
.sales-terms { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: 4px 24px; }
@media (max-width: 1000px) { .sales-terms { grid-template-columns: minmax(0, 1fr); } }
.sales-prov { font-size: 0.76rem; color: var(--text-tertiary); margin-top: 10px; }
.sales-terms .rec-list td.lt { width: 26px; color: var(--text-tertiary); }
.sales-terms .rec-list td.net { white-space: nowrap; }
.sales-terms .rec-list .sub { font-size: 0.78rem; color: var(--text-tertiary); padding-left: 10px; }
.pb-card-title .pb-import-note, .pb-card-title .badge { margin-left: 8px; vertical-align: middle; }
.rec-list-form { display: flex; gap: 8px; flex-wrap: wrap; align-items: flex-end; margin: 6px 0 10px; padding: 10px; border: 1px dashed var(--border-strong); border-radius: var(--radius-sm); background: var(--surface-subtle); }
.rec-list-form .delivery-field { min-width: 130px; }
@media (max-width: 700px) {
  .rec-sec > summary.rec-sec-head { flex-wrap: wrap; }
  .rec-sec-sum { margin-left: 0; justify-content: flex-start; text-align: left; }
  .rec-list-form .delivery-field { width: 100%; }
}

/* 30c — TEAMS PHASE 3: the Logistics section's delivery rows, the delivery form, and the
   Delivery Schedule card's compact shape + Full-card switch. Tokens only, both themes. */
.deliv-list .deliv-row { border: 0.5px solid var(--border-default); border-radius: var(--radius-sm); padding: 10px 12px; margin-bottom: 8px; background: var(--surface-card); }
.deliv-list .deliv-row:last-child { margin-bottom: 0; }
.deliv-row-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.deliv-row-date { font-weight: 600; font-variant-numeric: tabular-nums; }
.deliv-row-sp { flex: 1; }
.deliv-row-notes { font-size: 0.82rem; color: var(--text-secondary); margin-top: 4px; }
.deliv-row-none { font-size: 0.8rem; color: var(--text-tertiary); margin-top: 6px; }
.deliv-row-fields { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px 20px; margin-top: 10px; padding-top: 9px; border-top: 0.5px solid var(--border-soft); }
.deliv-row-fields .deliv-f-wide { grid-column: span 2; }
.deliv-row-fields .deliv-f-full { grid-column: 1 / -1; }
.deliv-row.deliv-row-flash { border-color: var(--cerio-blue); box-shadow: 0 0 0 3px var(--cerio-blue-tint); }
.deliv-form-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 10px 14px; width: 100%; }
.deliv-form-grid .deliv-f-wide { grid-column: span 2; }
.deliv-form-grid .deliv-f-full { grid-column: 1 / -1; }
.deliv-form-grid .delivery-field { min-width: 0; }
.deliv-card-head { display: flex; align-items: center; gap: 10px; }
/* 31g: .div-card-bar and .deliv-full-switch retired with the controls they styled. */
.deliv-cmp { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 7px 0; border-bottom: 0.5px solid var(--border-soft); font-size: 0.84rem; }
.deliv-cmp:last-of-type { border-bottom: 0; }
.deliv-cmp-date { font-weight: 600; font-variant-numeric: tabular-nums; min-width: 96px; }
.deliv-cmp-sp { flex: 1; }
.deliv-cmp-cov { font-size: 0.78rem; color: var(--text-secondary); }
.deliv-cmp-cov.ok::before { content: '✓ '; color: var(--green); }
.deliv-cmp-cov.no::before { content: '• '; color: var(--red); }
.deliv-cmp-open { background: none; border: 0; padding: 0; font: inherit; font-size: 0.78rem; font-weight: 600; color: var(--cerio-blue); cursor: pointer; white-space: nowrap; }
.deliv-cmp-open:hover { text-decoration: underline; }
.deliv-cmp-foot { font-size: 0.76rem; color: var(--text-tertiary); margin-top: 8px; }
@media (max-width: 700px) {
  .deliv-row-fields, .deliv-form-grid { grid-template-columns: 1fr; }
  .deliv-row-fields .deliv-f-wide, .deliv-form-grid .deliv-f-wide { grid-column: auto; }
  .deliv-cmp-sp { display: none; }
}

/* ── 31a — TEAMS PHASE 4: attachment categories ─────────────────────────────
   Chips on a file row, filter chips above the list, the categorise dialog, and a
   drop target around each division's attachments (the panels have only ever had a
   browse button). One colour per category, tinted from the existing palette so the
   chips read as labels, not as status pills. */
.file-cats { display: inline-flex; gap: 4px; flex-shrink: 0; }
.file-cat-chip { font-size: 0.66rem; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase;
  padding: 2px 6px; border-radius: 3px; white-space: nowrap; border: 0.5px solid transparent; }
/* 2026-10-16 — the SIX section chips. cat-commercial and cat-technical are GONE, not left as
   orphans: nothing can store those words once Part 2 of the migration runs, and the client canons
   them to sales/details on the way in. Tinted from tokens already in this file, the way the three
   they replace were, so they read as labels and not as status pills. */
.file-cat-chip.cat-sales { color: var(--cerio-blue); background: var(--cerio-blue-tint); border-color: var(--cerio-blue-tint); }
.file-cat-chip.cat-details { color: var(--text-secondary); background: var(--surface-subtle); border-color: var(--border-default); }
.file-cat-chip.cat-execution { color: var(--div-ups); background: var(--div-ups-tint); border-color: var(--div-ups-tint); }
.file-cat-chip.cat-project_management { color: var(--div-switchgear); background: var(--div-switchgear-tint); border-color: var(--div-switchgear-tint); }
.file-cat-chip.cat-finance { color: var(--div-mechanical); background: var(--div-mechanical-tint); border-color: var(--div-mechanical-tint); }
.file-cat-chip.cat-logistics  { color: var(--amber); background: rgba(217,164,6,0.14); border-color: rgba(217,164,6,0.28); }
/* 31e: the filter chip bar above an attachment list is retired (Chris) — the per-row
   chips are the whole story. Its rules are removed rather than left orphaned. */
.files-dropzone { border: 1.5px dashed transparent; border-radius: var(--radius-sm); transition: border-color .15s, background .15s; }
.files-dropzone.drag-over { border-color: var(--cerio-blue) !important; background: var(--cerio-blue-tint); }
.files-drop-hint { font-size: 0.76rem; color: var(--text-tertiary); margin-left: 8px; }
.fc-dialog { text-align: left; }
.fc-hint { font-size: 0.82rem; color: var(--text-secondary); margin-bottom: 10px; }
.fc-all { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 6px 0 10px; border-bottom: 0.5px solid var(--border-default); margin-bottom: 6px; }
.fc-all-label { font-size: 0.74rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-tertiary); }
.fc-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 7px 0; border-bottom: 0.5px solid var(--border-soft); }
.fc-row:last-child { border-bottom: 0; }
.fc-name { flex: 1; min-width: 160px; font-size: 0.84rem; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fc-boxes { display: flex; gap: 12px; flex-shrink: 0; }
.fc-box { display: inline-flex; align-items: center; gap: 5px; font-size: 0.8rem; cursor: pointer; white-space: nowrap; }
.fc-box input { width: 14px; height: 14px; cursor: pointer; }
@media (max-width: 700px) {
  .fc-row { align-items: flex-start; }
  .fc-boxes { gap: 8px; }
}

/* ── 31d — THE RECORD PANEL AS BOXES ────────────────────────────────────────
   Chris: "this whole page looks like a wall of text. can we make some boxes around
   the different sections for each team?" Style A of docs/mock-record-boxes.html: every
   box drawn the same, keeping the blue section title the app already uses. The frame is
   the change — no field moved, nothing was removed.
   The boxes sit on the division card, so they are RECESSED (surface-page inside
   surface-card) in both themes rather than tinted with a colour of their own. */
.rec-sec {
  margin-top: 14px;
  background: var(--surface-page);
  border: 1px solid var(--border-default);
  border-radius: 9px;
  overflow: hidden;
}
.rec-sec > summary.rec-sec-head {
  padding: 11px 14px;
  margin-bottom: 0;
  border-bottom: 1px solid var(--border-soft);
}
.rec-sec:not([open]) > summary.rec-sec-head { border-bottom: 0; }
.rec-sec > .rec-sec-body { padding: 14px; }
.rec-sec > summary.rec-sec-head:focus-visible { outline-offset: -2px; border-radius: 8px 8px 0 0; }
/* The sub-headers inside Project Details — General, Sales, Technical. Quieter than a
   box title on purpose: they group fields, they do not frame a section. */
/* 11d (Chris, 2026-09-11: "why is quantity, customer, etc all bigger font than the header
   titled General?") — because it WAS: 0.72rem here against .form-label's 0.78rem, so every
   group header sat smaller than the labels it introduced. Same size as the labels now, and
   the same blue as the box title (.rec-sec-head / .dce-sec-head), so General, Sales and the
   two Technical groups read as headers in the read panel, the editor and the partner panel
   alike — all three draw this one class. */
.rec-grp {
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--cerio-blue); padding-bottom: 7px; border-bottom: 0.5px solid var(--border-soft);
  margin: 0 0 11px;
}
.rec-sec-body > .rec-grp ~ .rec-grp, .rec-sec-body .view-fields + .rec-grp { margin-top: 18px; }
/* 19a — the New Project form (17j) reuses these headings outside any .rec-sec-body / .dce-sec-body, so
   the second and later section headings sat flush under the grid above them (Chris's screenshot,
   2026-09-19: Salesperson → TECHNICAL – PRIMARY, Layout → TECHNICAL – ACCESSORIES). Same 18px the
   editor gives its own. */
#f-newdiv-fields .proj-div-form + .rec-grp, #f-newdiv-fields .dce-sec + .rec-grp { margin-top: 18px; }
/* The Delivery Schedule / Inventory Allocation pair and the Attachments block get the
   same frame, so the panel reads as one set of boxes rather than boxes plus leftovers.
   The pair keeps its own element (refreshProjRecordLive swaps its innerHTML). */
.rec-pair { margin: 14px 0 0; }
.rec-pair > .view-section, .rec-att-box {
  background: var(--surface-page);
  border: 1px solid var(--border-default);
  border-radius: 9px;
  padding: 14px;
}
.rec-att-box { margin-top: 14px; }
.rec-pair > .view-section > .view-section-title:first-child,
.rec-att-box > .view-section-title:first-child { margin-top: 0; }
@media (max-width: 820px) {
  .rec-pair { grid-template-columns: 1fr; }
}
/* 01j (Chris, with a screenshot of an inventory record): the band under "Allocated To
   Projects". Grid stretches both cards of the pair to the taller one, so an item with many
   availability dates and nothing allocated painted a mostly-empty right card — ~350px of
   framed nothing on the record he sent. The cards hug their content now. INVENTORY ONLY:
   the project record's pair carries denser, closer-height content and its aligned bottoms
   read as intended; nothing there was reported. Both inventory renderers — the page and
   the modal — since the pair markup is shared by openInventoryPage and openViewInventory. */
#view-inventory-page .rec-pair, #view-inv-body .rec-pair { align-items: start; }

/* 31g: the quick actions moved onto the compact delivery row. */
.deliv-cmp .action-btn { flex-shrink: 0; }
.deliv-cmp .deliv-cmp-open { margin-left: 4px; }

/* ── 01a — DIVISION PILLS + QUANTITIES IN A SEARCH HIT ──────────────────────
   Chris: a search result should say which product divisions are on the project and how
   much of each. Layout B of docs/mock-search-divisions.html — plain pills on the title
   row, quantities on their own line.
   Every colour here is a TOKEN. The first draft hardcoded rgba(1,43,191,…) for PowerGen
   and the audit suite failed it on the spot: that literal is the LIGHT blue, and on the
   dark theme it paints a near-black wash in the wrong hue. --cerio-blue-tint has always
   existed for exactly this, and the other three divisions now have tokens of their own.
   An unknown division key falls back to the neutral pill — uncoloured, never unreadable. */
.gs-hit-main { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; width: 100%; }
.gs-hit-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gs-divs { display: inline-flex; gap: 5px; flex-wrap: wrap; margin-left: auto; }
.gs-dv { font-size: 0.64rem; font-weight: 700; letter-spacing: 0.02em; padding: 2px 8px;
  border-radius: 4px; white-space: nowrap; color: var(--text-secondary);
  background: var(--surface-subtle); border: 0.5px solid var(--border-default); }
.gs-dv-powergen   { color: var(--cerio-blue);     background: var(--cerio-blue-tint);      border-color: var(--cerio-blue-tint); }
.gs-dv-ups        { color: var(--div-ups);        background: var(--div-ups-tint);         border-color: var(--div-ups-tint); }
.gs-dv-switchgear { color: var(--div-switchgear); background: var(--div-switchgear-tint);  border-color: var(--div-switchgear-tint); }
.gs-dv-mechanical { color: var(--div-mechanical); background: var(--div-mechanical-tint);  border-color: var(--div-mechanical-tint); }
.gs-qty { font-size: 0.75rem; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.gs-qty b { color: var(--text-primary); font-weight: 600; }
.gs-qty-none, .gs-qty-tot { color: var(--text-tertiary); }
@media (max-width: 620px) { .gs-divs { margin-left: 0; } }
/* 01b: an attachment hit wears the same category chips as the attachment list. */
.gs-hit-main .file-cats { margin-left: auto; }

/* ── 01b — THE EDIT FORM'S BOXES ────────────────────────────────────────────
   Same frame as the record page's .rec-sec (31d) so Edit and read look like the same
   page, with one deliberate difference: these do not fold. Hiding half a form while
   someone is filling it in is hostile, and the fold memory belongs to reading. */
.dce-sec { margin-top: 14px; background: var(--surface-page); border: 1px solid var(--border-default);
  border-radius: 9px; overflow: hidden; }
.dce-sec-head { padding: 11px 14px; border-bottom: 1px solid var(--border-soft);
  font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.09em; color: var(--cerio-blue); }
.dce-sec-body { padding: 14px; }
.dce-sec-body > .rec-grp:first-child { margin-top: 0; }
.dce-sec-body .proj-div-form + .rec-grp { margin-top: 18px; }

/* ── 01g — REARRANGE TOOLBAR (Settings › My Views) ──────────────────────────
   Drag rows, per Chris. The grip and the drag states borrow the Report Builder's column
   list so there is one drag-reorder LOOK as well as one behaviour. The row is focusable
   because a drag handle is unreachable without a pointer and partners use whatever they have. */
/* 01i: eighteen destinations rather than six, but the list no longer has to scroll itself —
   it lives in a modal whose body scrolls at 74vh, so overflow:hidden stays and simply keeps
   the rows inside the rounded corners. */
.tb-list { margin-top: 12px; border: 1px solid var(--border-default); border-radius: 8px;
  overflow: hidden; background: var(--surface-page); }
/* The line between "on the toolbar" and "in the More menu". A band inside the list, not a margin
   around one, so it reads as part of the same list the rows belong to. */
/* 01t — the internal-only "Visible to partner" notation on task rows. Its own tokens on
   :root with dark overrides in the two dark blocks below would be the full ceremony; the
   existing --cerio-blue/-tint pair already answers both themes, so the badge rides them. */
.badge-cyan { background: var(--cerio-blue-tint); color: var(--cerio-blue); }

.tb-split { display: flex; align-items: center; gap: 8px; padding: 7px 12px;
  background: var(--surface-subtle); border-bottom: 0.5px solid var(--border-soft);
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-tertiary); }
.tb-split::after { content: ""; flex: 1; height: 1px; background: var(--border-subtle); }
.tb-row.tb-in-more .tb-pos { opacity: 0.55; }
.tb-row { display: flex; align-items: center; gap: 10px; padding: 9px 12px; cursor: grab;
  border-bottom: 0.5px solid var(--border-soft); background: var(--surface-page); }
.tb-row:last-child { border-bottom: 0; }
.tb-row:hover { background: var(--surface-subtle); }
.tb-row:focus-visible { outline: 2px solid var(--cerio-blue); outline-offset: -2px; }
.tb-row.tb-dragging { opacity: 0.45; cursor: grabbing; background: var(--cerio-blue-tint); }
.tb-grip { color: var(--text-faint); font-size: 0.8rem; letter-spacing: -2px; flex-shrink: 0; }
.tb-pos { width: 19px; height: 19px; border-radius: 5px; background: var(--surface-subtle);
  color: var(--text-tertiary); font-size: 0.68rem; font-weight: 700; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center; font-variant-numeric: tabular-nums; }
.tb-name { flex: 1; font-size: 0.87rem; font-weight: 500; }
.tb-saved { font-size: 0.75rem; color: var(--green); opacity: 0; transition: opacity .2s; }
.tb-saved.on { opacity: 1; }

/* 04u: .row-prod-label is GONE with the row label it styled — the partner list has the
   division TAB STRIP now, like the internal one, so nothing repeats the tab on every row. */

/* 07n: the 2026-09-05b FROZEN SPEC SECTIONS rules (.pb-frozen-*, .pb-tpl-card-right) are gone with
   the equipment templates whose dead groups they flagged. */

/* ── 10a · SUPPORTING ITEMS — SCR / packaging pools beside the genset allocation ──────────
   The read block is deliberately QUIETER than the Delivery/Allocation pair above it: dashed,
   unfilled, grey title. Chris: "not the same importance of the allocation as the genset." */
.supp-block { margin-top: 14px; }   /* 10p (Chris: "Why are the supporting Items and Coverage sections dotted?") — the record page's own .view-section frame, nothing else; the dashed box was a mockup flourish the app never had */
.supp-block .supp-title { color: var(--text-secondary); }
.supp-reqline { display:flex; flex-wrap:wrap; gap:6px 14px; align-items:center; font-size:0.82rem; color:var(--text-secondary);
  padding:7px 10px; border-radius:var(--radius-sm); background:var(--surface-subtle); border:0.5px solid var(--border-default); margin-bottom:8px; }
.supp-reqline b { color: var(--text-primary); font-weight: 500; }
.supp-row { padding: 8px 0; border-top: 0.5px solid var(--border-default); }
.supp-row:first-of-type { border-top: 0; }
.supp-row-head { display:flex; align-items:center; gap:10px; }
.supp-row-name { font-weight: 500; }
.supp-row-count { margin-left:auto; font-family:var(--font-body); font-variant-numeric:tabular-nums; color:var(--text-secondary); }
.supp-row.short .supp-row-count { color: var(--amber); }
.supp-pool { display:flex; align-items:center; gap:8px; padding:3px 0 0 18px; font-size:0.8rem; color:var(--text-secondary); }
.supp-pool-units { font-family:var(--font-body); font-variant-numeric:tabular-nums; }
.supp-pool-notes { color: var(--text-tertiary); }
/* the editor list's pool name (09c review: was an inline style, which the phone rule could not reach) */
.supp-list-label { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.supp-empty { font-size:0.82rem; color:var(--text-tertiary); padding:4px 0; }
.supp-pill { display:inline-block; font-size:0.68rem; padding:2px 8px; border-radius:999px; border:0.5px solid var(--border-strong); background:var(--surface-subtle); color:var(--text-secondary); }
.supp-pill.ok   { background:var(--sev-low-bg);  color:var(--sev-low-text);  border-color:var(--sev-low-border); }
.supp-pill.warn { background:var(--alert-supp-bg); color:var(--alert-supp-text); border-color:var(--alert-supp-border); }
/* the fifth alert kind — LOW severity by design: amber, distinct from the genset's yellow
   'Not fully allocated' so the two never read as the same problem */
:root { --alert-supp-bg:#FBF1E0; --alert-supp-border:#EBD3A6; --alert-supp-text:#6B4A0B; }
[data-theme="dark"] { --alert-supp-bg:#33291A; --alert-supp-border:#5A4823; --alert-supp-text:#EDC85C; }
.alert-pill-amber  { background:var(--alert-supp-bg); color:var(--alert-supp-text); border:0.5px solid var(--alert-supp-border); }
.cell-alerts-stack .alert-pill-amber { background:var(--alert-supp-bg); color:var(--alert-supp-text); border:0.5px solid var(--alert-supp-border); }
.chip-supporting.on, .chip-supporting:hover { border-color: var(--alert-supp-border); }
/* 10a · the fifth alert kind on the three surfaces the audit requires for EVERY kind: the
   Alerts-tab card, the Scheduled View row tint, the attention chip. Same amber as the pill. */
.conflict-item.alert-type-supporting { background:var(--alert-supp-bg); border-color:var(--alert-supp-border); }
[data-theme="dark"] .conflict-item.alert-type-supporting { background:var(--alert-supp-bg); border-color:var(--alert-supp-border); }
.alert-summary-chip.chip-supporting { background:var(--alert-supp-bg); border-color:var(--alert-supp-border); color:var(--alert-supp-text); }
[data-theme="dark"] .alert-summary-chip.chip-supporting { background:var(--alert-supp-bg); border-color:var(--alert-supp-border); color:var(--alert-supp-text); }
.schedule-table tbody tr.sched-alert-supporting td,
.schedule-table tbody tr.sched-alert-supporting th.sched-proj { background:var(--alert-supp-bg); }
.schedule-table tbody tr.sched-alert-supporting td.has-units   { background:var(--alert-supp-border); }
:root { --attn-supporting: #B8860B; }
[data-theme="dark"] { --attn-supporting: #EDC85C; }
.attn-supporting { background: var(--attn-supporting); }
/* 10n · the card editor's sections are BOXES, in the record page's own frame (.view-section), and
   the read-only pair it borrows sits in a quiet dashed Coverage box. Scoped to the editor's host,
   so nothing is re-classed and nothing has to be undone on close. */
#dce-inv-sections > .files-section, #dce-inv-sections > .dce-coverage, #dce-inv-sections > #dce-supp-section {
  background: var(--surface-card); border: 0.5px solid var(--border-default); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 17px 19px; margin-top: 14px;
}
.dce-coverage-title { color: var(--text-secondary); }
.dce-coverage-meta { font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--text-tertiary); margin-left: 8px; font-size: 0.78rem; }
#dce-inv-sections > .dce-coverage > .rec-pair { margin-top: 0; }
/* the genset-lane note on both allocation lists (editor header and read pair title) */
.dce-lane-note { font-size: 0.78rem; color: var(--text-tertiary); margin-left: 10px; font-weight: 400; }
.lane-note { font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--text-tertiary); margin-left: 8px; font-size: 0.74rem; }

/* ═══════════════════════════════════════════════════════════
   UPS CATALOG — Admin › Catalog (2026-09-11, phase 2)
   The page is built from existing components: .kpi-card, .df-tab, .col-preset-btn,
   .table-wrap + .proj-table, .comp-section-row, .alert-pill, .badge. These five rules are the
   only additions, and they exist for one reason: .proj-table td is max-width:160px / nowrap /
   ellipsis, which is right for a projects grid and wrong for a catalog, where the LINE IS the
   description and a truncated part number is worthless. Scoped to .cat-table so no other
   table's column widths move.
═══════════════════════════════════════════════════════════ */
.cat-table td.cat-wide  { max-width: 420px; white-space: normal; overflow: visible; }
.cat-table td.cat-part  { max-width: 250px; white-space: normal; overflow: visible;
                          font-size: 0.78rem; color: var(--text-secondary);
                          font-variant-numeric: tabular-nums; }
.cat-table td.cat-attrs { max-width: 270px; white-space: normal; overflow: visible; }
.cat-table td.cat-num, .cat-table th.cat-num { text-align: right; white-space: nowrap;
                          font-variant-numeric: tabular-nums; }
.cat-why { display: block; font-size: 0.76rem; color: var(--text-tertiary); margin-top: 2px; }

/* 12c — attributes as real columns, per group. Each group gets its own header row inside the
   one table (the column set changes per group), so it is a <tr> of <th>s, styled like thead. */
.cat-table tr.cat-colhead th { font-size: 0.72rem; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--text-tertiary); font-weight: 600; padding: 8px 12px 4px; border-bottom: 0.5px solid var(--border-default);
  background: var(--surface-card); white-space: nowrap; }
.cat-table td.cat-attr { white-space: nowrap; max-width: 160px; overflow: hidden; text-overflow: ellipsis; }
.cat-table td.cat-edit { width: 1%; white-space: nowrap; text-align: right; }
.cat-table .cat-add { float: right; padding: 2px 10px; font-size: 0.76rem; }
.cat-lock { display: flex; gap: 10px; align-items: center; padding: 10px 14px; margin: 0 0 12px; border-radius: 8px;
  background: rgba(217,164,6,0.10); border: 0.5px solid var(--border-default); font-size: 0.86rem; color: var(--text-secondary); }
/* the editor panel (js/catalog-edit.js) */
.ced-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
.ced-grid .ced-full { grid-column: 1 / -1; }
.ced-subhead { font-weight: 600; margin: 10px 0 6px; color: var(--text-primary); }
.ced-lock { padding: 10px 14px; margin-bottom: 12px; border-radius: 8px; font-size: 0.86rem;
  background: rgba(217,164,6,0.10); border-left: 3px solid var(--amber); color: var(--text-secondary); }
.ced-hist { border-top: 0.5px solid var(--border-default); margin-top: 14px; padding-top: 12px; }
.ced-hist-row { display: flex; gap: 10px; font-size: 0.8rem; color: var(--text-secondary); padding: 4px 0; }
.ced-hist-row b { color: var(--text-primary); font-weight: 600; }
.ced-hist-when { min-width: 150px; color: var(--text-tertiary); }
@media (max-width: 760px) { .ced-grid { grid-template-columns: 1fr; } }

/* ═══════════════════════════════════════════════════════════
   UPS CATALOG — CONFIGURE FROM CATALOG (2026-09-11, phase 4b)
   The configurator's option rows. Everything else on both surfaces is an existing component —
   .view-section, .files-section, .proj-table, .kpi-card, .alert-pill, .btn-*. These rules
   exist because a radio list of priced options with a reason under each is a shape the app did
   not have; they reuse the same tokens, spacing and radii as .cfg's neighbours so it reads as
   part of the card rather than as a visitor.
═══════════════════════════════════════════════════════════ */
.cfg-group { border: 0.5px solid var(--border-default); border-radius: var(--radius-sm);
  background: var(--surface-card); margin: 0 0 12px; overflow: hidden; }
.cfg-group-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  background: var(--surface-subtle); border-bottom: 0.5px solid var(--border-default);
  padding: 9px 14px; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--cerio-blue); }
.cfg-opt { display: grid; grid-template-columns: 22px 1fr auto; gap: 12px; align-items: start;
  padding: 10px 14px; border-bottom: 0.5px solid var(--border-soft); cursor: pointer; }
.cfg-opt:last-child { border-bottom: none; }
.cfg-opt:hover { background: var(--surface-subtle); }
.cfg-opt.on { background: var(--cerio-blue-tint); }
/* DIMMED IS NOT DISABLED (Chris, 2026-09-11: "keep the suggest dim allow override"). It stays
   clickable, keeps its pointer, and brightens on hover — a misfit is advice, not a gate. */
.cfg-opt.cfg-dim { opacity: 0.52; }
.cfg-opt.cfg-dim:hover { opacity: 0.85; }
.cfg-opt.qty { grid-template-columns: 66px 1fr auto; }
.cfg-opt-name { font-size: 0.88rem; color: var(--text-primary); }
.cfg-opt-cost { font-size: 0.88rem; white-space: nowrap; color: var(--text-primary);
  font-variant-numeric: tabular-nums; }
.cfg-radio { width: 15px; height: 15px; border-radius: 99px;
  border: 1.5px solid var(--border-strong); margin-top: 3px; }
.cfg-opt.on .cfg-radio { border-color: var(--cerio-blue);
  background: radial-gradient(circle at center, var(--cerio-blue) 0 4.5px, transparent 4.5px); }
.cfg-qty { width: 66px; padding: 4px 8px; font-size: 0.82rem; text-align: right; margin-top: 1px; }
.cfg-why { display: block; font-size: 0.76rem; color: var(--text-tertiary); margin-top: 2px; }
.cfg-why.ok { color: var(--green); }
.cfg-why.no { color: var(--amber); }
.cfg-unanswered { font-size: 0.82rem; color: var(--text-secondary); background: var(--surface-subtle);
  border: 0.5px solid var(--border-default); border-radius: var(--radius-sm);
  padding: 8px 12px; margin: 0 0 12px; line-height: 1.55; }
.cfg-reprice { font-size: 0.82rem; color: var(--sev-med-text); background: var(--sev-med-bg);
  border: 0.5px solid var(--sev-med-border); border-radius: var(--radius-sm);
  padding: 8px 12px; margin-top: 12px; line-height: 1.55; }
.mk-cfg-tot { display: flex; justify-content: flex-end; align-items: baseline; gap: 26px;
  flex-wrap: wrap; border-top: 0.5px solid var(--border-default); margin-top: 12px;
  padding-top: 12px; font-size: 0.88rem; color: var(--text-secondary); }
.mk-cfg-tot b { font-family: var(--font-display); font-size: 1.12rem; font-weight: 500;
  color: var(--text-primary); margin-left: 8px; }
@media (max-width: 620px) {
  .cfg-opt, .cfg-opt.qty { grid-template-columns: 22px 1fr; }
  .cfg-opt-cost { grid-column: 2; }
}

/* ── PROJECT SNAPSHOT (2026-09-11) — the builder modal's rows ───────────────────
   The scope radios, the section chips and the Hide-pricing switch. Same tones as the
   division card (blue heads, tinted selection), native inputs underneath so keyboard
   and screen readers get the real control; the visual is the label. */
.snap-group { border: 0.5px solid var(--border-default); border-radius: var(--radius-sm); background: var(--surface-card); margin-bottom: 14px; overflow: hidden; }
.snap-group-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; background: var(--surface-subtle); border-bottom: 0.5px solid var(--border-default); padding: 9px 14px; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--cerio-blue); }
.snap-sub { font-size: 0.78rem; color: var(--text-tertiary); font-weight: 400; text-transform: none; letter-spacing: 0; display: block; margin-top: 2px; }
.snap-group-head .snap-sub { display: inline; margin: 0; }
.snap-quick { margin-left: auto; display: inline-flex; gap: 6px; }
.snap-quick .btn-ghost { font-size: 0.74rem; padding: 3px 9px; text-transform: none; letter-spacing: 0; font-weight: 500; }
.snap-opt { display: grid; grid-template-columns: 18px 1fr auto; gap: 12px; align-items: start; padding: 10px 14px; border-bottom: 0.5px solid var(--border-soft); cursor: pointer; }
.snap-opt:last-child { border-bottom: none; }
.snap-opt.on { background: var(--cerio-blue-tint); }
.snap-opt input { margin: 3px 0 0; accent-color: var(--cerio-blue); }
.snap-opt-name { font-size: 0.88rem; color: var(--text-primary); }
.snap-opt-right { font-size: 0.8rem; color: var(--text-tertiary); white-space: nowrap; }
/* (Chris, 2026-09-11, mockup B): the pills line up in THREE fixed columns so every left edge
   aligns and the eye scans down; a long name ellipsizes inside its pill rather than breaking the grid. */
.snap-chips { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px 8px; padding: 12px 12px; }
.snap-chips .snap-chip { display: flex; justify-content: flex-start; min-width: 0; }
.snap-chips .snap-chip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.snap-chip { display: inline-flex; align-items: center; gap: 7px; font-size: 0.78rem; color: var(--text-primary); background: var(--surface-subtle); border: 0.5px solid var(--border-default); border-radius: 99px; padding: 5px 10px 5px 8px; cursor: pointer; }
.snap-chip input { margin: 0; accent-color: var(--cerio-blue); }
.snap-chip.off { color: var(--text-tertiary); }
.snap-chip.soon { opacity: 0.55; cursor: default; border-style: dashed; }
.snap-soon { font-size: 0.62rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--cerio-blue); }
.snap-money { border-top: 0.5px solid var(--border-soft); padding: 10px 14px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.snap-money .snap-sub { display: inline; margin: 0; flex: 1 1 260px; }
.snap-count { margin-right: auto; font-size: 0.82rem; color: var(--text-tertiary); }

/* ═══════════════════════════════════════════════════════════
   PRICING SHEET · FILL FROM CONFIGURATION (2026-09-11, phase 6)
   One bar above the cost lines. Quiet on purpose: it is an OFFER, not a warning and not a
   status — the sheet is perfectly usable without ever pressing it, and a saved sheet is never
   altered unless somebody does.
═══════════════════════════════════════════════════════════ */
.qb-cfgbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: var(--surface-subtle); border: 0.5px solid var(--border-default);
  border-radius: var(--radius-sm); padding: 10px 13px; margin: 0 0 12px;
  font-size: 0.84rem; color: var(--text-secondary); line-height: 1.55; }
.qb-cfgbar strong { color: var(--text-primary); }
.qb-cfgbar > span:first-child { flex: 1; min-width: 260px; }
.qb-cfgbar button { flex-shrink: 0; }
/* the newer-revision note sits under the sentence, not beside the button */
.qb-cfgbar-note { flex-basis: 100%; font-size: 0.8rem; color: var(--sev-med-text);
  background: var(--sev-med-bg); border: 0.5px solid var(--sev-med-border);
  border-radius: var(--radius-sm); padding: 6px 10px; }
@media (max-width: 620px) { .qb-cfgbar button { width: 100%; } }

/* ── PROJECT SNAPSHOT phase 2 — the attachments pane ─────────────────────────
   Two panes when the project has files: scope + sections left, attachments right.
   Rows are checkable, draggable (a grip) and nudgeable (↑↓); the appendix prints in
   this order. Category chips are the record's own .file-cat-chip. */
.snap-grid { display: grid; grid-template-columns: 1.12fr 1fr; gap: 16px; align-items: start; }   /* 11r: the left pane carries the three pill columns; it gets the room */
.snap-grid .snap-group { margin-bottom: 0; }
.snap-grid .snap-group + .snap-group { margin-top: 14px; }
.snap-att-list { max-height: 56vh; overflow-y: auto; }
.snap-att-src { font-size: 0.7rem; color: var(--text-tertiary); padding: 8px 14px 2px; text-transform: uppercase; letter-spacing: 0.06em; font-weight: 600; }
.snap-att { display: grid; grid-template-columns: 18px 1fr auto; gap: 10px; align-items: center; padding: 7px 14px; border-bottom: 0.5px solid var(--border-soft); cursor: default; }
.snap-att:last-child { border-bottom: none; }
.snap-att.on { background: var(--cerio-blue-tint); }
.snap-att.dragging { opacity: 0.45; }
.snap-att.over { box-shadow: inset 0 2px 0 var(--cerio-blue); }
.snap-att input { margin: 0; accent-color: var(--cerio-blue); }
.snap-att-name { font-size: 0.84rem; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.snap-att-name .file-cats { margin-left: 6px; vertical-align: 1px; }
.snap-att-meta { font-size: 0.72rem; color: var(--text-tertiary); margin-top: 1px; }
.snap-att-tools { display: inline-flex; align-items: center; gap: 2px; color: var(--text-tertiary); }
.snap-att-tools .btn-ghost { padding: 1px 6px; font-size: 0.72rem; line-height: 1.4; }
.snap-grip { font-size: 0.95rem; letter-spacing: -2px; cursor: grab; padding: 0 6px 0 2px; color: var(--text-tertiary); user-select: none; }
.snap-att-foot { border-top: 0.5px solid var(--border-soft); padding: 8px 14px; font-size: 0.74rem; color: var(--text-tertiary); }
.snap-att-empty { padding: 12px 14px; font-size: 0.82rem; color: var(--text-tertiary); }
@media (max-width: 900px) { .snap-grid { grid-template-columns: 1fr; } }

/* ── GLOBAL SEARCH — the machine lines inside a project hit (11w) ─────────────
   One line per machine: division dot, the tab's label, the status badge, the spec
   (kW · tier · package · qty) and the alert pills the record tab wears. */
.gs-machines { display: flex; flex-direction: column; gap: 3px; width: 100%; margin: 3px 0 2px; }
.gs-machine { display: flex; align-items: center; gap: 8px; font-size: 0.76rem; color: var(--text-secondary); min-width: 0; flex-wrap: wrap; }
.gs-machine .rec-divtab-dot { width: 7px; height: 7px; border-radius: 99px; flex-shrink: 0; display: inline-block; }
.gs-machine b { color: var(--text-primary); font-weight: 600; white-space: nowrap; }
.gs-machine .badge { font-size: 0.6rem; padding: 1px 7px; }
.gs-machine .alert-pill.gs-alert { font-size: 0.58rem; padding: 1px 6px; }
.gs-spec { color: var(--text-secondary); min-width: 0; }
.gs-spec i { font-style: normal; color: var(--text-tertiary); margin: 0 4px; }
.gs-none { color: var(--text-tertiary); }
.gs-hit-main .badge.gs-stage { font-size: 0.6rem; padding: 1px 7px; margin-left: auto; }

/* ── REPORT BUILDER — saved reports, ownership and sharing (12e) ─────────────── */
.rb-saved-sub { font-size: 0.68rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-tertiary); margin: 10px 0 2px; }
.rb-saved-sub:first-child { margin-top: 0; }
.rb-saved-row { display: flex; gap: 8px; align-items: center; padding: 4px 0; flex-wrap: wrap; }
.rb-saved-name { flex: 1; min-width: 200px; font-size: 0.86rem; }
.rb-saved-who { font-size: 0.74rem; color: var(--text-tertiary); font-weight: 400; text-transform: none; letter-spacing: 0; }
.rb-saved-tag { display: inline-block; font-size: 0.6rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; padding: 1px 8px; border-radius: 99px; background: var(--cerio-blue-tint); color: var(--cerio-blue); margin-left: 4px; vertical-align: 1px; }
.rb-saved-tag.is-co { background: rgba(99,102,241,0.15); color: #6366F1; }
.rb-saved-tag.is-scope { background: var(--surface-subtle); color: var(--text-secondary); border: 0.5px solid var(--border-default); }
.rb-saved-tag.is-warn { background: var(--sev-med-bg); color: var(--sev-med-text); border: 0.5px solid var(--sev-med-border); }   /* Phase 3: "Needs a decision" */
.rb-share-row { display: flex; gap: 18px; flex-wrap: wrap; align-items: center; font-size: 0.82rem; margin: 8px 0 10px; }
.rb-share-row label { display: flex; gap: 6px; align-items: center; cursor: pointer; }
.rb-share-hint { font-size: 0.74rem; color: var(--text-tertiary); }
.cm-switch { display: flex; gap: 8px; align-items: center; font-size: 0.86rem; color: var(--text-primary); cursor: pointer; margin-top: 2px; }

/* ── CATALOG PHOTOS (13a) — thumbnails, the cover card, the Photos field, the lightbox ────── */
.cat-line { display: flex; gap: 10px; align-items: flex-start; min-width: 0; }
.cat-line-txt { min-width: 0; }
.cp-thumb { width: 44px; height: 34px; object-fit: cover; border-radius: 5px; border: 0.5px solid var(--border-strong); background: var(--surface-subtle); flex: none; cursor: zoom-in; }
.cp-thumb.cat-thumb { margin-top: 1px; }
.cfg-wrap { display: flex; gap: 18px; align-items: flex-start; }
.cfg-wrap-tbl { flex: 1; min-width: 0; }
.cfg-photo { width: 220px; flex: none; }
.cp-cover { margin: 0; }
.cp-cover img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 10px; border: 0.5px solid var(--border-strong); background: var(--surface-subtle); cursor: zoom-in; }
.cp-cover figcaption { font-size: 0.74rem; color: var(--text-tertiary); margin-top: 6px; line-height: 1.4; }
.cp-cover figcaption a { color: var(--cerio-blue); text-decoration: none; }
.cp-inv-cover { float: right; margin: 0 0 10px 18px; width: 200px; }
.cp-inv-cover:empty { display: none; }
.cp-editor .pb-label { display: flex; gap: 6px; align-items: baseline; }
.cp-editor-sub { font-weight: 400; color: var(--text-tertiary); }
.cp-strip { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-start; }
.cp-card { position: relative; width: 128px; }
.cp-card img { display: block; width: 128px; height: 96px; object-fit: cover; border-radius: 8px; border: 0.5px solid var(--border-strong); background: var(--surface-subtle); cursor: zoom-in; }
.cp-card.cp-dragging { opacity: 0.45; }
.cp-star { position: absolute; top: 6px; left: 6px; background: var(--cerio-blue); color: #fff; font-size: 0.58rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; padding: 2px 6px; border-radius: 99px; }
.cp-x { position: absolute; top: 4px; right: 4px; width: 20px; height: 20px; border-radius: 99px; background: rgba(255,255,255,0.92); color: #B42318; font-size: 0.7rem; display: flex; align-items: center; justify-content: center; border: 0.5px solid var(--border-strong); cursor: pointer; padding: 0; }
.cp-grip { position: absolute; top: 74px; right: 6px; color: #fff; font-size: 0.85rem; text-shadow: 0 0 3px rgba(0,0,0,0.6); cursor: grab; }
.cp-cap { font-size: 0.72rem; padding: 3px 6px; margin-top: 4px; width: 100%; }
.cp-add { width: 128px; height: 96px; border: 1.5px dashed var(--border-strong); border-radius: 8px; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--cerio-blue); font-size: 0.78rem; font-weight: 600; background: var(--surface-subtle); cursor: pointer; }
.cp-add small { font-weight: 400; color: var(--text-tertiary); font-size: 0.68rem; margin-top: 3px; }
.cp-busy { font-size: 0.76rem; color: var(--text-tertiary); align-self: center; }
.cp-lb { position: fixed; inset: 0; z-index: 3000; background: rgba(10, 14, 30, 0.86); display: flex; align-items: center; justify-content: center; }
.cp-lb figure { margin: 0; max-width: 92vw; max-height: 92vh; display: flex; flex-direction: column; align-items: center; gap: 10px; }
.cp-lb img { max-width: 92vw; max-height: 84vh; object-fit: contain; border-radius: 8px; background: #fff; }
.cp-lb figcaption { color: rgba(255,255,255,0.85); font-size: 0.86rem; }
.cp-lb-x, .cp-lb-nav { position: absolute; background: rgba(255,255,255,0.12); color: #fff; border: none; border-radius: 99px; width: 40px; height: 40px; font-size: 1.3rem; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.cp-lb-x { top: 18px; right: 18px; font-size: 1rem; }
.cp-lb-nav { top: 50%; transform: translateY(-50%); font-size: 1.8rem; }
.cp-lb-nav.prev { left: 18px; } .cp-lb-nav.next { right: 18px; }
.cp-lb-x:hover, .cp-lb-nav:hover { background: rgba(255,255,255,0.24); }

/* ── POWERGEN PHOTO LIBRARY (08a) — js/pg-photo-library.js; the approved look is docs/photo-library-mockup.html
   (v2). Proposal Settings' groups and the step-7 Cover photo card; the photo cards themselves are the
   catalog's cp-* (no lightbox here, so no zoom cursor). PowerGen's own — nothing UPS reads these. ── */
.pl-toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin: 10px 0 14px; }
.pl-toolbar .form-input { max-width: 240px; }
.pl-group { background: var(--surface-card); border: 0.5px solid var(--border-default); border-radius: 12px; padding: 12px 14px; margin-bottom: 10px; }
.pl-group-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.pl-group-title { font-weight: 600; font-size: 0.93rem; }
.pl-tag { display: inline-block; font-size: 0.7rem; padding: 2px 8px; border-radius: 99px; border: 0.5px solid var(--border-strong); color: var(--text-tertiary); background: var(--surface-subtle); font-style: italic; }
.pl-used { margin-left: auto; font-size: 0.74rem; color: var(--text-tertiary); }
.pl-group .cp-card img, .pl-modal .cp-card img { cursor: default; }
.pl-cap-ro { font-size: 0.72rem; color: var(--text-tertiary); margin-top: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pl-retag { font-size: 0.7rem; padding: 2px 8px; margin-top: 4px; }
.pl-modal { max-width: 560px; }
.pl-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px; }
.pl-cover { display: grid; grid-template-columns: 260px 1fr; gap: 18px; align-items: start; }
/* The cover box is the document's own frame shape (4950929 x 2541653 EMU ≈ 1.95 : 1) and the photo is
   CONTAINED in it, as api/generate-proposal.js fits it — what the card shows is what prints. */
.pl-cover > img, .pl-cover-blank { width: 100%; aspect-ratio: 39 / 20; object-fit: contain; border-radius: 10px; border: 0.5px solid var(--border-strong); background: var(--surface-subtle); }
.pl-cover-blank { display: flex; align-items: center; justify-content: center; text-align: center; padding: 8px; font-size: 0.78rem; color: var(--text-tertiary); }
.pl-why { font-size: 0.84rem; line-height: 1.55; }
.pl-why-sub { color: var(--text-tertiary); margin-top: 4px; }
.pl-why-warn { margin-top: 8px; }
.pl-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.pl-picker { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 12px; }
.pl-pick { width: 128px; font-size: 0.72rem; text-align: center; color: var(--text-tertiary); cursor: pointer; background: none; border: 0; padding: 0; font-family: inherit; line-height: 1.4; }
.pl-pick img, .pl-pick .pl-none { display: block; width: 128px; height: 96px; object-fit: contain; border-radius: 8px; border: 0.5px solid var(--border-strong); background: var(--surface-subtle); margin-bottom: 4px; }
.pl-pick.on img, .pl-pick.on .pl-none { outline: 2px solid var(--cerio-blue); outline-offset: 2px; }
.pl-none { display: flex !important; align-items: center; justify-content: center; font-size: 0.76rem; }
@media (max-width: 760px) { .pl-cover, .pl-grid2 { grid-template-columns: 1fr; } }

/* ═══════════════════════════════════════════════════════════════════════════
   CERIO STACK (20260916e) — More › Stack
   The markup is built by js/stack-game.js; every class here is stk-prefixed so
   nothing in this 7k-line sheet can reach into it by accident and it cannot
   reach out. Tokens only, no literals, except inside the well.

   THE WELL IS AN IDENTITY ELEMENT AND DOES NOT FOLLOW THE THEME. It is brand
   navy in light and dark alike, exactly the call .hz-bar makes for the Horizon
   gradient — the reasoning is the same one written at that rule, and the same
   consequence: everything drawn INSIDE it takes fixed colours, because the
   surface behind them is fixed. Nothing outside the well does that.

   BLOCK SIZE, chosen by Chris 2026-09-16 from the four-way comparison: the
   standard 10 x 20 grid in a well that maxes at 250px, so a block is 25px
   rather than the 30px the first mockup used. Cell size is NOT set anywhere —
   ten equal columns in a 1:2 box are square by construction, so changing the
   clamp below is the only way to resize blocks, and it cannot desynchronise.
   NO TEXTURE, also chosen: the bevel alone (Chris picked A from six).
   ═══════════════════════════════════════════════════════════════════════════ */
.stk-head { margin-bottom: 18px; }
.stk-title { font-family: var(--font-display); font-size: 1.32rem; font-weight: 700; letter-spacing: -0.02em; }
.stk-sub { font-size: 0.84rem; color: var(--text-secondary); max-width: 62ch; margin-top: 2px; }

.stk-stage { display: grid; gap: 18px; grid-template-columns: minmax(170px, 1fr) auto minmax(170px, 1fr); align-items: start; justify-content: center; }
.stk-rail { display: flex; flex-direction: column; gap: 14px; }
.stk-rail-l { order: 1; } .stk-board-col { order: 2; } .stk-rail-r { order: 3; }

.stk-panel { background: var(--surface-card); border: 0.5px solid var(--border-default); border-radius: var(--radius); padding: 14px; }
.stk-panel-t { font-family: var(--font-display); font-size: 0.66rem; font-weight: 700; letter-spacing: 0.11em; text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 11px; display: flex; align-items: baseline; gap: 8px; }
.stk-panel-t em { font-style: normal; font-weight: 500; letter-spacing: 0.02em; text-transform: none; font-size: 0.7rem; color: var(--text-faint); }

.stk-stat { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 7px 0; }
.stk-stat + .stk-stat { border-top: 0.5px solid var(--border-soft); }
.stk-stat dt { font-size: 0.7rem; font-weight: 500; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-secondary); }
.stk-stat dd { font-family: var(--font-display); font-weight: 700; font-size: 1.3rem; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.stk-load { margin-top: 12px; }
.stk-load-l { display: flex; justify-content: space-between; font-size: 0.62rem; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 6px; }
.stk-load-track { height: 6px; border-radius: 999px; background: var(--surface-subtle); overflow: hidden; }
.stk-load-fill { height: 100%; width: 0; border-radius: 999px; background: var(--cerio-blue); transition: width 0.3s ease; }
.stk-callout { margin-top: 11px; min-height: 28px; font-family: var(--font-display); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; text-align: center; padding: 6px; border-radius: var(--radius-sm); background: var(--cerio-blue-tint); color: var(--cerio-blue); opacity: 0; transition: opacity 0.2s ease; }
.stk-callout.stk-on { opacity: 1; }

/* ── the well ── */
.stk-board-col { display: flex; flex-direction: column; gap: 12px; align-items: center; }
.stk-well-shell { position: relative; padding: 9px; border-radius: var(--radius-lg); background: linear-gradient(150deg, #011C7E, #012BBF 62%, #0A3BE0); box-shadow: var(--shadow); }
.stk-well {
  position: relative; width: clamp(200px, min(38vw, 46vh), 250px); aspect-ratio: 1 / 2; max-width: 100%;
  display: grid; grid-template-columns: repeat(10, 1fr); grid-template-rows: repeat(20, 1fr);
  gap: 1px; padding: 5px; overflow: hidden;
  background: linear-gradient(180deg, #0B1230 0%, #111B44 100%);
  border: 1px solid rgba(120,150,255,0.22); border-radius: 10px;
}
.stk-cell { background: rgba(255,255,255,0.035); border-radius: 2px; }
.stk-cell.stk-filled {
  background: var(--stk-bc); border-radius: 2px;
  box-shadow: inset 0 2px 0 rgba(255,255,255,0.30), inset 2px 0 0 rgba(255,255,255,0.12),
              inset 0 -3px 0 rgba(0,0,0,0.30), inset 0 0 0 1px rgba(0,0,0,0.30);
}
/* The ghost shows where the piece lands. color-mix so one rule serves all seven
   brands; a browser without it simply draws no ghost, which costs nothing. */
.stk-cell.stk-ghost { background: transparent; box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--stk-bc) 62%, transparent); }

.stk-veil { position: absolute; inset: 5px; border-radius: 8px; display: none; align-items: center; justify-content: center; flex-direction: column; gap: 6px; text-align: center; padding: 12px; background: rgba(4,8,26,0.82); color: #E9EBF2; font-family: var(--font-display); }
.stk-veil.stk-on { display: flex; }
.stk-veil b { font-size: 1rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }
.stk-veil span { font-size: 0.74rem; font-weight: 500; color: #93A0C4; max-width: 22ch; line-height: 1.45; }

.stk-ctrls { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
.stk-btn { font-family: var(--font-display); font-size: 0.74rem; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; padding: 9px 16px; border-radius: var(--radius-sm); border: 0.5px solid var(--border-strong); background: var(--surface-card); color: var(--text-primary); cursor: pointer; transition: background var(--transition), border-color var(--transition); }
.stk-btn:hover { border-color: var(--cerio-blue); color: var(--cerio-blue); }
.stk-btn-primary { background: var(--cerio-blue); border-color: var(--cerio-blue); color: #fff; }
.stk-btn-primary:hover { background: var(--cerio-blue-dark); border-color: var(--cerio-blue-dark); color: #fff; }

/* Touch pad: hidden on pointer devices, shown with the stacked layout below. */
.stk-pad { display: none; grid-template-columns: repeat(4, 1fr); gap: 8px; width: 100%; max-width: 330px; }
.stk-pad button { aspect-ratio: 1.5 / 1; border-radius: var(--radius-sm); border: 0.5px solid var(--border-strong); background: var(--surface-card); font-family: var(--font-display); font-weight: 700; font-size: 0.9rem; color: var(--text-secondary); cursor: pointer; display: flex; align-items: center; justify-content: center; }
.stk-pad button:active { background: var(--cerio-blue-tint); color: var(--cerio-blue); }
.stk-pad button small { font-size: 0.54rem; letter-spacing: 0.08em; text-transform: uppercase; }
.stk-pad-wide { grid-column: span 3; }

/* ── previews ── */
.stk-q-row { display: flex; align-items: center; gap: 11px; }
.stk-q-row + .stk-q-row { margin-top: 9px; }
.stk-mini { display: grid; grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(4, 1fr); gap: 1px; width: 50px; flex: 0 0 50px; aspect-ratio: 1; padding: 4px; border-radius: var(--radius-sm); background: linear-gradient(180deg, #0B1230, #111B44); border: 1px solid rgba(120,150,255,0.22); }
.stk-mini-lg { width: 64px; flex-basis: 64px; }
.stk-mini .stk-cell { background: transparent; border-radius: 1.5px; }
.stk-mini .stk-cell.stk-filled { background: var(--stk-bc); box-shadow: inset 0 1px 0 rgba(255,255,255,0.3), inset 0 -2px 0 rgba(0,0,0,0.3), inset 0 0 0 1px rgba(0,0,0,0.26); }
.stk-q-name { font-family: var(--font-display); font-weight: 700; font-size: 0.84rem; letter-spacing: -0.01em; color: var(--text-primary); }
.stk-q-sub { font-size: 0.68rem; color: var(--text-tertiary); letter-spacing: 0.03em; }
.stk-q-up .stk-q-name { color: var(--cerio-blue); }

.stk-keys { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 2px 14px; }
.stk-keys div { display: flex; align-items: center; gap: 7px; padding: 4px 0; font-size: 0.74rem; color: var(--text-secondary); }
.stk-keys kbd { font-family: var(--font-display); font-size: 0.62rem; font-weight: 700; padding: 2px 6px; min-width: 22px; text-align: center; border-radius: 5px; background: var(--surface-subtle); border: 0.5px solid var(--border-default); color: var(--text-primary); }

/* The board leads on a narrow screen and the two rails fall in beside each
   other under it; the touch pad appears at the same breakpoint, since that is
   where a pointer stops being a safe assumption. */
@media (max-width: 900px) {
  .stk-stage { grid-template-columns: 1fr 1fr; }
  .stk-board-col { order: 1; grid-column: 1 / -1; }
  .stk-rail-l { order: 2; } .stk-rail-r { order: 3; }
  .stk-pad { display: grid; }
  .stk-well { width: min(80vw, 300px); }
}
@media (max-width: 520px) { .stk-stage { grid-template-columns: 1fr; } }

/* ── CERIO STACK: the score board (16e) ───────────────────────────────────
   Two surfaces for one stored record. The RECORD LINE sits under the view
   header where it is read before the board is ever looked at; TOP RUNS is the
   rail panel with the field, dates, and your own row tinted. Chris picked both
   together over either alone, and over a single champion card.
   The board is shared with partner accounts — see schema-stack-scores.sql. */
/* 16f — THE hidden ATTRIBUTE LOSES TO A display RULE, and this cost a visible defect.
   drawRecord() sets `elRecord.hidden = !top`, which is correct; but `hidden` only works
   through the UA's `[hidden]{display:none}`, and an author rule of the same element wins
   on cascade order. So on a database with no scores the strip rendered as an empty blue
   bar reading "🏆 RECORD" and nothing else — shipped that way in 16e. A probe asking
   `#stk-record:not([hidden])` reported it hidden, because the ATTRIBUTE was set; only a
   screenshot showed the pixels. Any stk element toggled by .hidden needs this pair. */
.stk-record[hidden] { display: none; }
.stk-record {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 12px;
  padding: 9px 13px; border-radius: var(--radius-sm);
  background: var(--cerio-blue-tint); border: 0.5px solid var(--border-default);
}
.stk-record-cup { font-size: 0.92rem; line-height: 1; }
.stk-record-lab { font-family: var(--font-display); font-size: 0.62rem; font-weight: 700; letter-spacing: 0.11em; text-transform: uppercase; color: var(--cerio-blue); }
.stk-record-who { font-family: var(--font-display); font-weight: 700; font-size: 0.88rem; color: var(--text-primary); }
.stk-record-num { font-family: var(--font-display); font-weight: 800; font-size: 0.95rem; font-variant-numeric: tabular-nums; color: var(--cerio-blue); }
/* margin-left:auto pushes the date to the far edge on a wide rail and simply wraps
   underneath on a narrow one — no second rule and no breakpoint. */
.stk-record-when { font-size: 0.75rem; color: var(--text-tertiary); margin-left: auto; }

.stk-lb-row { display: flex; align-items: center; gap: 10px; padding: 7px 0; }
.stk-lb-row + .stk-lb-row { border-top: 0.5px solid var(--border-soft); }
/* Your own row is tinted wherever it falls — it is the line people look for. The
   negative margin lets the tint reach the panel's padding edge without the panel
   needing to know about it. */
.stk-lb-you { background: var(--cerio-blue-tint); margin: 0 -8px; padding-left: 8px; padding-right: 8px; border-radius: 6px; }
.stk-lb-you + .stk-lb-row { border-top-color: transparent; }
.stk-lb-rank { font-family: var(--font-display); font-weight: 800; font-size: 0.7rem; width: 20px; flex: 0 0 20px; text-align: center; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.stk-lb-row:first-child .stk-lb-rank { color: var(--amber); font-size: 0.85rem; }
.stk-lb-who { flex: 1; min-width: 0; }
/* A display name is free text and can be long; it truncates rather than pushing the
   score out of the panel. */
.stk-lb-name { font-size: 0.81rem; font-weight: 500; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stk-lb-date { font-size: 0.68rem; color: var(--text-tertiary); }
.stk-lb-score { font-family: var(--font-display); font-weight: 700; font-size: 0.88rem; font-variant-numeric: tabular-nums; color: var(--text-primary); }
.stk-lb-you .stk-lb-name, .stk-lb-you .stk-lb-score { color: var(--cerio-blue); }
.stk-board-empty { font-size: 0.79rem; color: var(--text-secondary); line-height: 1.6; }

.stk-mine { margin-top: 11px; padding-top: 9px; border-top: 0.5px solid var(--border-default); display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.stk-mine dt { font-size: 0.68rem; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-secondary); }
.stk-mine dd { font-family: var(--font-display); font-weight: 700; font-size: 0.95rem; font-variant-numeric: tabular-nums; color: var(--text-primary); }

/* The record-claiming veil. Caterpillar yellow for the headline — the one place in
   this view a piece colour is used as UI, and it earns it: it is the colour of the
   longest piece, and the moment is worth one flourish. */
.stk-veil.stk-gold b { color: #FFCD11; }

/* 16e — the record holder's company, beside their name on the strip. Muted and one step
   smaller: it identifies, it does not compete with the name or the number. On the BOARD the
   company shares the existing .stk-lb-date sub-line rather than adding a third row per entry. */
.stk-record-org { font-size: 0.75rem; color: var(--text-tertiary); }

/* 16f — the Hold explainer. Sits under the preview, not beside it: the row above is a
   fixed-width tile plus two short lines, and threading prose through that grid would
   squeeze it to a column of single words at rail width. */
.stk-help { margin-top: 10px; font-size: 0.74rem; line-height: 1.55; color: var(--text-secondary); }
.stk-help b { color: var(--text-primary); font-weight: 700; }
/* ── 19g · the welcome window, the tour and custom keys (js/stack-game.js) ─────────────────────
   The welcome is an ordinary .modal-overlay/.modal. The tour is one .modal positioned BESIDE its
   target (fixed, above a dim layer); the target is lifted over the dim with .stk-tour-target so
   the arrow points at something lit. Under 900 px the window centres and the arrow hides. */
.stk-howto { font-size: 0.72rem; padding: 2px 9px; margin-left: 8px; vertical-align: 1px; }
.stk-welcome-body p { margin: 0 0 12px; line-height: 1.55; font-size: 0.92rem; }
.stk-welcome-body p:last-child { margin-bottom: 0; }
.stk-welcome-small { font-size: 0.76rem; color: var(--text-tertiary); }
.stk-panel-t .stk-keys-edit { float: right; font-size: 0.66rem; padding: 1px 8px; letter-spacing: 0.04em; text-transform: none; }
.stk-panel-t .stk-keys-edit[hidden] { display: none; }
.stk-key-row { font: inherit; display: flex; align-items: center; gap: 7px; padding: 4px 6px; margin: 0 -6px; font-size: 0.74rem; color: var(--text-secondary); background: none; border: 0.5px dashed var(--border-strong); border-radius: 6px; cursor: pointer; text-align: left; }
.stk-key-row:hover { color: var(--cerio-blue); border-color: var(--cerio-blue); }
.stk-key-row.stk-listening kbd { color: var(--cerio-blue); border-color: var(--cerio-blue); background: var(--cerio-blue-tint); animation: stk-pulse 1s ease-in-out infinite; }
@keyframes stk-pulse { 50% { opacity: 0.45; } }
.stk-keys-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-top: 10px; padding-top: 10px; border-top: 0.5px solid var(--border-soft); }
.stk-keys-foot.hidden { display: none; }
.stk-keys-note { font-size: 0.72rem; color: var(--text-secondary); line-height: 1.4; }
.stk-keys-note.stk-bad { color: var(--sev-high-text); }
.stk-keys-btns { display: flex; gap: 6px; }
.stk-keys-btns .btn-ghost, .stk-keys-btns .btn-primary { font-size: 0.7rem; padding: 4px 10px; }
.stk-tour-dim { position: fixed; inset: 0; background: rgba(28,35,51,0.55); z-index: var(--z-overlay); }
.stk-tour-dim.hidden { display: none; }
.stk-tour-target { position: relative; z-index: calc(var(--z-overlay) + 1); box-shadow: 0 0 0 3px var(--cerio-blue), 0 0 0 7px var(--cerio-blue-tint); border-radius: var(--radius-sm); }
.stk-tour { position: fixed; z-index: calc(var(--z-overlay) + 2); max-width: 420px; width: min(420px, calc(100vw - 32px)); }
.stk-tour.hidden { display: none; }
.stk-tour.stk-tour-centered { left: 50% !important; top: 50% !important; transform: translate(-50%, -50%); }
.stk-tour .modal-body { font-size: 0.9rem; line-height: 1.55; }
.stk-tour .modal-body kbd { font-family: var(--font-display); font-size: 0.72rem; font-weight: 700; padding: 1px 6px; border-radius: 5px; background: var(--surface-subtle); border: 0.5px solid var(--border-default); }
.stk-tour-step { font-size: 0.7rem; color: var(--text-tertiary); letter-spacing: 0.05em; text-transform: uppercase; }
.stk-tour-spacer { flex: 1; }
.stk-tour-arrow { position: absolute; width: 16px; height: 16px; background: var(--surface-card); border-left: 0.5px solid var(--border-default); border-bottom: 0.5px solid var(--border-default); transform: rotate(45deg); }
.stk-tour-arrow.stk-arrow-left { left: -8px; }
.stk-tour-arrow.stk-arrow-right { right: -8px; transform: rotate(225deg); }
.stk-tour-arrow.hidden { display: none; }
@media (prefers-reduced-motion: reduce) { .stk-key-row.stk-listening kbd { animation: none; } }

/* ── 17c · the inventory catalog picker (js/catalog-config.js, cfgInvPartHtml) ─────────────────
   Unit, then options. Everything else it draws is the configurator's own (.cfg-group/.cfg-opt),
   the record tabs (.df-tabs) and the preset buttons (.col-preset-btn). These four are the two
   bits of layout it has of its own: the capacity strip and the search hits. */
.cfg-strip { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0 10px; }
.cfg-strip .col-preset-btn { padding: 4px 12px; }
.cfg-hits { border: 0.5px solid var(--border-default); border-radius: 8px; overflow: hidden; margin-top: 8px; max-width: 760px; background: var(--surface-card); }
.cfg-hits-hd { padding: 6px 12px; font-size: 0.7rem; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-secondary); background: var(--surface-subtle); }
.cfg-hit { display: block; width: 100%; text-align: left; padding: 9px 12px; border: 0; border-bottom: 0.5px solid var(--border-default); background: transparent; color: var(--text-primary); font: inherit; cursor: pointer; }
.cfg-hit:last-child { border-bottom: 0; }
.cfg-hit:hover { background: var(--surface-subtle); }
.cfg-hit .gs-hit-sub { display: block; margin-top: 2px; }
.cfg-hit.gs-none { cursor: default; color: var(--text-secondary); font-size: 0.84rem; }

/* ── 17g — CHANGE ORDERS (js/team-lists.js): the modal's own parts and the Alerts chips ── */
.co-modal { max-width: 780px; }
.co-modal .modal-header { gap: 12px; }
.co-modal-status { margin-left: auto; }
.co-subtitle { font-size: 0.82rem; color: var(--text-tertiary); margin: -4px 0 16px; }
.co-subtitle b { color: var(--text-secondary); font-weight: 600; }
.co-money, .co-days { display: flex; gap: 8px; align-items: center; }
.co-days .form-input { max-width: 110px; }
.co-days span { font-size: 0.84rem; color: var(--text-secondary); white-space: nowrap; }
.co-sign { display: inline-flex; flex-shrink: 0; }
.co-sign .btn-ghost { padding: 7px 10px; font-size: 0.8rem; border-radius: 0; }
.co-sign .btn-ghost:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.co-sign .btn-ghost:last-child  { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; margin-left: -0.5px; }
.co-sign .btn-ghost.on { background: var(--cerio-blue-tint); color: var(--cerio-blue); border-color: var(--cerio-blue); position: relative; z-index: 1; }
.co-hint { margin-top: 12px; }
.rec-list-head .tl-co-new { margin-left: auto; padding: 6px 14px; font-size: 0.8rem; }
.co-sales-tail .view-field-val .badge { font-size: 0.64rem; vertical-align: 1px; }
.alert-summary-chip.chip-co_pastdue { background:#FCEBEB;border-color:#F0BABA;color:#791F1F; }
.alert-summary-chip.chip-co_pending { background:#FCF3B8;border-color:#ECDC6E;color:#7A5F0A; }
@media (max-width: 700px) { .co-modal .form-grid { grid-template-columns: 1fr; } .co-modal .span-2, .co-modal .span-3 { grid-column: span 1; } }
/* 17g — the two change-order kinds at every alert surface the audit demands: attention chip,
   Alerts-page card, schedule row tint. Past Due wears the past-due delivery's colours; Pending
   wears Not-fully-allocated's yellow — same severities, same eye. */
.attn-co_pastdue { background: var(--attn-pastdue); }
.attn-co_pending { background: var(--attn-unallocated); }
.attn-proposal_stale { background: var(--attn-qtymismatch); }   /* 05a: the blue family, like its pill */
.conflict-item.alert-type-co_pastdue { background:#FCEBEB; border-color:#F0BABA; }
.conflict-item.alert-type-co_pending { background:#FCF3B8; border-color:#ECDC6E; }
[data-theme="dark"] .conflict-item.alert-type-co_pastdue { background:#2E1A1A; border-color:#5A2E2E; }
[data-theme="dark"] .conflict-item.alert-type-co_pending { background:#2E2810; border-color:#524015; }
.schedule-table tbody tr.sched-alert-co_pastdue td,
.schedule-table tbody tr.sched-alert-co_pastdue th.sched-proj      { background:#FCEBEB; }
.schedule-table tbody tr.sched-alert-co_pastdue td.has-units       { background:#F6D6D6; }
.schedule-table tbody tr.sched-alert-co_pending td,
.schedule-table tbody tr.sched-alert-co_pending th.sched-proj      { background:#FCF3B8; }
.schedule-table tbody tr.sched-alert-co_pending td.has-units       { background:#ECDC6E; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-co_pastdue td,
[data-theme="dark"] .schedule-table tbody tr.sched-alert-co_pastdue th.sched-proj { background:#3A2020; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-co_pastdue td.has-units  { background:#4E2A2A; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-co_pending td,
[data-theme="dark"] .schedule-table tbody tr.sched-alert-co_pending th.sched-proj { background:#2E2810; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-co_pending td.has-units  { background:#524015; }

/* ── 05a — Proposal out of date: the qtymismatch blues, one shade apart (a fact about a document,
   not a late delivery). Chip, Alerts-page card and schedule row tint, light and dark. ── */
.alert-summary-chip.chip-proposal_stale { background:#E4EBFB; border-color:#B9CCF0; color:#23407F; }
.conflict-item.alert-type-proposal_stale { background:#E9EFFB; border-color:#BFD0F1; }
[data-theme="dark"] .conflict-item.alert-type-proposal_stale { background:#1B2437; border-color:#2F4670; }
.schedule-table tbody tr.sched-alert-proposal_stale td,
.schedule-table tbody tr.sched-alert-proposal_stale th.sched-proj   { background:#EBF0FB; }
.schedule-table tbody tr.sched-alert-proposal_stale td.has-units    { background:#D1DCF3; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-proposal_stale td,
[data-theme="dark"] .schedule-table tbody tr.sched-alert-proposal_stale th.sched-proj { background:#1B2437; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-proposal_stale td.has-units  { background:#2F4670; }

/* ── 18a — the FULL change-order modal: group headers, the document slot, machine ticks ── */
.co-modal { max-width: 880px; }
.co-modal .rec-grp.co-grp { margin: 20px 0 12px; }
.co-modal .rec-grp.co-grp:first-of-type { margin-top: 0; }
.co-derived { font-size: 0.86rem; padding: 9px 12px; background: var(--surface-subtle); border-radius: var(--radius-sm); color: var(--text-secondary); min-height: 38px; display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.co-derived b { color: var(--text-primary); font-variant-numeric: tabular-nums; }
.co-chk { display: flex; gap: 14px; flex-wrap: wrap; align-items: center; font-size: 0.86rem; padding-top: 6px; }
.co-chk label { display: inline-flex; gap: 6px; align-items: center; }
.co-chk-hint { margin: 0; flex-basis: 100%; }
.co-doc { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; min-height: 38px; padding: 4px 0; }
.co-doc .btn-ghost { padding: 6px 10px; font-size: 0.8rem; }
.co-doc-name { font-size: 0.86rem; color: var(--text-primary); max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.co-doc-hint { font-size: 0.76rem; color: var(--text-tertiary); }
.co-doc-remove { color: var(--red); }
.co-invoice-btn { margin-right: auto; }
.rec-list .co-doc-open { padding: 2px 8px; font-size: 0.74rem; }

/* ── 18c · record links are REAL links (right-click / middle-click open a new tab) ────────────
   The project and inventory names in the tables, the allocation links on a record, the approval
   links, the activity feed and the global search hits carry an href now; a left click still
   behaves exactly as before (the handlers preventDefault). These rules only stop the browser's
   default link colouring from changing how they look. */
a.rec-link { color: inherit; text-decoration: none; }
a.rec-link:hover strong { text-decoration: underline; text-underline-offset: 2px; }
a.gs-hit { text-decoration: none; color: inherit; }
a.approval-proj-link, a.view-alloc-inv-link { color: inherit; }

/* 01e — "this is the bit that is missing". Step 4 refused to advance on all thirteen of
   production's saved proposals (cancellation terms, required since 11y, absent on every proposal
   written before it), and the toast named a section the reader then had to go and find. The
   refusal now scrolls here and marks it for a moment. Movement stops under prefers-reduced-motion;
   the ring does not, because the ring is the message. */
.pb-needs-you {
  border-radius: 8px;
  box-shadow: 0 0 0 3px var(--cerio-blue-tint, rgba(42,107,205,.22)), 0 0 0 1px var(--cerio-blue, #2A6BCD);
  animation: pb-needs-you-in .35s ease-out;
}
@keyframes pb-needs-you-in { from { box-shadow: 0 0 0 10px transparent, 0 0 0 1px transparent; } }
@media (prefers-reduced-motion: reduce) { .pb-needs-you { animation: none; } }

/* 01g — the compiled bill of materials, above the engine sections it produced. Tinted rather than
   warned: a compile is a good outcome, unlike the scratch-mode banner it replaces. */
.pb-compiled-banner {
  margin-bottom: 10px; padding: 8px 10px; border-radius: 8px;
  border: 1px solid var(--cerio-blue, #2A6BCD);
  background: var(--cerio-blue-tint, rgba(42, 107, 205, .10));
}

/* 01m — the cross-check on step 2. Colour carries the state, but never alone: every row says in
   words what it is and why, because a coloured strip with no sentence is a puzzle. */
#pb-bom-checks { margin-top: 12px; }
.pb-chk-head { margin-bottom: 8px; }
.pb-chk-row {
  display: flex; gap: 12px; align-items: flex-start; justify-content: space-between;
  padding: 8px 10px; margin-bottom: 6px; border-radius: 8px;
  border: 1px solid var(--border, #D8D6CE); border-left-width: 4px;
}
.pb-chk-row.pb-chk-open { border-left-color: var(--amber, #C98A14); }
.pb-chk-row.pb-chk-done { border-left-color: var(--green, #2E8B57); opacity: .8; }
.pb-chk-row.pb-chk-ok   { border-left-color: var(--border-strong, #D8D6CE); opacity: .7; }
.pb-chk-main { min-width: 0; }
.pb-chk-acts { display: flex; gap: 6px; flex-shrink: 0; align-items: center; }
.pb-chk-tag {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .04em;
  padding: 1px 6px; border-radius: 999px; border: 1px solid var(--border, #D8D6CE);
  color: var(--text-tertiary, #6b6b6b);
}
.pb-chk-done-tag { font-size: .78rem; color: var(--green, #2E8B57); }
.pb-chk-agree { margin-top: 4px; }
.pb-chk-agree summary { cursor: pointer; font-size: .82rem; color: var(--text-tertiary, #6b6b6b); }
@media (max-width: 640px) { .pb-chk-row { flex-direction: column; gap: 6px; } }

/* 01p — where a line came from, in the REVIEW only. The customer's copy never carries it:
   "a line we added from the record reads exactly like a line the vendor quoted, because telling a
   customer which of your own statements you are less sure of is not information they can use." */
.pb-origin-badge {
  font-size: .7rem; text-transform: uppercase; letter-spacing: .04em;
  padding: 1px 6px; margin-left: 6px; border-radius: 999px;
  border: 1px solid var(--border-strong, #D8D6CE);
  color: var(--text-tertiary, #6b6b6b); white-space: nowrap;
}


/* 01s — the proposal agent on step 2. A conversation, so it reads as one: the rep's turns and the
   agent's are visibly different, and what the agent DID is listed under what it said. An agent
   that edits a customer's proposal while talking about something else is worse than one that
   cannot edit at all. */
.pb-agent {
  margin-top: 14px; padding: 10px 12px; border-radius: 10px;
  border: 1px solid var(--border, #D8D6CE);
}
.pb-agent-head { margin-bottom: 8px; }
.pb-agent-head .pb-hint { display: block; margin-top: 2px; }
.pb-agent-log { max-height: 320px; overflow-y: auto; margin-bottom: 10px; }
.pb-agent-turn { padding: 8px 10px; border-radius: 8px; margin-bottom: 6px; }
.pb-agent-turn strong { font-size: .74rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-tertiary, #6b6b6b); display: block; margin-bottom: 2px; }
.pb-agent-you {
  /* 02q — was var(--surface-2), which is not a variable this stylesheet defines, so it always
     used the cream literal and in dark mode put light text on it. Both properties are set from
     real tokens now: a background without a matching colour is half a theme. */
  background: var(--surface-subtle);
  color: var(--text-primary);
}
.pb-agent-it  { border-left: 3px solid var(--cerio-blue, #2A6BCD); }
.pb-agent-did, .pb-agent-not { margin: 6px 0 0; padding-left: 18px; font-size: .86rem; }
.pb-agent-did li { color: var(--green, #2E8B57); }
.pb-agent-not li { color: var(--text-tertiary, #6b6b6b); }
.pb-agent-record {
  margin-top: 8px; padding: 8px 10px; border-radius: 8px;
  border: 1px solid var(--amber, #C98A14);
}
.pb-agent-record ul { margin: 4px 0 0; padding-left: 18px; }
.pb-agent-ask { display: flex; gap: 8px; align-items: flex-start; }
.pb-agent-ask textarea { flex: 1; resize: vertical; }
@media (max-width: 640px) { .pb-agent-ask { flex-direction: column; } .pb-agent-ask button { width: 100%; } }


/* 02g — the cross-check's action buttons. They borrowed .pb-mini-btn, which is a 38x38 SQUARE
   built for the "+" beside a dropdown, so every text label wrapped one character per line and
   overlapped the row above. A text button needs to be as wide as its text. */
.pb-chk-btn {
  flex: 0 0 auto; white-space: nowrap;
  padding: 6px 12px; border-radius: 8px;
  border: 0.5px solid var(--border-default, #D8D6CE);
  background: var(--surface-subtle, #F3F1EA);
  font-size: .82rem; line-height: 1.2; cursor: pointer;
}
.pb-chk-btn:hover { border-color: var(--cerio-blue, #2A6BCD); color: var(--cerio-blue, #2A6BCD); }
.pb-chk-acts { flex-wrap: nowrap; }
.pb-chk-main { flex: 1 1 auto; }
@media (max-width: 760px) {
  .pb-chk-acts { flex-wrap: wrap; justify-content: flex-end; }
}

/* 02m — the section picker on an unplaced line. Sized like .pb-chk-btn beside it so the row reads
   as one control group; nowrap for the same reason the 02g buttons needed it — a text control
   squeezed into an icon-sized box wraps one character per line. */
.pb-chk-sel {
  flex: 0 1 auto; max-width: 220px;
  padding: 6px 10px; border-radius: 8px;
  border: 0.5px solid var(--border-strong, #D8D6CE);
  background: var(--surface-card, #FFFFFF); color: inherit;
  font-family: inherit; font-size: .82rem; line-height: 1.2; cursor: pointer;
}
.pb-chk-sel:focus { outline: 2px solid var(--cerio-blue, #012BBF); outline-offset: 1px; }
.pb-unmapped .pb-chk-row { align-items: flex-start; }
@media (max-width: 760px) { .pb-chk-sel { max-width: 100%; } }

/* ══ 02n — THE BILL OF MATERIALS REVIEW (mockup A) ═════════════════════════════════════════
   Chris picked A as the review screen with C as a mode on it. A compiled bill used to be drawn
   by the TEMPLATE renderer — canonical skeleton, frozen-section banner, override dropdowns —
   none of which describes a bill an agent compiled from a document. See js/bom-review.js. */
.bomr-top { display:flex; justify-content:space-between; align-items:center; gap:12px;
  flex-wrap:wrap; padding:12px 14px; margin-bottom:10px;
  border:1px solid var(--border-default); border-radius:var(--radius-sm, 10px);
  background:var(--surface-subtle); }
.bomr-grp { margin-bottom:14px; }
.bomr-grphead { display:flex; justify-content:space-between; align-items:baseline; gap:10px;
  padding-bottom:5px; margin-bottom:2px; border-bottom:1px solid var(--border-strong); }
.bomr-grpname { font-size:.93rem; font-weight:500; }
.bomr-chip { font-size:.64rem; text-transform:uppercase; letter-spacing:.05em; padding:1px 7px;
  border-radius:999px; border:1px solid var(--amber); color:var(--amber); white-space:nowrap; }

/* A line. The action buttons stay hidden until the row is hovered or focused — 58 rows each
   showing two buttons is the noise the old screen was made of. Keyboard users get them on
   focus-within, so hover is never the only way to reach them. */
.bomr-line { display:grid; grid-template-columns:20px 1fr auto 118px; gap:10px;
  align-items:start; padding:6px 8px; border-radius:7px; font-size:.88rem; }
.bomr-line:hover { background:var(--surface-subtle); }
.bomr-line:hover .bomr-acts, .bomr-line:focus-within .bomr-acts { visibility:visible; }
.bomr-line.bomr-editing { background:var(--cerio-blue-tint); }
.bomr-mark { color:var(--green); font-size:.8rem; line-height:1.6; }
.bomr-mark.bomr-edited { color:var(--amber); }
.bomr-main { min-width:0; overflow-wrap:anywhere; }
.bomr-acts { visibility:hidden; display:flex; gap:5px; }
.bomr-sm { padding:3px 9px; font-size:.76rem; }
.bomr-origin { font-size:.72rem; color:var(--text-tertiary); text-align:right; line-height:1.7; }
.bomr-quote { font-size:.78rem; font-style:italic; color:var(--text-tertiary);
  border-left:2px solid var(--border-strong); padding-left:9px; margin-top:4px; }
.bomr-input { width:100%; padding:7px 10px; border-radius:8px; font-family:inherit;
  font-size:.88rem; color:var(--text-primary); background:var(--surface-card);
  border:1px solid var(--cerio-blue); }
.bomr-editacts { display:flex; gap:6px; align-items:center; flex-wrap:wrap; margin-top:7px; }
.bomr-add { padding:4px 8px 0 30px; }

@media (max-width: 760px) {
  .bomr-line { grid-template-columns:20px 1fr; }
  .bomr-acts { visibility:visible; grid-column:2; }
  .bomr-origin { grid-column:2; text-align:left; }
  .bomr-add { padding-left:8px; }
}
.bomr-grpacts { display:flex; gap:8px; align-items:baseline; }
.bomr-grpacts .pb-chk-btn { visibility:hidden; }
.bomr-grp:hover .bomr-grpacts .pb-chk-btn,
.bomr-grp:focus-within .bomr-grpacts .pb-chk-btn { visibility:visible; }

/* 02p — the only other state the bill of materials has now: nothing compiled yet. */
.bomr-empty { text-align:center; padding:34px 20px; border-radius:var(--radius, 14px);
  border:1px dashed var(--border-strong); background:var(--surface-subtle); }
.bomr-empty-h { font-family:var(--font-display); font-size:1.05rem; font-weight:500; margin-bottom:6px; }
.bomr-empty .pb-hint { max-width:62ch; margin:0 auto 14px; }
/* 02q — the agent said it would close a cross-check item it has no power to close. The reply is
   constrained in the prompt, and a turn that changed nothing now says so on screen, because a
   prompt rule can be argued with and a visible contradiction cannot. */
.pb-agent-none { font-size: .82rem; color: var(--text-tertiary); margin-top: 6px; font-style: italic; }

/* ══ 02s — WHICH LINES ARE NOT THE SUPPLIER'S WORDS ════════════════════════════════════════
   Chris: "I would like to be able to see where the item is added by using a colored flag like we
   have to the left of these items. That way I can scroll up and easily see where that addition
   was placed."
   The flag is permanent, matching the cross-check's own left-edge colouring, because a line the
   supplier never wrote stays worth knowing about long after the scroll. The flash is the
   momentary half and removes itself. */
.bomr-line.bomr-from-record { border-left: 3px solid var(--green); padding-left: 10px; }
.bomr-line.bomr-from-you    { border-left: 3px solid var(--amber); padding-left: 10px; }
.bomr-line.bomr-from-record .bomr-origin { color: var(--green); }
.bomr-line.bomr-from-you    .bomr-origin { color: var(--amber); }
.bomr-line.bomr-flash { background: var(--cerio-blue-tint); transition: background .35s ease; }
@media (prefers-reduced-motion: reduce) { .bomr-line.bomr-flash { transition: none; } }

/* ══ 02t — LEFT OUT OF THIS PROPOSAL ═══════════════════════════════════════════════════════
   Chris: "I'd like to see a section showing everything that was excluded from the BOM section
   just so we have a reference and clear picture of what we're deciding to leave out."
   Grouped by REASON, not by heading: the question it answers is "what are we leaving out and
   why", not "where did it used to live". Deliberately quiet — a reference, not an alarm.
   Nothing in it is wrong; it is the record of decisions already made. */
.bomr-left { margin-top: 18px; padding: 14px 16px; border-radius: var(--radius-sm, 10px);
  border: 1px dashed var(--border-strong); background: var(--surface-subtle); }
.bomr-left-h { font-size: .95rem; font-weight: 500; margin-bottom: 2px;
  display: flex; gap: 8px; align-items: baseline; }
.bomr-left-grp { margin-top: 10px; }
.bomr-left-why { font-size: .7rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-tertiary); margin-bottom: 4px; }
.bomr-left-row { font-size: .86rem; padding: 3px 0 3px 12px;
  border-left: 2px solid var(--border-strong); color: var(--text-secondary); }

/* ══ 02z — THE QUESTIONS, ABOVE THE BILL ═══════════════════════════════════════════════════
   Chris: "if the ai agent has to ask me questions before it fully generates the BOM, that is ok.
   id rather the user clarify than it guess and me have to look for assumptions or errors it made."
   Above the bill, not below it: they are the reason to doubt what is underneath. Coloured as a
   prompt rather than an alarm — nothing here is wrong, it is undecided, and an unanswered one is
   a stated assumption rather than a blocker. */
.bomr-qs { margin: 0 0 16px; padding: 14px 16px; border-radius: var(--radius-sm, 10px);
  border: 1px solid var(--cerio-blue); background: var(--cerio-blue-tint); }
.bomr-qs-h { font-family: var(--font-display); font-size: 1rem; font-weight: 500; margin-bottom: 2px; }
.bomr-q { padding: 10px 0; border-top: 1px solid var(--border-default); }
.bomr-q:first-of-type { border-top: 0; }
.bomr-q-text { font-size: .9rem; font-weight: 500; margin-bottom: 2px; }
.bomr-q-assumed { font-size: .8rem; color: var(--amber); margin: 4px 0 6px; }
.bomr-q-opts { display: flex; gap: 6px; flex-wrap: wrap; margin: 6px 0; }
.bomr-q-acts { display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
  margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border-default); }
.bomr-q-done .bomr-q-text::after { content: ' \2713'; color: var(--green); }
.bomr-q-done { opacity: .72; }
/* 02za — what has already been settled. Folded: reassurance, not work. */
.bomr-q-log { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border-default); }
.bomr-q-log summary { cursor: pointer; font-size: .82rem; color: var(--text-tertiary); }
.bomr-q-logrow { margin: 8px 0 0; padding-left: 12px; border-left: 2px solid var(--green); }
.bomr-q-logq { font-size: .84rem; }
.bomr-q-loga { font-size: .84rem; color: var(--green); }
/* 02zb — the inline panel is a SUMMARY now; the questions are answered in the modal. */
.bomr-q-sum { font-size: .88rem; padding: 7px 0 7px 12px; margin-top: 4px;
  border-left: 2px solid var(--cerio-blue); }

/* 02zc — the rebuild banner, at the top of the bill. It goes where the RESULT will appear, which
   is where somebody waiting for it is already looking — not on the button they pressed, which in
   the modal case dismisses itself on the same click. */
.bomr-rebuilding { margin: 0 0 12px; padding: 11px 14px; border-radius: var(--radius-sm, 10px);
  border: 1px solid var(--cerio-blue); background: var(--cerio-blue-tint);
  color: var(--cerio-blue); font-size: .9rem; font-variant-numeric: tabular-nums; }

/* ══ 02zd — THE STAGED DOCUMENTS ═══════════════════════════════════════════════════════════
   Chris: "Can we have a button that we have to click for the compiler to start running? That way
   we can do multiple uploads." Choosing files and compiling are two actions now, so the ones
   waiting to be compiled need somewhere to sit and be counted. */
.pb-staged { margin: 10px 0 0; text-align: left; }
.pb-staged-row { display: flex; align-items: center; gap: 10px; padding: 6px 10px;
  border: 1px solid var(--border-default); border-radius: var(--radius-sm, 8px); margin-bottom: 6px;
  background: var(--surface-card); }
.pb-staged-name { flex: 1 1 auto; font-size: .86rem; overflow-wrap: anywhere; }
.pb-staged-size { flex: 0 0 auto; font-size: .78rem; color: var(--text-tertiary);
  font-variant-numeric: tabular-nums; }

/* ══ 05g — THE DASHBOARD'S NEW PROJECTS TILE (js/dash-tiles.js; option B of the mockups) ══ */
.dash-new-projects .task-row { padding: 5px 0; }
#dash-np-list { max-height: 312px; overflow-y: auto; }
.np-day { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; font-weight: 600;
  color: var(--text-tertiary); padding: 10px 0 2px; }
.np-day:first-child { padding-top: 0; }
.np-when { font-size: .76rem; color: var(--text-tertiary); white-space: nowrap; }
.np-right { margin-left: auto; display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; flex-shrink: 0; }
.np-div { display: inline-flex; align-items: center; gap: 5px; }
.np-div .rec-divtab-dot { width: 6px; height: 6px; margin-right: 0; }
/* 06a — My Assigned Projects: a full-width short table under Tasks + New Projects (option B). The
   two-class selector outranks the span-12 .dash-tasks rule inside the wide media query. */
.dash-bento > .dash-tasks.dash-my-assigned { grid-column: span 24; }
.ma-table-wrap { overflow-x: auto; }
.ma-table { width: 100%; border-collapse: collapse; font-size: .84rem; }
.ma-table th { text-align: left; font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; font-weight: 600;
  color: var(--text-tertiary); padding: 6px 10px; border-bottom: 1px solid var(--border-default); white-space: nowrap; }
.ma-table td { padding: 8px 10px; border-bottom: 0.5px solid var(--border-default); vertical-align: middle; }
.ma-table tr:last-child td { border-bottom: 0; }
.ma-table .ma-c { text-align: center; }
.ma-link { color: var(--cerio-blue); font-weight: 500; }
.ma-stages { display: inline-flex; gap: 4px; flex-wrap: wrap; align-items: center; }
.ma-due { white-space: nowrap; font-weight: 600; color: var(--text-primary); }
.ma-due.soon, .ma-due.past { color: var(--sev-high-text); }
.ma-due small { font-weight: 500; font-size: .72rem; }
.ma-alert { display: inline-block; min-width: 18px; padding: 0 6px; border-radius: 99px; font-size: .7rem; font-weight: 700;
  text-align: center; background: var(--sev-high-bg); color: var(--sev-high-text); }
.ma-none { color: var(--text-tertiary); }
.ma-closed-row td { opacity: .6; }
.ma-toggle { font-size: .76rem; color: var(--cerio-blue); background: none; border: 0; padding: 0; cursor: pointer; font-family: inherit; }
.np-link { color: var(--cerio-blue); text-decoration: none; }
.np-link:hover { text-decoration: underline; }

/* ══ 05h — POSSIBLE DUPLICATE: the amber family (Supporting Items'), a question for a person.
   Chip, Alerts-page card and schedule row tint, light and dark; the attention chip; the form's
   warning and the record's decision block. ══ */
.attn-duplicate { background: var(--attn-supporting, var(--attn-unallocated)); }
.alert-summary-chip.chip-duplicate { background:#FBEFD6; border-color:#EBCB8B; color:#6B4A12; }
.conflict-item.alert-type-duplicate { background:#FBF1DE; border-color:#EBCB8B; }
[data-theme="dark"] .conflict-item.alert-type-duplicate { background:#33260F; border-color:#5C4418; }
.schedule-table tbody tr.sched-alert-duplicate td,
.schedule-table tbody tr.sched-alert-duplicate th.sched-proj   { background:#FBF1DE; }
.schedule-table tbody tr.sched-alert-duplicate td.has-units    { background:#F2DDAE; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-duplicate td,
[data-theme="dark"] .schedule-table tbody tr.sched-alert-duplicate th.sched-proj { background:#33260F; }
[data-theme="dark"] .schedule-table tbody tr.sched-alert-duplicate td.has-units  { background:#5C4418; }
.dup-why { font-size: .78rem; color: var(--text-secondary); margin-top: 2px; }
.dup-why b { font-weight: 600; }
.dup-warning .task-row { padding: 8px 0; }
.dup-block .task-row { padding: 8px 0; }

/* ══ ATTACHMENTS BY SECTION (2026-10-16) ═══════════════════════════════════════════════════
   Chris chose variant A of four renderings: "I would also like to see what it looks like
   without all the additional lines/boxes around the attachments section. I think a simple list
   would be OK." So: no frame, no dividers, no dropzone border. A small section title, then a
   plain list. He also rejected the one-line variant by name — "You can hardly read any of the
   notes there" — so THE NOTE GOES UNDER THE NAME and wraps. */
.att-head {
  font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.09em;
  color: var(--cerio-blue); margin: 14px 0 2px; padding-top: 12px;
  border-top: 1px dashed var(--border-default);
}
.att-row { display: flex; align-items: flex-start; gap: 9px; padding: 5px 0; min-width: 0; }
.att-open {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px;
  text-decoration: none; color: inherit;
}
.att-name { font-size: 0.85rem; font-weight: 500; color: var(--cerio-blue); word-break: break-word; }
.att-open:hover .att-name { text-decoration: underline; }
.att-note { font-size: 0.78rem; color: var(--text-tertiary); line-height: 1.35; }
.att-also { font-size: 0.72rem; color: var(--text-tertiary); white-space: nowrap; flex-shrink: 0; margin-top: 2px; }
.att-kb   { font-size: 0.75rem; color: var(--text-tertiary); flex-shrink: 0; margin-top: 2px; }
.att-dl   { flex-shrink: 0; margin-top: 3px; color: var(--text-tertiary); }
.att-row .action-btn { flex-shrink: 0; }
/* ★ 2026-10-19 (Chris, with a phone screenshot): "With the new way we are handling attachments,
   mobile is not formatting correctly." The filename was wrapping ONE CHARACTER PER LINE.

   Every other child of .att-row is flex-shrink:0 — the chips, the size, the three icons — and
   .att-open is `flex: 1`, i.e. flex-basis 0 with nothing holding it open. So the name column gets
   whatever is left after the furniture, and on a phone that is nothing. MEASURED on the deployed
   build with a ONE-chip row: 334px of name at 640, 254 at 560, 124 at 430, and 84px at 390 —
   three lines for a short name. Chris's row carries TWO chips (PRODUCT DETAILS + SALES, ~135px
   together), which is why his looked far worse than this arithmetic suggests.

   So below 560 the row wraps and the name takes a line of its own, with the chips, the size and
   the actions beneath it. 560 rather than 640 because at 560 the name still has 254px and reads
   fine on one line — wrapping a row that fits is its own kind of wrong.

   THREE LINES, EACH ONE DELIBERATE: the name, then the chips, then the size and the controls.
   It took two tries to get here and both failures are worth keeping:
     · First I pinned the controls right with margin-left:auto on .att-dl. The auto margin ate the
       free space and pushed the tag and delete buttons onto two further lines of their own.
     · Removing it changed almost nothing, because the real constraint is that .action-btn is a
       40x40 TOUCH TARGET (min-width:40px). MEASURED child by child at 390px: the row is 308px
       wide and chips 135 + size 41 + download 13 + two 40px buttons + four 9px gaps comes to
       ~317. It overflows by about ten pixels, so the last button wraps — and with "Project
       Management" instead of "Sales" it would be worse. A layout that fits by ten pixels is not
       a layout that fits.
   So the chips take a line of their own (flex-basis 100%) and the controls get a line to
   themselves with room to spare, centred rather than top-aligned so a 13px icon and a 40px
   button sit on one axis. The touch targets stay 40px: they are what makes this usable on the
   device that reported the bug. */
@media (max-width: 640px) { .att-also { display: none; } }   /* the chips already say it */
@media (max-width: 560px) {
  .att-row { flex-wrap: wrap; row-gap: 3px; padding-bottom: 8px; align-items: center; }
  .att-open { flex: 1 1 100%; }
  .att-row .file-cats { flex: 1 1 100%; flex-wrap: wrap; row-gap: 4px; }
  .att-kb, .att-dl { margin-top: 0; }
}

/* Untagged: 74 of production's 172 attachments land here on day one, so it is a real part of
   the page and not an edge case. Tinted like a "needs attention" row, never like an error. */
.att-untagged {
  background: var(--sev-med-bg); border: 1px solid var(--sev-med-border);
  border-radius: 6px; padding: 10px 12px; margin-bottom: 10px;
}
.att-untagged-h {
  font-family: var(--font-display); font-weight: 700; font-size: 0.8rem;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--sev-med-text);
  display: flex; align-items: center; gap: 8px;
}
.att-count {
  font-size: 0.68rem; padding: 1px 7px; border-radius: 99px;
  background: var(--sev-med-text); color: var(--sev-med-bg); font-weight: 700;
}
.att-untagged-p { font-size: 0.82rem; color: var(--sev-med-text); opacity: 0.9; margin: 5px 0 7px; }
.att-untagged .att-name { color: var(--sev-med-text); }

/* The category dialog's note field and its "pick one" line. */
.fc-note { margin-top: 12px; }
.fc-note-label {
  display: block; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 4px;
}
.fc-need { margin-top: 9px; font-size: 0.8rem; color: var(--red); font-weight: 500; }

/* ── DELIVERY & INVENTORY: one section, one background (2026-10-16) ───────────────────────────
   Chris: "why is the supporting item a different color?" Because the Delivery/Inventory pair was
   overridden to --surface-page by the rule above while .supp-block kept the standard
   --surface-card, and the two sat side by side. Inside the merged section both are flattened to
   the section's own ground, so the question cannot arise again. */
[data-di-slot] > .rec-pair { margin: 0; }
[data-di-slot] > .rec-pair > .view-section,
[data-di-slot] > .supp-block {
  background: transparent; border: 0; box-shadow: none; padding: 0;
}
[data-di-slot] > .supp-block { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border-default); }
[data-di-slot] > .rec-pair > .view-section > .view-section-title,
[data-di-slot] > .supp-block > .view-section-title {
  color: var(--text-tertiary); font-size: 0.7rem;
}

/* The supporting-item pool name is a link to that item's record (Chris, 2026-10-16). Styled like
   the genset allocation links beside it, which carry inline styles from main.js — this rule gives
   the supporting rows the same look without repeating them per element. */
.supp-pool-name .view-alloc-inv-link {
  color: var(--cerio-blue); text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
}

/* ══ REASON FOR DE-ALLOCATION (2026-10-16) ══════════════════════════════════════════════════
   Chris: the prompt always appears, the comment is optional. It REPLACES the bare "Remove this
   allocation?" confirmation rather than following it — two dialogs to remove one row would be
   worse than the thing it improves. */
.dr-what {
  padding: 11px 13px; border-radius: 6px;
  background: var(--surface-subtle); border: 1px solid var(--border-default);
  margin-bottom: 15px;
}
.dr-item { font-weight: 700; font-size: 0.95rem; }
.dr-sub  { color: var(--text-secondary); font-size: 0.85rem; margin-top: 2px; }
.dr-many { color: var(--text-tertiary); font-size: 0.8rem; margin-top: 4px; }
.dr-label {
  display: block; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 5px;
}
.dr-input { width: 100%; resize: vertical; line-height: 1.45; }
.dr-foot { color: var(--text-tertiary); font-size: 0.8rem; margin-top: 8px; line-height: 1.45; }

/* Who uploaded it and when (Chris, 2026-10-16). A step quieter than the note: the note is what
   somebody chose to say about the file, the provenance is a fact about it. Same wording shape
   the Change History uses, so an upload and the history row it produces read alike. */
.att-who { font-size: 0.74rem; color: var(--text-faint); }

/* The proposal block now lives INSIDE the Sales section (Chris, 2026-10-16: "this proposal
   section should be embedded within the sales section of the machine record"), so it loses its
   own card frame — a card inside a card reads as two things, and it is one. Same treatment the
   merged Delivery & Inventory sub-blocks get, and the same reason. */
details.rec-sec[data-rec-sec="sales"] > .rec-sec-body > [data-prop-section] {
  background: transparent; border: 0; box-shadow: none; padding: 0;
  margin: 16px 0 0; padding-top: 14px; border-top: 1px solid var(--border-default);
}
details.rec-sec[data-rec-sec="sales"] > .rec-sec-body > [data-prop-section] > .view-section-title {
  color: var(--text-tertiary); font-size: 0.7rem;
}

/* ── 16f — PROJECT TRACKER: the mode switch, and the Gantt's tick boxes ──────────────────────
   Chris (2026-10-16) on the Gantt: "You can't completely remove the existing At a Glance
   tracker." It is a SWITCH, not a replacement — At a glance keeps the default. The legend and
   footnote classes below also replace the inline styles the lane view used to carry, so both
   modes wear the same furniture. */
.trk-modes { display: flex; gap: 6px; margin-left: auto; }
.trk-mode {
  font-family: var(--font-body); font-size: 0.78rem; padding: 5px 13px; border-radius: 99px;
  border: 1px solid var(--border-strong); background: var(--surface-card);
  color: var(--text-secondary); cursor: pointer;
}
.trk-mode:hover { background: var(--cerio-blue-tint); }
.trk-mode.trk-on {
  background: var(--cerio-blue); border-color: var(--cerio-blue); color: #fff; font-weight: 600;
}
.trk-legend {
  display: flex; gap: 16px; flex-wrap: wrap; align-items: center; margin-top: 8px;
  font-size: 0.72rem; color: var(--text-tertiary);
}
.trk-key {
  display: inline-block; width: 10px; height: 10px; border-radius: 2px;
  margin-right: 4px; vertical-align: -1px;
}
.trk-legend-note { margin-left: auto; font-style: italic; }
.trk-foot {
  font-size: 0.75rem; color: var(--text-tertiary); line-height: 1.55; margin-top: 10px;
  padding-top: 10px; border-top: 1px solid var(--border-soft);
}
.trk-foot b { color: var(--text-secondary); }

/* Chris: "all machines with the ability to check off any combination of what we see on the
   screen at any one time." Machines on one row, sections on the next, and the empty-date rows
   as their own box so hiding them is never mistaken for hiding a section. */
.gnt-filters {
  background: var(--surface-subtle); border: 1px solid var(--border-soft);
  border-radius: 7px; padding: 9px 12px; margin-bottom: 12px;
}
.gnt-frow { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 3px 0; }
.gnt-flab {
  flex: 0 0 72px; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-tertiary);
}
.gnt-chk {
  display: inline-flex; align-items: center; gap: 5px; font-size: 0.79rem;
  color: var(--text-secondary); cursor: pointer;
}
.gnt-chk input { accent-color: var(--cerio-blue); cursor: pointer; margin: 0; }
.gnt-chk:last-child span { font-style: italic; color: var(--text-tertiary); }
.gnt-fsep { flex: 1; }
.gnt-all {
  font-size: 0.72rem; padding: 2px 9px; border-radius: 99px;
  border: 1px solid var(--border-default); background: var(--surface-card);
  color: var(--text-tertiary); cursor: pointer;
}
.gnt-all:hover { background: var(--cerio-blue-tint); color: var(--cerio-blue); }
.gnt-count { font-size: 0.72rem; color: var(--text-tertiary); padding-top: 5px; font-style: italic; }
@media (max-width: 720px) {
  .gnt-flab { flex-basis: 100%; }
  .trk-modes { margin-left: 0; }
}

/* ── 16j — the attachment list inside a record section folds ────────────────────────────────
   Chris: "i would also like the arrow on each of the attachment sections (within the finance,
   product, sales, etc sections) to allow us to collapse or expand the view of attachments."
   The same ▸/▾ the record's own sections use, one level in, so the arrow means one thing on
   this page. .att-head keeps its dashed rule and spacing; it only becomes a summary. */
.att-fold { margin: 0; }
.att-fold > summary.att-head {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: 8px;
  user-select: none;
}
.att-fold > summary.att-head::-webkit-details-marker { display: none; }
.att-fold > summary.att-head::before { content: '▸'; font-size: 0.8rem; flex-shrink: 0; }
.att-fold[open] > summary.att-head::before { content: '▾'; }
.att-fold > summary.att-head:focus-visible { outline: 2px solid var(--cerio-blue); outline-offset: 2px; border-radius: 4px; }
/* The count sits on the header so a COLLAPSED section still says how many files are inside.
   .att-count is the Untagged box's chip, which is tinted for "needs attention"; here it is a
   neutral tally, so it wears the section's own blue. */
.att-fold > summary.att-head .att-count {
  background: var(--cerio-blue-tint); color: var(--cerio-blue);
}
.att-fold-body { padding-top: 2px; }

/* ── 16q — the margin target on the pricing sheet ────────────────────────────────────────────
   Chris: "if the blended margin falls at or below 14.99%, we should get a pop up" — then, better:
   "I'm not sure we need to pop up if we can record notes in the body of the pricing sheet…
   Can't we just use the note section that's already there? Except just make it required."
   So there is no dialog and no second box: the notes field that already says "the reasoning
   behind this price" wears these while the margin is at or below the division's minimum. */
.qb-notes-req > .qb-label { color: var(--red); }
.qb-notes-req > textarea { border-color: var(--red); }
.qb-notes-req > textarea:focus {
  border-color: var(--cerio-blue); box-shadow: 0 0 0 3px var(--cerio-blue-tint);
}
.qb-req {
  background: var(--red); color: #fff; font-size: 0.6rem; font-weight: 700;
  letter-spacing: 0.07em; text-transform: uppercase; padding: 2px 6px;
  border-radius: 99px; margin-left: 7px; vertical-align: 1px;
}
.qb-hint-req { color: var(--red); font-weight: 600; }
/* The existing .qb-warn is the line that already says "Margin must be between 0 and 99.99" —
   the below-target message rides in it rather than inventing a second kind of warning. Green
   once the reason is there, so the sheet says it is releasable instead of only saying it is not. */
.qb-warn-ok { color: var(--green); }
.qb-q {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; border-radius: 50%; padding: 0;
  border: 1px solid var(--cerio-blue); background: transparent; color: var(--cerio-blue);
  font-size: 0.68rem; font-weight: 700; margin-left: 6px; vertical-align: 1px; cursor: help;
}
.qb-q:hover { background: var(--cerio-blue); color: #fff; }

/* 16q — the division's minimum margin, on its own tab in Admin › Division Fields. A property of
   the DIVISION, not a registry row, so it sits above the table and looks like a setting. */
.df-margin-target {
  background: var(--surface-subtle); border: 1px solid var(--border-soft);
  border-radius: 7px; padding: 11px 14px; margin-bottom: 12px;
}
.df-mt-label {
  display: block; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 7px;
}
.df-mt-row { display: flex; align-items: center; gap: 8px; }
.df-mt-in { width: 90px; text-align: right; }
.df-mt-pct { color: var(--text-secondary); font-size: 0.85rem; margin-right: 6px; }
.df-mt-hint { font-size: 0.78rem; color: var(--text-tertiary); line-height: 1.5; margin-top: 8px; }
.df-mt-hint b { color: var(--text-secondary); }
