/* ============================================================================
 * luminate-theme.css — the GTM design system, re-created for the app.
 *
 * NEVER LOADED ON THE NLE PROFILE. base.html emits its <link> inside
 * `{% if not is_nle_profile %}`, after main.css, so NLE's documents are byte-identical
 * to before and never fetch this file. Nothing here may be relied on by a template
 * that the nle profile renders.
 *
 * TWO SCOPES, because two profiles share the generic Finance Hub:
 *   • Everything the Finance Hub DRAWS with (the GTM design tokens and the `.lum-*`
 *     components) is defined on :root, so both the generic profile and the Momentis
 *     DEMO get working cards, Gantt lanes and charts.
 *   • Everything that RE-SKINS THE APP (the `--color-*` remap and the brand-cyan /
 *     cool-slate utility remaps) is scoped to `:root[data-lum-chrome]`, an attribute
 *     base.html stamps for the generic profile ONLY. The demo instance has its own
 *     locked identity (html[data-brand="momentis"] in main.css) and keeps every bit
 *     of it. Deviation from the original brief, which said the demo should not get
 *     this file at all: it renders the same finance templates, so withholding the
 *     file entirely would have shipped it unstyled charts.
 *
 * Two jobs:
 *   1. TOKEN REMAP — re-point the app's own `--color-*` custom properties at the
 *      GTM warm-neutral palette so the ENTIRE app chrome (sidebar, cards, tables,
 *      inputs, toasts) shifts without editing a single NLE-shared template. The
 *      hardcoded brand-cyan utility classes (#00B4E6 / solar-*) are re-pointed at
 *      `--color-accent` with the same rules main.css applies under
 *      [data-accent="custom"], so a generic instance is copper-consistent whether
 *      or not an admin has set an accent in Settings -> General. When the admin HAS
 *      set one, the inline #brand-accent <style> in <head> loads after this file at
 *      equal-or-higher specificity and wins — which is correct, and it already
 *      agrees (locked brand accent is copper #C87A2F).
 *   2. GTM COMPONENTS — the `lum-` prefixed card / KPI / Gantt / bar-chart / SVG
 *      chart classes the Finance Hub templates draw with. Prefixed to guarantee no
 *      collision with Tailwind utilities or main.css.
 *
 * Theme mechanism: <html data-theme="light|dark">, stamped by the inline script in
 * base.html from localStorage `nle-theme`. Light tokens live on the unsuffixed selector
 * (this file loads after main.css, so at equal specificity it wins in both themes); the
 * dark set adds [data-theme="dark"] so it beats them. Every --color-* override carries
 * [data-lum-chrome], which is a higher specificity than main.css's own [data-theme=light]
 * token block — that is what makes the remap stick without !important.
 *
 * GTM's amber accent is RETIRED. The locked brand is COPPER.
 * Reference: Momentis - AI Folder/05_Website/momentis-web/src/app/demo/
 *            luminate-gtm-7kx4/gtm.css (tokens, chart + table patterns).
 * ========================================================================== */

/* ─────────── 0. DISPLAY FACE — Space Grotesk (Luminate chrome only) ───────────
   Declared here (not main.css) so no rebuild is needed and no other profile
   fetches it. Applied ONLY under [data-lum-chrome]; the Momentis demo loads this
   file but lacks the attribute, so it keeps its own faces. */
@font-face{
  font-family:"Space Grotesk";
  font-style:normal;
  font-weight:300 700;
  font-display:swap;
  src:url("/static/fonts/SpaceGrotesk-Variable.woff2") format("woff2");
}
:root[data-lum-chrome]{
  --font-display:"Space Grotesk","Geist Sans",ui-sans-serif,system-ui,sans-serif;
}

/* PHASE B, step 5 — the blanket element rules that used to live here are GONE.
   Phase A restyled `h1`, `h1.text-lg/.text-base`, `:is(h2,h3).uppercase` and
   `th.uppercase` by element selector because no template carried a type class and
   the alternative was 300 edits. Those templates now carry .t-page-title,
   .t-panel-title, .t-eyebrow and .t-th, defined in main.css and shared by every
   profile, so the element rules are redundant — and worse, they were invisible
   overrides that a reader of the template could not see. The --font-display
   definition above is all that Luminate still needs: it re-points the SAME classes
   at Space Grotesk. That is the whole two-face mechanism now.

   ONE Phase-A rule intentionally survives (below), and it is not the caps lift. */

/* Sentence-case panel titles keep the display face.
   DELIBERATE DEVIATION from "delete every blanket element rule": Phase B migrated
   h1s, caps eyebrows and table headers, but 221 sentence-case h2/h3 panel titles
   were NOT part of any of those passes and still carry bare text-sm/font-semibold
   utility strings. Deleting this rule would drop all 221 off Space Grotesk and back
   onto Geist on the one profile Phase A was built for — a silent visual regression
   with no migration behind it. It stays until a Phase C moves those sites onto
   .t-panel-title; at that point delete this rule in the same commit. */
:root[data-lum-chrome] :is(h2,h3):not(.uppercase){
  font-family:var(--font-display);letter-spacing:-.006em;
}

/* Caps tracking lift for badges/pills/labels still on tracking utilities */
:root[data-lum-chrome] .uppercase.tracking-wider{letter-spacing:.085em}
:root[data-lum-chrome] .uppercase.tracking-wide{letter-spacing:.07em}

/* ─────────────────────────── 1. TOKENS — LIGHT ─────────────────────────── */
:root{
  /* GTM design-system tokens (used by the .lum-* components below) */
  --plane:#F1EEE7; --surface:#FFFFFF; --surface-2:#FAF8F3; --surface-3:#F3F0E9;
  --ink:#1A1714; --ink-2:#57534A; --muted:#8C877B;
  --border:#E5E1D7; --border-strong:#D5CFC1;
  --accent:#C87A2F; --accent-ink:#8F5115;
  --accent-wash:rgba(200,122,47,.12); --accent-line:rgba(200,122,47,.42);
  --res:#2a78d6; --com:#0f9d6f;
  --res-wash:rgba(42,120,214,.12); --com-wash:rgba(15,157,111,.13);
  --res-line:rgba(42,120,214,.42); --com-line:rgba(15,157,111,.42);
  --cash:#16a34a; --cash-ink:#0f7a37;
  --cash-wash:rgba(22,163,74,.14); --cash-line:rgba(22,163,74,.5);
  --good:#0ca30c; --warning:#c98908; --serious:#d9713f; --critical:#d03b3b;
  --good-wash:rgba(12,163,12,.12); --warning-wash:rgba(201,137,8,.14);
  --critical-wash:rgba(208,59,59,.11);
  --shadow:0 1px 2px rgba(26,23,20,.05),0 4px 14px rgba(26,23,20,.05);
  --shadow-lg:0 4px 12px rgba(26,23,20,.08),0 18px 40px rgba(26,23,20,.10);
  --r:10px; --r-sm:7px;
}

/* App chrome remap — GTM warm neutrals in the app's own token names. Generic profile
   only (see the header note); the demo keeps main.css's momentis palette. */
:root[data-lum-chrome]{
  color-scheme:light;
  --color-bg:#F1EEE7;
  --color-surface:#FFFFFF;
  --color-surface-raised:#FAF8F3;
  --color-surface-hover:#F3F0E9;
  --color-border:#E5E1D7;
  --color-border-muted:#D5CFC1;
  --color-divider:#EDE9E0;
  --color-text-primary:#1A1714;
  --color-text-secondary:#3D3934;
  --color-text-muted:#57534A;
  /* GTM's --muted (#8C877B) is 3.6:1 on white and 3.1:1 on the plane — below AA for the
     small helper text the app hangs off --color-text-faint. Darkened to 5.1:1 on white /
     4.4:1 on the plane. The .lum-* components still use --muted directly, where (as in
     gtm.css) it only carries 10-11px labels inside a white card. */
  --color-text-faint:#726D62;
  --color-text-disabled:#B3ADA0;
  --color-accent:#C87A2F;
  --color-accent-hover:#B0691F;
  --color-accent-dim:rgba(200,122,47,.12);
  --color-accent-fg:#fff;   /* ink painted on .btn-accent / accent fills — copper is dark enough for white text */
  /* Text-weight copper (6.3:1 on white). --color-accent is a FILL colour at 3.3:1 — fine
     behind white/near-black text, not fine AS text — so anything that renders the accent
     as type reaches for this instead. */
  --color-accent-ink:#8F5115;
  --color-brand-solar:#C87A2F;
  --sidebar-bg:#FFFFFF;
  --sidebar-border:#E5E1D7;
  --topbar-bg:rgba(241,238,231,.95);
}

/* ─────────────────────────── 2. TOKENS — DARK ──────────────────────────── */
:root[data-theme="dark"]{
  --plane:#111010; --surface:#1B1917; --surface-2:#211E1B; --surface-3:#26221E;
  --ink:#F3EFE8; --ink-2:#B6B0A4; --muted:#8B857A;
  --border:#302C27; --border-strong:#3B362E;
  /* ember highlight #F0A94F is available for a hover/active state */
  --accent:#E8A055; --accent-ink:#E8A055;
  --accent-wash:rgba(232,160,85,.15); --accent-line:rgba(232,160,85,.5);
  --res:#3987e5; --com:#17a97a;
  --res-wash:rgba(57,135,229,.17); --com-wash:rgba(23,169,122,.16);
  --res-line:rgba(57,135,229,.5); --com-line:rgba(23,169,122,.5);
  --cash:#22c55e; --cash-ink:#4ade80;
  --cash-wash:rgba(34,197,94,.18); --cash-line:rgba(34,197,94,.55);
  --good:#2bb42b; --warning:#e6a52b; --serious:#e08655; --critical:#e05656;
  --good-wash:rgba(43,180,43,.16); --warning-wash:rgba(230,165,43,.15);
  --critical-wash:rgba(224,86,86,.15);
  --shadow:0 1px 2px rgba(0,0,0,.4),0 4px 14px rgba(0,0,0,.35);
  --shadow-lg:0 6px 16px rgba(0,0,0,.5),0 24px 48px rgba(0,0,0,.45);
}

:root[data-lum-chrome][data-theme="dark"]{
  color-scheme:dark;
  --color-bg:#111010;
  --color-surface:#1B1917;
  --color-surface-raised:#211E1B;
  --color-surface-hover:#2E2A25;
  --color-border:#302C27;
  --color-border-muted:#4A443B;
  --color-divider:#26221E;
  --color-text-primary:#F3EFE8;
  --color-text-secondary:#D7D1C6;
  --color-text-muted:#B6B0A4;
  --color-text-faint:#9C968A;      /* 6.0:1 on --color-surface */
  --color-text-disabled:#5A554C;
  --color-accent:#E8A055;
  --color-accent-hover:#F0A94F;    /* ember */
  --color-accent-dim:rgba(232,160,85,.15);
  --color-accent-fg:#fff;   /* ink painted on .btn-accent / accent fills — matches the light copper scope */
  /* On a dark plane the accent is already 8:1 — no separate ink rung is needed. */
  --color-accent-ink:#E8A055;
  --color-brand-solar:#E8A055;
  --sidebar-bg:#1B1917;
  --sidebar-border:#302C27;
  --topbar-bg:rgba(17,16,16,.92);
}

/* ───────── 3. Brand-cyan utilities -> the accent token (both themes) ─────────
 * COMPLETE mirror of main.css's [data-accent="custom"] layer, minus the attribute gate,
 * PLUS every remaining cyan-emitting utility main.css compiled but that layer never
 * covered (see §3b). main.css's own copies are more specific and win when an admin HAS
 * set an accent — they resolve to the same var(), so the result is identical; these carry
 * dark mode AND, critically, an instance whose accent_color has never been set.
 *
 * That last case is the whole point: QuoteSettings.accent_color is not seeded, and
 * base.html only stamps data-accent="custom" when brand_accent() is non-blank. A blank accent
 * therefore switches main.css's entire remap OFF, and without this section ~25 cyan
 * variants — every modal input's focus ring, every checkbox, the "In Progress" chip —
 * would render raw cyan on copper chrome. This layer must stay a SUPERSET of main.css's.
 *
 * WHY MIRROR INSTEAD OF REBUILD: main.css is a pre-built Tailwind artifact shared with the
 * nle instance. Rebuilding it would rewrite NLE's stylesheet. Adding the rules here — a
 * file NLE never fetches — is the only zero-risk route.
 *
 * The list below was derived mechanically: every rule in main.css whose declaration emits
 * a brand-cyan literal (#00B4E6 / #7DE3FF / #29D0FF / #0099c8, in hex, `r g b` or
 * `rgba()` form) has a counterpart here. Re-derive it the same way if main.css is rebuilt.
 */

/* --- 3a. verbatim mirror of main.css's [data-accent=custom] layer --- */
:root[data-lum-chrome] .bg-\[\#0099c8\],:root[data-lum-chrome] .bg-\[\#00B4E6\],:root[data-lum-chrome] .bg-solar-400,:root[data-lum-chrome] .bg-solar-500,:root[data-lum-chrome] .checked\:bg-\[\#00B4E6\]:checked{background-color:var(--color-accent)!important}
:root[data-lum-chrome] .hover\:text-\[\#00B4E6\]:hover,:root[data-lum-chrome] .text-\[\#00B4E6\],:root[data-lum-chrome] .text-solar-300,:root[data-lum-chrome] .text-solar-400,:root[data-lum-chrome] .text-solar-500{color:var(--color-accent)!important}
:root[data-lum-chrome] .border-\[\#00B4E6\],:root[data-lum-chrome] .border-solar-500,:root[data-lum-chrome] .focus\:border-\[\#00B4E6\]:focus,:root[data-lum-chrome] .focus\:border-solar-400:focus,:root[data-lum-chrome] .focus\:border-solar-500:focus,:root[data-lum-chrome] .hover\:border-\[\#00B4E6\]:hover{border-color:var(--color-accent)!important}
:root[data-lum-chrome] .focus\:ring-\[\#00B4E6\]:focus,:root[data-lum-chrome] .focus\:ring-solar-500:focus,:root[data-lum-chrome] .ring-\[\#00B4E6\],:root[data-lum-chrome] .ring-solar-400{--tw-ring-color:var(--color-accent)!important}
:root[data-lum-chrome] .accent-\[\#00B4E6\]{accent-color:var(--color-accent)!important}
:root[data-lum-chrome] .hover\:bg-\[\#0099c8\]:hover,:root[data-lum-chrome] .hover\:bg-\[\#29D0FF\]:hover,:root[data-lum-chrome] .hover\:bg-solar-600:hover,:root[data-lum-chrome] .hover\:bg-solar-400:hover{background-color:var(--color-accent-hover)!important}
:root[data-lum-chrome] .hover\:text-\[\#29D0FF\]:hover,:root[data-lum-chrome] .hover\:text-solar-300:hover{color:var(--color-accent-hover)!important}
:root[data-lum-chrome] .bg-\[\#00B4E6\]\/5{background-color:color-mix(in srgb,var(--color-accent) 5%,transparent)!important}
:root[data-lum-chrome] .bg-\[\#00B4E6\]\/10,:root[data-lum-chrome] .bg-solar-400\/10,:root[data-lum-chrome] .bg-solar-500\/10,:root[data-lum-chrome] .hover\:bg-\[\#00B4E6\]\/10:hover{background-color:color-mix(in srgb,var(--color-accent) 10%,transparent)!important}
:root[data-lum-chrome] .bg-\[\#00B4E6\]\/15{background-color:color-mix(in srgb,var(--color-accent) 15%,transparent)!important}
:root[data-lum-chrome] .bg-\[\#00B4E6\]\/20,:root[data-lum-chrome] .bg-solar-500\/20,:root[data-lum-chrome] .hover\:bg-solar-500\/20:hover,:root[data-lum-chrome] .hover\:bg-\[\#00B4E6\]\/20:hover{background-color:color-mix(in srgb,var(--color-accent) 20%,transparent)!important}
:root[data-lum-chrome] .hover\:bg-\[\#00B4E6\]\/25:hover{background-color:color-mix(in srgb,var(--color-accent) 25%,transparent)!important}
:root[data-lum-chrome] .bg-\[\#00B4E6\]\/50{background-color:color-mix(in srgb,var(--color-accent) 50%,transparent)!important}
:root[data-lum-chrome] .hover\:bg-\[\#00B4E6\]\/60:hover{background-color:color-mix(in srgb,var(--color-accent) 60%,transparent)!important}
:root[data-lum-chrome] .hover\:bg-\[\#00B4E6\]\/80:hover{background-color:color-mix(in srgb,var(--color-accent) 80%,transparent)!important}
:root[data-lum-chrome] .border-\[\#00B4E6\]\/15{border-color:color-mix(in srgb,var(--color-accent) 15%,transparent)!important}
:root[data-lum-chrome] .border-\[\#00B4E6\]\/20{border-color:color-mix(in srgb,var(--color-accent) 20%,transparent)!important}
:root[data-lum-chrome] .border-\[\#00B4E6\]\/25{border-color:color-mix(in srgb,var(--color-accent) 25%,transparent)!important}
:root[data-lum-chrome] .border-\[\#00B4E6\]\/30,:root[data-lum-chrome] .border-solar-500\/30{border-color:color-mix(in srgb,var(--color-accent) 30%,transparent)!important}
:root[data-lum-chrome] .border-\[\#00B4E6\]\/40,:root[data-lum-chrome] .border-solar-400\/40{border-color:color-mix(in srgb,var(--color-accent) 40%,transparent)!important}
:root[data-lum-chrome] .border-\[\#00B4E6\]\/50,:root[data-lum-chrome] .hover\:border-\[\#00B4E6\]\/50:hover{border-color:color-mix(in srgb,var(--color-accent) 50%,transparent)!important}
:root[data-lum-chrome] .border-\[\#00B4E6\]\/60,:root[data-lum-chrome] .focus\:border-\[\#00B4E6\]\/60:focus{border-color:color-mix(in srgb,var(--color-accent) 60%,transparent)!important}
:root[data-lum-chrome] .ring-\[\#00B4E6\]\/20{--tw-ring-color:color-mix(in srgb,var(--color-accent) 20%,transparent)!important}
:root[data-lum-chrome] .ring-\[\#00B4E6\]\/30{--tw-ring-color:color-mix(in srgb,var(--color-accent) 30%,transparent)!important}
:root[data-lum-chrome] .ring-\[\#00B4E6\]\/40{--tw-ring-color:color-mix(in srgb,var(--color-accent) 40%,transparent)!important}
:root[data-lum-chrome] .text-\[\#00B4E6\]\/60{color:color-mix(in srgb,var(--color-accent) 60%,transparent)!important}
:root[data-lum-chrome] .text-\[\#00B4E6\]\/70{color:color-mix(in srgb,var(--color-accent) 70%,transparent)!important}
:root[data-lum-chrome] .divide-\[\#00B4E6\]\/10>:not([hidden])~:not([hidden]){border-color:color-mix(in srgb,var(--color-accent) 10%,transparent)!important}

/* --- 3b. cyan utilities main.css COMPILED but its [data-accent=custom] layer never
 *        covered. These are raw cyan on EVERY white-label instance today, accent set or
 *        not — the light-cyan `#7DE3FF` hover text (45 template occurrences), the
 *        group-hover / peer-checked / has-[:checked] variants behind selectable cards and
 *        toggles, and the focus-visible ring. `#7DE3FF` and `#29D0FF` are the tints ABOVE
 *        brand cyan, so they map to --color-accent-hover, which is the lighter rung in
 *        both themes (light: #B0691F is darker — deliberate, a light-mode hover wants MORE
 *        contrast on a white plane, not less; dark: #F0A94F ember). --- */
:root[data-lum-chrome] .text-\[\#7DE3FF\]{color:var(--color-accent-hover)!important}
:root[data-lum-chrome] .hover\:text-\[\#7DE3FF\]:hover{color:var(--color-accent-hover)!important}
:root[data-lum-chrome] .hover\:border-solar-400:hover,:root[data-lum-chrome] .has-\[\:checked\]\:border-\[\#00B4E6\]:has(:checked),:root[data-lum-chrome] .group:hover .group-hover\:border-\[\#00B4E6\]{border-color:var(--color-accent)!important}
:root[data-lum-chrome] .group:hover .group-hover\:text-\[\#00B4E6\]{color:var(--color-accent)!important}
:root[data-lum-chrome] .peer:checked~.peer-checked\:bg-\[\#00B4E6\]{background-color:var(--color-accent)!important}
:root[data-lum-chrome] .ring-solar-500,:root[data-lum-chrome] .focus-visible\:ring-\[\#00B4E6\]:focus-visible{--tw-ring-color:var(--color-accent)!important}
:root[data-lum-chrome] .has-\[\:checked\]\:bg-\[\#00B4E6\]\/10:has(:checked){background-color:color-mix(in srgb,var(--color-accent) 10%,transparent)!important}
:root[data-lum-chrome] .hover\:border-\[\#00B4E6\]\/60:hover{border-color:color-mix(in srgb,var(--color-accent) 60%,transparent)!important}
:root[data-lum-chrome] .hover\:border-\[\#00B4E6\]\/70:hover{border-color:color-mix(in srgb,var(--color-accent) 70%,transparent)!important}
:root[data-lum-chrome] .border-l-\[\#00B4E6\]\/40{border-left-color:color-mix(in srgb,var(--color-accent) 40%,transparent)!important}
/* --- 3c. NEWLY COMPILED by the Typography Phase B rebuild (2026-08-26). main.css was a
 *        stale artifact; rebuilding it from src/input.css picked up arbitrary values that
 *        templates had been using against a sheet that never contained them. Two of the
 *        new arrivals paint brand cyan, so §3's mirror has to grow with them.
 *        • .border-l-[#00B4E6]  — change_orders/co_view.html callout rail. Raw cyan: it
 *          genuinely rendered wrong the moment it started compiling.
 *        • .bg-[--color-accent,#00B4E6] — change_orders/co_form.html "active" chip class,
 *          built in JS. Already correct in practice (the hex is only the var() FALLBACK,
 *          and --color-accent is copper here), but it is remapped anyway so the sheet's
 *          cyan surface area stays at zero and the §3 audit test stays honest. --- */
:root[data-lum-chrome] .border-l-\[\#00B4E6\]{border-left-color:var(--color-accent)!important}
:root[data-lum-chrome] .bg-\[--color-accent\2c \#00B4E6\]{background-color:var(--color-accent)!important}
/* Login-screen logo glow. main.css hangs a cyan drop-shadow off .auth-logo for dark mode
   and replaces it entirely in light mode; only the dark rule needs re-pointing, and
   scoping to [data-theme="dark"] leaves main.css's light-mode rule untouched. */
:root[data-lum-chrome][data-theme="dark"] .auth-logo{filter:drop-shadow(0 0 28px color-mix(in srgb,var(--color-accent) 35%,transparent))}

/* ───────── 4. Hardcoded cool-slate surfaces -> the warm tokens (dark) ────────
 * main.css already remaps these under [data-theme=light]; dark mode had no such
 * layer because slate WAS the dark palette. On a warm plane a #0f172a panel reads
 * as a blue hole, so the same remap is applied for dark. Foreground slate text
 * classes are deliberately left alone: they sit on colored chips as dark ink. */
:root[data-lum-chrome][data-theme="dark"] .bg-slate-950{background-color:var(--color-bg)!important}
:root[data-lum-chrome][data-theme="dark"] .bg-slate-900{background-color:var(--color-surface)!important}
:root[data-lum-chrome][data-theme="dark"] .bg-slate-800{background-color:var(--color-surface-raised)!important}
:root[data-lum-chrome][data-theme="dark"] .bg-slate-700{background-color:var(--color-surface-hover)!important}
:root[data-lum-chrome][data-theme="dark"] .hover\:bg-slate-800:hover{background-color:var(--color-surface-raised)!important}
:root[data-lum-chrome][data-theme="dark"] .hover\:bg-slate-700:hover{background-color:var(--color-surface-hover)!important}
:root[data-lum-chrome][data-theme="dark"] .border-slate-800,:root[data-lum-chrome][data-theme="dark"] .border-slate-700{border-color:var(--color-border)!important}
:root[data-lum-chrome][data-theme="dark"] .divide-slate-700>:not([hidden])~:not([hidden]){border-color:var(--color-border)!important}

/* ═══════════════════════ 5. GTM COMPONENTS (lum-*) ═══════════════════════ */
/* Type scale. The app's Geist face is kept — only sizes/weights/tracking are the
   GTM ones. */
.tnum,.lum-kpi-v,.lum-table td,.lum-table th{font-variant-numeric:tabular-nums}
.lum-eyebrow{font-size:11px;font-weight:600;letter-spacing:.10em;text-transform:uppercase;color:var(--accent-ink)}
.lum-kpi-k{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}
.lum-kpi-v{font-size:26px;font-weight:620;letter-spacing:-.02em;line-height:1.05;color:var(--ink)}
.lum-kpi-d{font-size:11px;color:var(--ink-2);line-height:1.35;margin-top:1px}

/* ── Missing-utility backfill (RECONCILED — Typography Phase B, 2026-08-26) ──
 * main.css was a stale PRE-BUILT Tailwind artifact that predated the Finance Hub
 * templates, so five arbitrary values those templates use rendered as nothing and were
 * backfilled here. Phase B rebuilt main.css from src/input.css, and FOUR of the five
 * now compile for real — `text-[10.5px]`, `bg-[--color-accent-dim]`,
 * `border-[--color-accent]` and `text-[22px]` were verified present in the rebuilt
 * sheet and their backfill lines have been deleted. (`text-[22px]` has since dropped
 * back out of main.css, correctly: Phase B step 5 moved its only two consumers onto
 * .t-kpi, so no template asks for it any more and Tailwind purges it. Nothing to
 * restore — if a template ever uses it again, the next build emits it.)
 *
 * THE LAST SURVIVOR IS GONE (register #49, 2026-09-25). `border-[--color-accent]/40`
 * could never compile — Tailwind v3 cannot apply an opacity modifier to a
 * bare-custom-property arbitrary value, it has no colour to mix — so it was backfilled
 * by hand here. But this file is NEVER LOADED ON THE NLE PROFILE, so the backfill only
 * ever worked on generic/demo; nle rendered those three borders unstyled. The three
 * templates (funding/finance_overview.html x2, funding/project_money.html) now use the
 * profile-independent house class `.border-accent-40` from src/input.css, which ships
 * in main.css with a plain-var base rule plus a color-mix rule inside @supports. The
 * backfill declaration is deleted; nothing is left to reconcile here. */

/* Finance Hub tab strip (generic + demo only — nle never renders _finance_tabs.html). */
.lum-tab-on{color:var(--color-accent-ink,var(--color-accent))!important;font-weight:650;
  border-bottom-color:var(--color-accent)}

.lum-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);box-shadow:var(--shadow)}
.lum-card-hd{padding:11px 14px 10px;border-bottom:1px solid var(--border)}
.lum-card-t{font-size:15px;font-weight:600;color:var(--ink);letter-spacing:-.006em}
.lum-card-s{font-size:11.5px;color:var(--muted);margin-top:2px}
:root[data-lum-chrome] .lum-card-t{font-family:var(--font-display)}
.lum-card-b{padding:14px}

.lum-kpi-band{display:grid;grid-template-columns:repeat(5,1fr);gap:12px}
.lum-kpi-band-4{grid-template-columns:repeat(4,1fr)}
@media(max-width:940px){.lum-kpi-band,.lum-kpi-band-4{grid-template-columns:repeat(2,1fr)}}
@media(max-width:520px){.lum-kpi-band,.lum-kpi-band-4{grid-template-columns:1fr}}
.lum-kpi{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);
  box-shadow:var(--shadow);padding:13px 14px;display:flex;flex-direction:column;gap:3px}
/* FLAT, NOT GRADIENT. These four each used to paint a 160° wash across the card. Stacked
   four-across on a desktop that reads as a tint; stacked ONE-across on a phone (the
   <=520px rule above) it is four full-width panels each fading corner to corner, and the
   band stops reading as data and starts reading as decoration — the number, which is the
   only thing on the card anyone came for, competes with its own background. The colour
   the card is FOR now lives entirely in the two places that carry meaning: the border and
   the value. Keep it that way; a wash here is a tax paid on every mobile KPI band. */
.lum-kpi-accent{border-color:var(--accent-line);background:var(--surface)}
.lum-kpi-accent .lum-kpi-v{color:var(--accent-ink)}
.lum-kpi-cash{border-color:var(--cash-line);background:var(--surface)}
.lum-kpi-cash .lum-kpi-v{color:var(--cash-ink)}
.lum-kpi-warning{border-color:var(--warning);background:var(--surface)}
.lum-kpi-warning .lum-kpi-v{color:var(--warning)}
.lum-kpi-critical{border-color:var(--critical);background:var(--surface)}
.lum-kpi-critical .lum-kpi-v{color:var(--critical)}

.lum-pill{display:inline-block;font-size:10px;font-weight:700;letter-spacing:.02em;
  padding:2px 8px;border-radius:20px;white-space:nowrap;
  background:var(--surface-3);border:1px solid var(--border-strong);color:var(--ink-2)}
.lum-pill-cash{background:var(--cash-wash);border-color:var(--cash-line);color:var(--cash-ink)}
.lum-pill-accent{background:var(--accent-wash);border-color:var(--accent-line);color:var(--accent-ink)}

.lum-note{display:flex;gap:11px;align-items:baseline;flex-wrap:wrap;
  background:var(--accent-wash);border:1px solid var(--accent-line);border-radius:var(--r-sm);
  padding:10px 13px;font-size:12px;color:var(--ink-2);line-height:1.5}
.lum-note b{color:var(--ink);font-weight:700}
.lum-note-k{font-size:10.5px;font-weight:600;letter-spacing:.10em;text-transform:uppercase;
  color:var(--accent-ink);white-space:nowrap;flex:none}
.lum-foot{font-size:11px;color:var(--muted);margin-top:12px;line-height:1.5;max-width:92ch}
.lum-foot b{color:var(--ink-2)}
.lum-legend{display:flex;gap:16px;flex-wrap:wrap;align-items:center;margin:2px 0 14px;font-size:12px;color:var(--ink-2)}
.lum-legend .li{display:inline-flex;align-items:center;gap:7px}
.lum-legend .sw{width:14px;height:10px;border-radius:3px;flex:none}

/* ── table ── */
.lum-tbl-wrap{overflow-x:auto}
table.lum-table{border-collapse:collapse;width:100%;font-size:12px;color:var(--ink-2)}
table.lum-table th,table.lum-table td{padding:7px 9px;text-align:right;border-bottom:1px solid var(--border);white-space:nowrap}
table.lum-table th:first-child,table.lum-table td:first-child{text-align:left}
table.lum-table thead th{font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);
  font-weight:600;border-bottom:1.5px solid var(--border-strong)}
table.lum-table tbody tr:hover{background:var(--surface-2)}
table.lum-table tbody td:first-child{font-weight:650;color:var(--ink);white-space:normal;min-width:150px}
table.lum-table tfoot td{font-weight:750;color:var(--ink);border-top:1.5px solid var(--border-strong);
  border-bottom:none;background:var(--surface-2)}
table.lum-table .cash-c{color:var(--cash-ink);font-weight:650}
table.lum-table .neg{color:var(--critical);font-weight:700}

/* ── Gantt ── */
.lum-gantt-scroll{overflow-x:auto}
.lum-gantt{min-width:760px}
.lum-axis{display:grid;grid-template-columns:var(--labcol,190px) repeat(12,1fr);align-items:end;
  border-bottom:1px solid var(--border)}
.lum-axis .yr{grid-row:1;font-size:10px;font-weight:700;letter-spacing:.06em;color:var(--muted);
  text-transform:uppercase;padding:0 0 3px 2px}
.lum-axis .mo{grid-row:2;text-align:center;font-size:11px;font-weight:600;color:var(--ink-2);
  padding:4px 0 6px;border-left:1px solid var(--border)}
.lum-axis .corner{grid-row:1/3}
.lum-lane{display:grid;grid-template-columns:var(--labcol,190px) 1fr;border-bottom:1px solid var(--border)}
.lum-lane:nth-child(odd) .lum-lane-label,.lum-lane:nth-child(odd) .lum-lane-track{background:color-mix(in srgb,var(--surface-2) 55%,transparent)}
.lum-lane-label{padding:10px;display:flex;flex-direction:column;justify-content:center;gap:2px;border-right:1px solid var(--border)}
.lum-lane-label .ln{font-size:12.5px;font-weight:700;color:var(--ink);display:flex;align-items:center;gap:7px}
.lum-lane-label .lc{width:9px;height:9px;border-radius:3px;flex:none;background:var(--com)}
.lum-lane-label .ld{font-size:10.5px;color:var(--muted);font-variant-numeric:tabular-nums}
.lum-lane-track{padding:9px 0;display:grid;grid-template-columns:repeat(12,1fr);grid-auto-rows:24px;gap:5px 0;align-content:center}
.lum-lane-track.has-collect{grid-auto-rows:unset;grid-template-rows:24px 11px;gap:3px 0}
.lum-lane-track.has-collect .lum-bar{grid-row:1}
.lum-lane-track.has-collect .lum-collect-tick{grid-row:2}
.lum-bar{height:24px;border-radius:6px;display:flex;align-items:center;justify-content:center;padding:0 8px;
  font-size:10.5px;font-weight:700;white-space:nowrap;overflow:hidden;margin:0 2px;
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.04)}
.lum-bar.washed{background:var(--com-wash);border:1px solid var(--com-line);color:var(--com)}
.lum-bar.solid{background:var(--com);border:1px solid var(--com);color:#fff}
.lum-bar.hollow{background:repeating-linear-gradient(135deg,var(--surface-3),var(--surface-3) 5px,transparent 5px,transparent 10px);
  border:1px dashed var(--border-strong);color:var(--muted)}
.lum-collect-tick{align-self:center;height:8px;margin:0 3px;border-radius:2px;background:var(--cash);
  box-shadow:0 0 0 1px color-mix(in srgb,var(--cash-ink) 40%,transparent)}
.lum-cash-lane .lum-lane-label .lc{background:var(--cash)}
.lum-cash-track{grid-auto-rows:26px;align-content:center}
.lum-cash-bar{align-self:center;height:26px;border-radius:6px;display:flex;align-items:center;justify-content:center;
  margin:0 2px;background:var(--cash-wash);border:1px solid var(--cash-line);color:var(--cash-ink);
  font-size:10.5px;font-weight:750;font-variant-numeric:tabular-nums;overflow:hidden;white-space:nowrap}

/* ── bar chart ── */
.lum-bars-wrap{display:flex;gap:12px;align-items:stretch}
.lum-yaxis{display:flex;flex-direction:column-reverse;justify-content:space-between;font-size:10px;
  color:var(--muted);text-align:right;padding-bottom:26px;min-width:38px;flex:none}
.lum-plot{flex:1;min-width:0}
.lum-bcols{display:grid;grid-template-columns:repeat(12,1fr);gap:0;height:250px;position:relative;
  border-bottom:1.5px solid var(--border-strong);
  background:repeating-linear-gradient(to top,transparent,transparent calc(20% - 1px),var(--border) calc(20% - 1px),var(--border) 20%)}
.lum-bcol{display:flex;flex-direction:column-reverse;align-items:center;justify-content:flex-start;padding:0 3px;position:relative}
.lum-stack{width:100%;max-width:48px;height:100%;display:flex;flex-direction:column-reverse;
  align-items:stretch;justify-content:flex-start;margin:0 auto}
.lum-seg{width:100%;min-height:0}
.lum-seg.com{background:var(--com);border-radius:0 0 3px 3px}
.lum-seg.res{background:var(--res);border-radius:4px 4px 0 0;margin-bottom:2px}
.lum-seg.cash{background:var(--cash);border-radius:4px 4px 0 0;position:relative}
.lum-seg.cash-collected{background:var(--cash-ink);border-radius:0 0 3px 3px}
.lum-seg.mat{background:var(--critical)}
.lum-seg.lab{background:var(--res)}
.lum-seg.pro{background:var(--cash);border-radius:4px 4px 0 0}
.lum-bcol:hover .lum-stack{filter:brightness(1.06)}
.lum-cbar-lbl{position:absolute;top:-15px;left:50%;transform:translateX(-50%);font-size:10px;font-weight:750;
  color:var(--cash-ink);white-space:nowrap;font-variant-numeric:tabular-nums}
.lum-xlabels{display:grid;grid-template-columns:repeat(12,1fr);margin-top:6px}
.lum-xlabels span{text-align:center;font-size:10px;font-weight:600;color:var(--ink-2)}

/* ── SVG charts ── */
.lum-cum-svg{width:100%;height:auto;aspect-ratio:800/200;display:block;overflow:visible}
.lum-cum-svg .grid{stroke:var(--border);stroke-width:1}
.lum-cum-svg .band{fill:var(--accent-wash);stroke:var(--accent-line);stroke-width:1;stroke-dasharray:4 3}
.lum-cum-svg .line{fill:none;stroke:var(--accent);stroke-width:2.5;stroke-linejoin:round;stroke-linecap:round}
.lum-cum-svg .area{fill:var(--accent-wash)}
.lum-cum-svg .dot{fill:var(--surface);stroke:var(--accent);stroke-width:2}
.lum-cum-svg .endlbl{fill:var(--ink);font-size:11px;font-weight:750}
.lum-cum-svg .axl{fill:var(--muted);font-size:9.5px}
.lum-cum-svg .cost-line{fill:none;stroke:var(--critical);stroke-width:2;stroke-dasharray:5 4;stroke-linejoin:round;stroke-linecap:round}
.lum-cash-cum .line{stroke:var(--cash)}
.lum-cash-cum .area{fill:var(--cash-wash)}
.lum-cash-cum .dot{stroke:var(--cash)}
.lum-wc-svg{aspect-ratio:800/240}
.lum-wc-svg .wc-area{fill:var(--cash-wash)}
.lum-wc-svg .wc-line{fill:none;stroke:var(--cash);stroke-width:2.5;stroke-linejoin:round;stroke-linecap:round}
.lum-wc-svg .wc-zero{stroke:var(--ink-2);stroke-width:1.5}
.lum-wc-svg .wc-floor{stroke:var(--critical);stroke-width:1.5;stroke-dasharray:5 4;opacity:.8}
.lum-wc-svg .wc-floorlbl{fill:var(--critical);font-size:10px;font-weight:700}
.lum-wc-svg .wc-trough{fill:var(--surface);stroke:var(--warning);stroke-width:2}
.lum-wc-svg .wc-trough.bad{stroke:var(--critical)}
.lum-wc-svg .wc-troughlbl{fill:var(--ink);font-size:10.5px;font-weight:750}

/* ── Type marker (commercial/residential/trade) ─────────────────────────────
 * Quiet outline tag — replaces the saturated `.lum-pill-accent` purple "Commercial"
 * chip (owner feedback, 2026-08-24: reads as AI slop). Deliberately NOT pill-shaped
 * (5px radius, not 20px) and NOT color-coded — icon + label carry the distinction,
 * matching var(--color-border)/var(--color-text-muted) so it reads identically in
 * light and dark. Sized to sit level with `.lum-pill` (2px vertical padding, ~16px
 * total height) wherever the two appear side by side (quotes.html, quote_view.html). */
.lum-type-marker{display:inline-flex;align-items:center;gap:4px;
  padding:2px 7px;border-radius:5px;
  border:1px solid var(--color-border);background:transparent;
  color:var(--color-text-muted);
  font-size:10.5px;font-weight:650;letter-spacing:.05em;text-transform:uppercase;
  line-height:1.4;white-space:nowrap;vertical-align:middle}
.lum-type-marker svg{width:12px;height:12px;flex:none;opacity:.85}
/* Third-trade fallback (multi-trade shops): a small color dot instead of the generic
 * glyph, so trades beyond Residential/Commercial stay visually distinct from each
 * other without reintroducing a tinted-background wash. */
.lum-type-marker-dot{width:6px;height:6px;border-radius:50%;flex:none}

/* ── App-chrome modal (.lum-dialog) ─────────────────────────────────────────
 * A native <dialog> styled in the app's own tokens, for the one place inside the
 * authenticated app that needs a real modal: the quote page's Create-deal confirm sheet,
 * which asks for the ops fields the deal gate requires before it opens a job.
 *
 * NATIVE <dialog>, not a div: `showModal()` brings the top layer, the backdrop, focus
 * trapping and Esc-to-close for free, and a browser without it simply never opens the
 * sheet — the page under it is unchanged and the bound-quote path (a plain one-button
 * form) still works. Same reasoning as public/accept.html's own dialog.
 *
 * GENERIC PROFILE ONLY, like everything else in this file: base.html emits the <link>
 * inside `{% if not is_nle_profile %}`, and the template that uses these classes is a
 * generic-only page whose route 404s on nle. */
.lum-dialog{
  border:1px solid var(--color-border);border-radius:14px;
  background:var(--color-surface-raised);color:var(--color-text-primary);
  padding:0;width:min(560px,calc(100vw - 32px));max-height:calc(100vh - 64px);
  box-shadow:0 24px 60px rgba(0,0,0,.28)}
.lum-dialog::backdrop{background:rgba(17,16,16,.55)}
.lum-dialog-hd{padding:16px 18px 10px;border-bottom:1px solid var(--color-border)}
.lum-dialog-t{font-size:15px;font-weight:700;line-height:1.3}
.lum-dialog-s{font-size:12px;color:var(--color-text-muted);margin-top:3px;line-height:1.45}
.lum-dialog-b{padding:14px 18px;overflow-y:auto;max-height:calc(100vh - 220px)}
.lum-dialog-ft{padding:12px 18px 16px;border-top:1px solid var(--color-border);
  display:flex;gap:10px;align-items:center;justify-content:flex-end;flex-wrap:wrap}

/* ── Bulk-action bar (.lum-bulkbar) ─────────────────────────────────────────
 * The sticky footer the quote list's Select mode raises. Fixed rather than sticky so it
 * cannot be scrolled away from mid-selection, and it pairs with `.nle-above-bottomnav`
 * (main.css) on the element itself so it clears the mobile bottom nav instead of sitting
 * under it. Hidden by default: JS-off never reveals it, and nothing it offers is
 * reachable without the checkboxes it acts on.
 *
 * GENERIC PROFILE ONLY, like everything else in this file — the flat-quote list 404s on
 * the nle profile. */
/* ONE ROW, ALWAYS. `flex-wrap:wrap` let the bar grow to two and three rows at 390px —
 * and this bar is fixed to the bottom of the viewport, so every extra row it grew ate
 * more of the list it is acting on, from the bottom up. The verbs now live in their own
 * horizontal scroller (.lum-bulkbar-acts) so the bar's height is a constant the page can
 * reserve space for, and nothing is ever unreachable: the count and Cancel are pinned at
 * the left, the actions scroll. */
.lum-bulkbar{
  position:fixed;left:0;right:0;bottom:0;z-index:40;
  display:flex;align-items:center;gap:10px;flex-wrap:nowrap;
  padding:10px 14px;
  border-top:1px solid var(--color-border);
  background:var(--color-surface-raised);
  box-shadow:0 -8px 28px rgba(0,0,0,.18)}
/* The verbs. `--scroll-fade` is set inline on the element to the bar's own fill, because
 * `.scroll-row`'s edge covers must be painted in whatever is actually behind the strip
 * (see the longhand note in src/input.css) — here that is --color-surface-raised, not the
 * page and not a card. */
/* AUTO MARGIN, NOT `justify-content:flex-end`. Both right-align the verbs when there is
 * room; only one of them survives overflow. On an overflowing flex line `flex-end` pushes
 * the excess out the LEFT edge, into negative scroll space no scroller can reach — at
 * 390px that put Archive underneath the Cancel button, visible and permanently
 * unclickable (Playwright: "Cancel intercepts pointer events"). An auto margin on the
 * first child absorbs free space the same way and resolves to ZERO once the line
 * overflows, so the excess goes right and scrolls normally. */
.lum-bulkbar-acts{flex:1 1 auto;min-width:0;display:flex;gap:8px}
.lum-bulkbar-acts>*{flex:0 0 auto;white-space:nowrap}
.lum-bulkbar-acts>*:first-child{margin-left:auto}
/* The count and the way out never shrink into an ellipsis to make room for the verbs. */
.lum-bulkbar-n,.lum-bulkbar>.btn{flex:0 0 auto}
/* AND IT STOPS AT THE SIDEBAR. `left:0` spans the whole viewport, and at `lg`+ the first
   256px of that is base.html's static `w-64` sidebar (z-50, which out-stacks this bar's
   z-40) — so on a desktop the left end of this bar was painted OVER. That was invisible
   while the only thing living there was the "N selected" label; it became a real defect
   the moment a CONTROL sat there, because Cancel was unclickable (Playwright: "sidebar
   subtree intercepts pointer events"). 16rem = the sidebar's own `lg:w-64`; keep the two
   in step if that width ever moves. Below `lg` the sidebar is an off-canvas drawer and
   this bar correctly spans the full width. */
@media (min-width:1024px){.lum-bulkbar{left:16rem}}
.lum-bulkbar-n{font-size:12.5px;font-weight:700;color:var(--color-text-primary);
  white-space:nowrap;font-variant-numeric:tabular-nums}
.lum-bulkbar-sp{flex:1 1 auto}
/* The two destructive verbs. `.btn-danger` does not exist in the built main.css and this
 * worktree cannot rebuild it (LESSONS-DB AU-6), so the danger tone is defined here. */
.lum-btn-danger{
  display:inline-flex;align-items:center;gap:6px;
  font-size:13px;font-weight:650;line-height:1;
  border-radius:8px;padding:9px 13px;
  border:1px solid var(--color-critical,#d03b3b);
  background:transparent;color:var(--color-critical,#d03b3b);cursor:pointer}
.lum-btn-danger:hover{background:var(--color-critical,#d03b3b);color:#fff}

/* ── Ask Debbie — the agent chat surface (.dbb-*) ───────────────────────────
 * The quoting agent's conversation page: a scrolling transcript, a composer pinned to the
 * bottom of it, and the nine-item intake panel beside it.
 *
 * DEFINED HERE, NOT AS TAILWIND UTILITIES, for the reason `.lum-btn-danger` above is:
 * `main.css` is a BUILD ARTIFACT and this worktree carries no `bin/tailwindcss`, so a class
 * that is not already compiled is silently inert (LESSONS-DB AU-6 / BS-3). Everything the
 * chat needs that the built bundle does not already emit — the bubble geometry, the avatar
 * disc, the drawer behaviour at phone width — lives in this hand-written file instead.
 *
 * GENERIC PROFILE ONLY, like everything else here: the agent routes 404 on nle. */
.dbb-thread{
  display:flex;flex-direction:column;gap:12px;
  overflow-y:auto;-webkit-overflow-scrolling:touch;
  padding:14px;min-height:220px;max-height:min(62vh,640px)}
.dbb-row{display:flex;gap:9px;align-items:flex-start}
.dbb-row-me{flex-direction:row-reverse}
/* The avatar. An initial-letter disc today; the <img> slot inside it is what a portrait
 * drops into (Track 2) without touching a single other rule — the disc keeps the size, the
 * ring and the fallback letter underneath. */
.dbb-av{
  flex:0 0 auto;width:30px;height:30px;border-radius:50%;overflow:hidden;position:relative;
  display:inline-flex;align-items:center;justify-content:center;
  font-size:13px;font-weight:750;letter-spacing:.01em;
  background:var(--accent-wash,rgba(200,122,47,.12));
  color:var(--accent-ink,#8F5115);
  border:1px solid var(--accent-line,rgba(200,122,47,.42))}
.dbb-av img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.dbb-av-me{
  background:var(--color-surface-hover,var(--surface-3,#F3F0E9));
  color:var(--color-text-secondary,var(--ink-2,#57534A));
  border-color:var(--color-border,var(--border,#E5E1D7))}
.dbb-bubble{
  max-width:min(85%,58ch);
  border-radius:14px;padding:9px 12px;
  font-size:13.5px;line-height:1.5;
  white-space:pre-wrap;overflow-wrap:anywhere;
  border:1px solid var(--color-border,var(--border,#E5E1D7));
  background:var(--color-surface,var(--surface,#fff));
  color:var(--color-text-primary,var(--ink,#1A1714))}
.dbb-bubble-me{
  background:var(--accent-wash,rgba(200,122,47,.12));
  border-color:var(--accent-line,rgba(200,122,47,.42))}
.dbb-when{font-size:10.5px;color:var(--color-text-faint,var(--muted,#8C877B));margin-top:3px}
/* "Debbie is thinking…" — three dots, no library, and it disappears with the element. */
.dbb-think{display:inline-flex;gap:4px;align-items:center;padding:2px 0}
.dbb-think i{
  width:5px;height:5px;border-radius:50%;display:block;
  background:var(--accent,#C87A2F);animation:dbb-blink 1.15s infinite ease-in-out}
.dbb-think i:nth-child(2){animation-delay:.16s}
.dbb-think i:nth-child(3){animation-delay:.32s}
@keyframes dbb-blink{0%,80%,100%{opacity:.25}40%{opacity:1}}
@media (prefers-reduced-motion:reduce){.dbb-think i{animation:none;opacity:.6}}
/* The composer sits under the thread inside the same card, so the card scrolls the
 * transcript and the input never leaves the screen on a phone. */
/* `flex-wrap` is for the character counter below and nothing else: the textarea and the Send
 * button both fit one row at 320px, so wrapping changes nothing until `.dbb-count` (which is
 * `flex:1 0 100%`) is on the screen — and then it takes a row of its own instead of squeezing
 * the composer. */
.dbb-composer{
  display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap;
  padding:10px 12px;border-top:1px solid var(--color-border,var(--border,#E5E1D7));
  background:var(--color-surface-raised,var(--surface-2,#FAF8F3))}
.dbb-composer textarea{
  flex:1 1 auto;resize:none;min-height:40px;max-height:140px;
  font:inherit;font-size:13.5px;line-height:1.45;
  padding:9px 11px;border-radius:10px;
  border:1px solid var(--color-border,var(--border,#E5E1D7));
  background:var(--color-surface,var(--surface,#fff));
  color:var(--color-text-primary,var(--ink,#1A1714))}
.dbb-send{
  flex:0 0 auto;border:0;cursor:pointer;border-radius:10px;
  padding:10px 15px;font-size:13px;font-weight:700;line-height:1;
  background:var(--accent,#C87A2F);color:#fff}
.dbb-send[disabled]{opacity:.55;cursor:default}
/* THE COMPOSER'S CHARACTER BUDGET, SAID BEFORE IT IS SPENT (DI-G10/HI-6). `#dbb-input` now
 * carries a `maxlength`, and a maxlength TRUNCATES A PASTE IN SILENCE — the browser fires no
 * event a person can see, so a rep who pastes a spec book gets a message that is quietly
 * shorter than what they pasted and no clue which end went missing. This is the line that
 * says it. It is the composer's THIRD flex child on its own row (`flex:1 0 100%` + the
 * wrap below), so the textarea and the Send button keep the row they have today, and it is
 * `.hidden` until the last stretch of the budget — a counter that is always on is noise on a
 * phone screen that has room for about four lines of text. */
.dbb-count{
  flex:1 0 100%;margin:0;
  font-size:11px;line-height:1.4;
  color:var(--color-text-faint,var(--muted,#8C877B))}
/* AT THE CAP, NOT NEAR IT — a different sentence and a different colour, because this is the
 * state where text the person believes they sent is not in the box at all. */
.dbb-count-max{color:var(--critical,#d03b3b);font-weight:650}
/* Attachment chips under a message: a thumbnail (or a glyph) plus the filename, laid out as
 * a wrapping strip so three site photos and a PDF fit a phone without a horizontal scroll. */
.dbb-clips{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
.dbb-clip{
  display:inline-flex;align-items:center;gap:6px;max-width:100%;
  padding:4px 8px 4px 4px;border-radius:9px;text-decoration:none;
  border:1px solid var(--color-border,var(--border,#E5E1D7));
  background:var(--color-surface-raised,var(--surface-2,#FAF8F3));
  color:var(--color-text-secondary,var(--ink-2,#57534A))}
.dbb-clip:hover{border-color:var(--accent-line,rgba(200,122,47,.42))}
.dbb-clip img{width:26px;height:26px;border-radius:6px;object-fit:cover;display:block;
  background:var(--color-surface-hover,var(--surface-3,#F3F0E9))}
.dbb-clip-i{width:22px;text-align:center;font-size:14px;line-height:1;flex:0 0 auto}
.dbb-clip-n{font-size:11.5px;line-height:1.2;max-width:15ch;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
/* The takeoff hint — the one thing on this page that runs longer than a turn, so it needs
 * its own line rather than borrowing the thinking dots (which belong to the turn). */
.dbb-note{
  display:flex;gap:8px;align-items:flex-start;
  margin:2px 4px;padding:8px 10px;border-radius:10px;
  font-size:12px;line-height:1.45;
  border:1px solid var(--accent-line,rgba(200,122,47,.42));
  background:var(--accent-wash,rgba(200,122,47,.12));
  color:var(--color-text-secondary,var(--ink-2,#57534A))}
/* A NOTE THAT IS BAD NEWS — a refused send, or a turn that died. Same shape as the takeoff
 * hint (it is the same kind of thing: a line the SERVER is saying, not a chat bubble), in the
 * critical colour so it is not mistaken for progress. */
.dbb-note-bad{
  border-color:var(--critical-line,rgba(190,52,44,.42));
  background:var(--critical-wash,rgba(190,52,44,.10));
  color:var(--color-text-secondary,var(--ink-2,#57534A))}
/* A LINE THE SERVER IS SAYING, IN THE TRANSCRIPT (round-8 r1, Q). Both renderers used to give
 * every non-user role Debbie's bubble and Debbie's avatar, so the offer notice — visible precisely
 * so a rep reads the SERVER's percentages rather than the model's paraphrase of them — was
 * indistinguishable from her talking. Same shape as the takeoff hint and the refusal line, because
 * it is the same kind of thing; copper rather than critical, because it is not bad news; full width
 * with no avatar gutter, because nobody said it. */
.dbb-note-server{
  border-color:var(--accent-line,rgba(200,122,47,.55));
  background:var(--accent-wash,rgba(200,122,47,.14));
  color:var(--color-text-primary,var(--ink-1,#2E2A24));
  font-weight:600}
/* THE ONE-TAP YES WHEN THIS PAGE CANNOT STAND BEHIND IT (round-8 r1, R/S) — a run of failed polls,
 * or a turn already running. Disabled is the mechanism; this is only so it looks disabled. */
.dbb-assent-btn:disabled{opacity:.55;cursor:default;filter:none}
.dbb-assent-blind{opacity:.45}
/* AN ATTACHMENT WHOSE DOCUMENT HAS BEEN DELETED. It still renders — the transcript is the
 * record of what was SENT, and that outlives the file — but it is not a link, and it looks
 * like what it is. */
.dbb-clip-gone{opacity:.6;cursor:default;text-decoration:line-through}
/* CAPTURE, under the composer. A <details> so it costs nothing until it is opened and works
 * with no JavaScript; the recorder inside it is the quote hub's, verbatim. */
.dbb-attach{border-top:1px solid var(--color-border,var(--border,#E5E1D7));
  background:var(--color-surface-raised,var(--surface-2,#FAF8F3))}
.dbb-attach>summary{
  cursor:pointer;list-style:none;display:flex;align-items:center;gap:7px;
  padding:9px 12px;font-size:12px;font-weight:650;
  color:var(--color-text-muted,var(--ink-2,#57534A))}
.dbb-attach>summary::-webkit-details-marker{display:none}
.dbb-attach>summary em{font-style:normal;font-weight:500;
  color:var(--color-text-faint,var(--muted,#8C877B))}
.dbb-attach[data-dbb-nojob]>summary{opacity:.65}
.dbb-attach-b{display:flex;flex-direction:column;gap:12px;padding:0 12px 12px}
/* The intake checklist. One row per gate item; the tick is the whole status. */
.dbb-gate{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
.dbb-gate li{display:flex;gap:8px;align-items:flex-start;font-size:12.5px;line-height:1.4;
  color:var(--color-text-muted,var(--ink-2,#57534A))}
.dbb-gate li.is-on{color:var(--color-text-primary,var(--ink,#1A1714));font-weight:600}
/* THE ITEMS A REFUSAL NAMED (UX-6). Not "unconfirmed" — the panel already shows that as an
 * unlit tick — but "these are the ones that sent it back", which is a different fact and the
 * only one that tells a rep what to go and ask. Critical tone, and it clears itself the moment
 * the item is confirmed (see `paintGate` in agent_chat.html). */
.dbb-gate li.is-bounced{color:var(--critical,#d03b3b);font-weight:600}
.is-bounced .dbb-tick{
  border-color:var(--critical,#d03b3b);
  background:var(--critical-wash,rgba(208,59,59,.11));color:transparent}
.dbb-tick{
  flex:0 0 auto;width:16px;height:16px;border-radius:50%;margin-top:1px;
  display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:800;
  border:1px solid var(--color-border-muted,var(--border,#E5E1D7));
  background:transparent;color:transparent}
.is-on .dbb-tick{
  border-color:var(--cash-line,rgba(22,163,74,.5));
  background:var(--cash-wash,rgba(22,163,74,.14));color:var(--cash-ink,#0f7a37)}
/* ALL NINE CONFIRMED — the finalize affordance. A SENTENCE, not a button: nine ticks is
 * necessary and not sufficient (the margin floor and the $/SF band still have to clear
 * server-side), and a control here would either lie about what it could do or become a
 * second, thinner path to the same irreversible act. Toggled by the poller on the `.hidden`
 * class, which is why it is listed in the override block below. */
.dbb-ready{
  display:block;margin:10px 0 0;padding:8px 10px;border-radius:9px;
  font-size:11.5px;line-height:1.45;
  border:1px solid var(--cash-line,rgba(22,163,74,.5));
  background:var(--cash-wash,rgba(22,163,74,.14));
  color:var(--color-text-secondary,var(--ink-2,#57534A))}
/* A CHECK THAT DID NOT RUN, SAID OUT LOUD (UX-5). Deliberately NOT the green of `.dbb-ready`
 * and not the red of a refusal: "NOT APPLICABLE" is neither a pass nor a failure, and giving
 * it either colour is the whole defect — a skipped check that wears a passing colour is worse
 * than no check at all (`debbie_tools._benchmark_check` keeps the same rule in words). Warning
 * tone, because the consequence is that one of the two money checks is not going to run. */
.dbb-na{
  display:block;margin:10px 0 0;padding:8px 10px;border-radius:9px;
  font-size:11.5px;line-height:1.45;
  border:1px solid var(--warning,#c98908);
  background:var(--warning-wash,rgba(201,137,8,.14));
  color:var(--color-text-secondary,var(--ink-2,#57534A))}
/* THE BOUNCED HEADLINE (UX-6) — the sentence over the ticked list naming what came back.
 * Empty and hidden until a refusal names items; written with textContent, like everything
 * else on that page. */
.dbb-bounced{
  display:block;margin:10px 0 0;padding:8px 10px;border-radius:9px;
  font-size:11.5px;line-height:1.45;font-weight:600;
  border:1px solid var(--critical-line,rgba(190,52,44,.42));
  background:var(--critical-wash,rgba(208,59,59,.11));
  color:var(--color-text-secondary,var(--ink-2,#57534A))}
/* THE GAP LIST (round-8, D4) — one row per item Debbie tried to record and could not, saying
 * what is MISSING and never what was refused. Deliberately quieter than `.dbb-bounced` above:
 * that line is a refusal that just happened and shouts once, this is a standing state ("nobody
 * has said the payment figures yet") that a rep reads while they work, and shouting it on every
 * poll for the rest of the conversation is how a panel gets ignored. Warning tone, not critical:
 * nothing is broken, an answer is outstanding. */
.dbb-gaps{
  display:block;margin:10px 0 0;padding:0;list-style:none;
  border-top:1px solid var(--color-border,rgba(0,0,0,.08));padding-top:8px}
.dbb-gaps>li{
  display:block;margin:0 0 6px;font-size:11.5px;line-height:1.45}
.dbb-gaps>li:last-child{margin-bottom:0}
.dbb-gap-item{
  display:block;font-weight:700;
  color:var(--warning,#c98908)}
.dbb-gap-why{
  display:block;color:var(--color-text-secondary,var(--ink-2,#57534A))}
/* RECORDED BUT NOT YET CHECKED (round-8, D3) — the two items that owe a read-back. Shares the
 * gap list's row shape (same two spans) because it is the same kind of statement in the same
 * place, and a second visual language for "still owed" would just be noise. Its own class so the
 * two lists can be toggled independently, which they are: one clears when an item is RECORDED,
 * the other when it is CONFIRMED BACK. */
.dbb-readback{
  display:block;margin:10px 0 0;padding:0;list-style:none;
  border-top:1px solid var(--color-border,rgba(0,0,0,.08));padding-top:8px}
.dbb-readback>li{
  display:block;margin:0 0 6px;font-size:11.5px;line-height:1.45}
.dbb-readback>li:last-child{margin-bottom:0}
/* THE RECORDED VALUE, AT A SIZE A PERSON CAN CHECK IT IN (round-8 W10b, part C). The row is now a
 * QUESTION rather than a status, so the thing being asked about is the loudest part of it — the
 * same reasoning `.dbb-assent-schedule` has, because it is the same act: read the server's own
 * text, then tap. `word-break` because a legal name or an email address is one long token and the
 * panel is 300px wide on a phone. */
.dbb-readback-value{
  display:block;font-weight:700;font-size:12.5px;line-height:1.35;
  overflow-wrap:anywhere;word-break:break-word}
/* 44px MINIMUM TARGET, same as the assent button and for the same reason: a thumb, one-handed, on
 * a ladder. Quieter than the assent's solid cash fill — confirming a recorded value is an ordinary
 * step, not the moment money gets agreed — but the same size, because target size is about hands
 * and not about emphasis. */
.dbb-confirm-btn{
  display:block;width:100%;min-height:44px;margin-top:6px;padding:10px 12px;border-radius:9px;
  font-size:13px;font-weight:700;cursor:pointer;
  border:1px solid var(--cash-ink,#0f7a37);
  background:transparent;color:var(--cash-ink,#0f7a37)}
.dbb-confirm-btn:hover{background:var(--cash-wash,rgba(22,163,74,.14))}
.dbb-confirm-btn:disabled{opacity:.55;cursor:default;filter:none}
/* THE ONE-TAP YES (round-8, D1) — shown only while the server says a payment schedule is on the
 * table and unanswered. Cash tone, because agreeing to the house schedule is the ordinary happy
 * path and not a warning; the schedule sentence is the server's own text and is set at a size a
 * person can actually check the percentages in, since checking them is the entire point. */
.dbb-assent{
  display:block;margin:10px 0 0;padding:8px 10px;border-radius:9px;
  border:1px solid var(--cash-line,rgba(22,163,74,.5));
  background:var(--cash-wash,rgba(22,163,74,.14))}
.dbb-assent-q{
  display:block;margin:0 0 6px;font-size:11.5px;line-height:1.45;
  color:var(--color-text-secondary,var(--ink-2,#57534A))}
.dbb-assent-schedule{display:block;font-weight:700;font-size:12.5px}
/* 44px MINIMUM TARGET — this is the control a rep taps on a ladder, one-handed, and the whole
 * reason it exists instead of "type yes". `width:100%` so the target is the full panel width at
 * phone sizes rather than a word-sized box in the corner. */
.dbb-assent-btn{
  display:block;width:100%;min-height:44px;padding:10px 12px;border-radius:9px;
  font-size:13px;font-weight:700;cursor:pointer;
  border:1px solid var(--cash-ink,#0f7a37);
  background:var(--cash-ink,#0f7a37);color:#fff}
.dbb-assent-btn:hover{filter:brightness(1.06)}
/* PHONE: the panel becomes a drawer under the chat rather than a second column beside it.
 * <details> so it works with no JavaScript at all — the one control on this page a rep
 * might reach for while the network is deciding whether to answer. */
.dbb-panel-toggle{display:none}
@media (max-width:1023px){
  .dbb-thread{max-height:min(54vh,520px)}
  /* THE DRAWER CLEARS THE FIXED BOTTOM NAV, AND IT DID NOT (round-7 layout finding). At
   * 375×667 this <details> is the LAST thing in the page's flow and `<main>` ends with 16px
   * of padding — so `#bottom-nav` (fixed, 4rem tall, z-30) sat on top of the drawer's own
   * summary: `elementFromPoint` on "Intake & records" returned the NAV, which is not a
   * cosmetic overlap but a control a thumb cannot hit. The whole panel is behind that summary
   * on a phone, so the failure is "the intake checklist is unreachable", quietly, on the one
   * screen size every rep in a truck is holding.
   *
   * 4rem + the safe-area inset is the app's own number for this clearance — `.nle-above-
   * bottomnav` in main.css sets exactly `calc(4rem + env(safe-area-inset-bottom))` — kept in
   * step deliberately rather than re-measured, so a nav that grows is fixed in one place.
   * PADDING, not margin: the drawer's own box grows, which is what gives the document the
   * scroll room to lift the summary out from under the bar. */
  .dbb-panel-toggle{display:block;
    padding-bottom:calc(4rem + env(safe-area-inset-bottom))}
  .dbb-panel-toggle>summary{
    cursor:pointer;list-style:none;
    font-size:12px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
    color:var(--color-text-muted,var(--ink-2,#57534A));padding:2px 0}
  .dbb-panel-toggle>summary::-webkit-details-marker{display:none}
  .dbb-panel-desktop{display:none}
}
/* `.hidden` HAS TO WIN, AND ORDER IS WHY IT OTHERWISE WOULDN'T. base.html loads
 * luminate-theme.css AFTER main.css so this file's tokens override the app's; the side
 * effect is that any `display` set here beats Tailwind's `.hidden{display:none}` at equal
 * specificity. Every `.dbb-*` element the page toggles carries a display rule above, so
 * "hidden" would have shown the thinking indicator permanently. Stated explicitly rather
 * than relied on — the same ordering fact LESSONS-DB records for `.hidden` vs `.flex`. */
.dbb-row.hidden,.dbb-thread.hidden,.dbb-composer.hidden,.dbb-think.hidden,
.dbb-av.hidden,.dbb-gate.hidden,.dbb-panel-desktop.hidden,.dbb-panel-toggle.hidden,
.dbb-note.hidden,.dbb-clips.hidden,.dbb-clip.hidden,.dbb-attach.hidden,
.dbb-attach-b.hidden,.dbb-ready.hidden,.dbb-note-bad.hidden,.dbb-clip-gone.hidden,
.dbb-count.hidden,.dbb-na.hidden,.dbb-bounced.hidden,
.dbb-gaps.hidden,.dbb-readback.hidden,.dbb-assent.hidden,.dbb-assent-btn.hidden,
.dbb-confirm-btn.hidden{
  display:none}

/* ── THE FILE-PICKER CHIP (supporting docs, register #37/#38) ────────────────────────
 * `input[type=file]` renders its button from the PLATFORM, not from the page: light grey
 * with black text in every theme, which on the quote page's dark surface is the one
 * control that does not belong to the design system. `::file-selector-button` is standard
 * CSS (WebKit still needs the `::-webkit-file-upload-button` alias) — no JavaScript, so
 * the section's no-JS floor was never the obstacle an earlier comment there claimed.
 *
 * 44 px MIN-HEIGHT ON THE CHIP, not only on the input. A thumb lands on the button, and
 * the input's own `min-h-[44px]` only makes the BOX tall enough (lesson L9-6 — assert the
 * axis the finger actually hits). Tokens with literal fallbacks, like every `.lum-*` rule
 * above, so the chip is styled even where the remap is not stamped. */
.lum-file-input::file-selector-button,
.lum-file-input::-webkit-file-upload-button{
  min-height:44px;margin-right:10px;padding:0 14px;
  font:inherit;font-weight:700;cursor:pointer;
  border:1px solid var(--color-border-muted,#d8d2c6);border-radius:9999px;
  background:var(--color-surface-raised,#f6f3ee);
  color:var(--color-text-primary,#231f1a);
  transition:border-color .15s ease,background-color .15s ease}
.lum-file-input::file-selector-button:hover,
.lum-file-input::-webkit-file-upload-button:hover{
  border-color:var(--color-accent,#b87333)}
