/* ============================================================================
   Kizuna — application stylesheet (extracted from base.html, verbatim order)
   Sections: Tokens · Typography · Components · Layout · Dark mode · A11y
   Keep cascade order identical to the original inline <style>.
   ============================================================================ */

/* ---- self-hosted fonts (Phase-0 UI/UX uplift, Lane C: Switzer display + Geist body) ----
   Switzer (Fontshare) = display voice; Geist Sans (Vercel) = the numerate body (excellent
   tabular figures for a finance product); Geist Mono available for code/eyebrows. All free,
   self-hosted, NOT Google. General Sans + Inter kept as graceful fallbacks. ---- */
@font-face{font-family:"Switzer";src:url("/static/fonts/switzer-400.woff2") format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Switzer";src:url("/static/fonts/switzer-500.woff2") format("woff2");font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"Switzer";src:url("/static/fonts/switzer-600.woff2") format("woff2");font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:"Switzer";src:url("/static/fonts/switzer-700.woff2") format("woff2");font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:"Geist";src:url("/static/fonts/geist-sans-400.woff2") format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Geist";src:url("/static/fonts/geist-sans-500.woff2") format("woff2");font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"Geist";src:url("/static/fonts/geist-sans-600.woff2") format("woff2");font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:"Geist";src:url("/static/fonts/geist-sans-700.woff2") format("woff2");font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:"Geist Mono";src:url("/static/fonts/geist-mono-400.woff2") format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Geist Mono";src:url("/static/fonts/geist-mono-500.woff2") format("woff2");font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"Geist Mono";src:url("/static/fonts/geist-mono-600.woff2") format("woff2");font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:"General Sans";src:url("/static/fonts/general-sans-400.woff2") format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"General Sans";src:url("/static/fonts/general-sans-500.woff2") format("woff2");font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"General Sans";src:url("/static/fonts/general-sans-600.woff2") format("woff2");font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:"General Sans";src:url("/static/fonts/general-sans-700.woff2") format("woff2");font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:"Inter";src:url("/static/fonts/inter-400.woff2") format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Inter";src:url("/static/fonts/inter-500.woff2") format("woff2");font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"Inter";src:url("/static/fonts/inter-600.woff2") format("woff2");font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:"Inter";src:url("/static/fonts/inter-700.woff2") format("woff2");font-weight:700;font-style:normal;font-display:swap}

/* ---- Tokens (brand) — Kizuna Logic indigo/cyan system ---- */
:root {
  --kz:#4F46E5; --kz2:#2563EB; --accent:#06B6D4;
  --font-display:"Switzer","General Sans","Inter",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  --bs-body-font-family:'Geist','Inter',system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  --font-mono:'Geist Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
}
/* ============================================================================
   FOUNDATIONS v1 — design-token layer (Phase 1 of the UI uplift, 2026-08-05).
   ADDITIVE ONLY: new variables + a .fig utility; nothing existing is redefined,
   so no page changes until a component opts in. Premium components (Phase 2)
   build from these. Kept in sync in kizuna-ui §2.
   ========================================================================== */
:root{
  /* type scale (rem) — ONE size per role, used everywhere */
  --fs-caption:.75rem; --fs-label:.72rem; --fs-body:.95rem; --fs-body-lg:1.02rem;
  --fs-title:1.06rem;  --fs-h3:1.25rem;   --fs-h2:1.6rem;   --fs-h1:2.1rem;   --fs-display:2.6rem;
  --lh-tight:1.14; --lh-snug:1.35; --lh-body:1.6; --tracking-caps:.07em;
  /* spacing — 4px base */
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px; --sp-6:24px; --sp-7:32px; --sp-8:48px;
  /* surface hierarchy + hairlines */
  --surface-ground:#F5F7FB; --surface-card:#FFFFFF; --surface-raised:#FFFFFF; --surface-sunken:#EEF1F8;
  --hairline:#E5E9F2; --hairline-2:#D9DEEC;
  /* THEME-AWARE foreground text (the app's --ink is a fixed light-mode value; use these
     for any component text that must read in BOTH themes) */
  --fg:#0F1626; --fg-muted:#5A6576; --fg-faint:#8A94A6;
  /* table-header ink — stronger than muted for a bolder, confident header that never
     reads "brighter" than the content; theme-aware (mixes toward --fg in both themes). */
  --th-fg:color-mix(in srgb, var(--fg) 62%, var(--fg-muted));
  /* semantic (fg + soft bg), theme-aware — for pills, variance, status. NOT the accent. */
  --good:#0E9F6E; --good-bg:#E6F6EF; --warn:#B7791F; --warn-bg:#FBF3E4;
  --bad:#D64545;  --bad-bg:#FBEBEB;  --info:#2563EB; --info-bg:#E9EEFC;
  /* elevation ladder (rest → hover → overlay) */
  --elev-1:0 1px 2px rgba(15,22,38,.05), 0 1px 1px rgba(15,22,38,.04);
  --elev-2:0 8px 24px -12px rgba(15,22,38,.16), 0 2px 6px -3px rgba(15,22,38,.10);
  --elev-3:0 30px 60px -28px rgba(15,22,38,.28), 0 8px 20px -12px rgba(79,70,229,.14);
  /* motion */
  --ease:cubic-bezier(.2,.7,.3,1); --dur-fast:.14s; --dur:.2s;
  /* canonical brand gradient */
  --kz-grad:linear-gradient(110deg,var(--kz),var(--kz2) 52%,var(--accent));
}
html[data-theme="dark"]{
  --surface-ground:#0B1120; --surface-card:#151C2C; --surface-raised:#1B2438; --surface-sunken:#0E1524;
  --hairline:#232D42; --hairline-2:#2E3A54;
  --fg:#E9EDF6; --fg-muted:#93A0B4; --fg-faint:#6C7A90;
  --good:#34D399; --good-bg:rgba(16,185,129,.14); --warn:#E7B24A; --warn-bg:rgba(231,178,74,.15);
  --bad:#F27272;  --bad-bg:rgba(239,68,68,.15);    --info:#8AB0FF; --info-bg:rgba(59,130,246,.16);
  --elev-1:0 1px 2px rgba(0,0,0,.4);
  --elev-2:0 10px 28px -14px rgba(0,0,0,.6), 0 2px 8px -4px rgba(0,0,0,.5);
  --elev-3:0 34px 70px -30px rgba(0,0,0,.75), 0 8px 22px -12px rgba(79,70,229,.4);
}
/* Tabular figures — add to ANY money/ratio/day value or numeric column so digits align. */
.fig, .tnum{ font-variant-numeric:tabular-nums; font-feature-settings:"tnum" 1; }
/* Kizuna icon (Lucide-style stroke set, self-hosted sprite) — <svg class="ki">…<use>. */
/* Phosphor Bold icons (Phase-0 uplift) — FILL-based, 256×256 viewBox, currentColor. */
.ki{ width:1.15em; height:1.15em; display:inline-block; vertical-align:-.18em; flex:0 0 auto;
  fill:currentColor; stroke:none; }
.ki-tile{ display:inline-grid; place-content:center; width:34px; height:34px; border-radius:10px;
  background:color-mix(in srgb, var(--kz) 12%, transparent); color:var(--kz); }
.ki-tile.grad{ background:var(--kz-grad); color:#fff; }
.ki-tile .ki{ width:18px; height:18px; }

/* ============================================================================
   PREMIUM COMPONENTS v1 (Phase 2, 2026-08-06). New OPT-IN classes built from the
   Foundations tokens — pages adopt them in Phase 3. Dark-paired via tokens; nothing
   existing is restyled. Reference gallery: /design/components. See kizuna-ui §4a.
   ========================================================================== */

/* --- Section header: icon tile + title + optional actions --- */
.kz-sec{ display:flex; align-items:center; gap:var(--sp-3); margin:0 0 var(--sp-4); }
.kz-sec__t{ font-family:var(--font-display); font-weight:700; letter-spacing:-.02em; font-size:var(--fs-h3); color:var(--fg); margin:0; line-height:1.15; }
.kz-sec__sub{ font-size:var(--fs-body); color:var(--fg-muted); font-weight:400; }
.kz-sec__act{ margin-left:auto; display:flex; align-items:center; gap:var(--sp-2); }

/* A TABLE TITLE AND ITS SUBHEADING ARE ALWAYS LEFT-ALIGNED (owner rule, 2026-08-12).
   A centred heading over a left-aligned grid gives the eye two different starting edges,
   so the title stops pointing at the data it names. Enforced structurally rather than
   per-template — `:has(table)` scopes it to headings that actually caption a table, so a
   deliberately centred page hero (checkout, plan pricing) is untouched.
   `start`, not `left`, so the rule still means "the reading edge" under any direction. */
.kz-sec__t, .kz-sec__sub{ text-align:start; }
:is(.card, section, .kz-sec):has(table) :is(.page-title, .kz-sec__t, .kz-sec__sub){ text-align:start; }

/* --- KPI tile (wrap in <a class="kz-kpi"> to drill down) --- */
.kz-kpi{ position:relative; background:var(--surface-card); border:1px solid var(--hairline); border-radius:var(--radius); padding:var(--sp-4); box-shadow:var(--elev-1); }
a.kz-kpi{ display:block; text-decoration:none; color:inherit; }
.kz-kpi__ic{ width:34px; height:34px; border-radius:10px; display:grid; place-content:center; color:#fff; margin-bottom:var(--sp-3); background:var(--kz-grad); }
.kz-kpi__ic .ki{ width:18px; height:18px; }
.kz-kpi__l{ font-size:var(--fs-label); letter-spacing:.08em; text-transform:uppercase; color:var(--fg-faint); font-weight:700; }
.kz-kpi__v{ font-family:var(--font-display); font-weight:700; font-size:1.5rem; letter-spacing:-.02em; margin:2px 0 3px; color:var(--fg); font-variant-numeric:tabular-nums; white-space:nowrap; overflow-wrap:normal; min-width:0; }
/* A KPI figure SHRINKS to fit its tile, it never clips and never breaks mid-number (owner
   screenshot 2026-09-09: "₹3,85,84,0" cut off beside the conversation dock). `ux.js`
   (kzFitText) sizes .kz-kpi__v and .stat-card .v down to a floor whenever the tile is too
   narrow — on load, on resize, when the dock opens, when a count-up finishes. */
.kz-kpi__d{ font-size:var(--fs-caption); color:var(--fg-muted); }
.kz-kpi__delta{ position:absolute; top:var(--sp-4); right:var(--sp-4); font-size:.72rem; font-weight:700; padding:2px 8px; border-radius:999px; font-variant-numeric:tabular-nums; }
.kz-kpi__delta.up{ color:var(--good); background:var(--good-bg); }
.kz-kpi__delta.down{ color:var(--bad); background:var(--bad-bg); }
@media (hover:hover) and (prefers-reduced-motion:no-preference){
  a.kz-kpi{ transition:transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease); }
  a.kz-kpi:hover{ transform:translateY(-2px); box-shadow:var(--elev-2); border-color:var(--hairline-2); }
}

/* --- Status pill --- */
.kz-pill{ display:inline-flex; align-items:center; gap:5px; font-size:.72rem; font-weight:700; letter-spacing:.01em; padding:3px 10px; border-radius:999px; white-space:nowrap; }
.kz-pill.is-good{ color:var(--good); background:var(--good-bg); }
.kz-pill.is-bad{ color:var(--bad); background:var(--bad-bg); }
.kz-pill.is-warn{ color:var(--warn); background:var(--warn-bg); }
.kz-pill.is-info{ color:var(--info); background:var(--info-bg); }
.kz-pill.is-mut{ color:var(--fg-muted); background:var(--surface-sunken); }
.req-dl dt{ color:var(--fg-muted); font-weight:600; font-size:.82rem; }
.req-dl dd{ margin-bottom:.55rem; }
a.kz-pill{ text-decoration:none; } a.kz-pill:hover{ filter:brightness(.95); }  /* a pill can be a filter link (requests inbox) */

/* --- Financial table (data grids + statements). Cells: .num right-aligns + tabular. --- */
.fin-table{ width:100%; border-collapse:separate; border-spacing:0; font-family:var(--bs-body-font-family); font-size:var(--fs-table); color:var(--fg); }
.fin-table thead th{ font-size:var(--fs-table-head); text-transform:uppercase; letter-spacing:var(--tracking-caps); color:var(--th-fg); font-weight:700; text-align:left; padding:11px 14px; background:var(--surface-sunken); border-bottom:1px solid var(--hairline); }
/* 🔴 MONEY READS FROM THE LEFT (owner decision 2026-09-07). Reverses the earlier
   right-aligned convention. The rule that survives is that a heading and its figures
   share ONE edge — both are left now, never one of each, which is the defect the
   owner reported in August. Digits stay tabular so they remain the same width. */
.fin-table th.num, .fin-table td.num{ text-align:left; font-variant-numeric:tabular-nums; white-space:nowrap; }
.fin-table tbody td, .fin-table tfoot td{ padding:12px 14px; border-bottom:1px solid var(--hairline); vertical-align:middle; }
.fin-table tbody td:first-child{ font-weight:500; }
/* Transposed (vertical) master grid — field labels run down the left column, one column
   per record. Keep labels aligned + readable and the record headers top-aligned. */
.kz-vtable thead th{ vertical-align:top; }
.kz-vtable .kz-vlabel{ color:var(--fg-muted); white-space:nowrap; }
/* Every input box in the transposed grid is left-aligned + full-width so they line up
   down each project column. Drop number spinners so number cells match text cells. */
.kz-vtable .ge-edit input, .kz-vtable .ge-edit select{ width:100%; text-align:left; }
.kz-vtable input[type=number]{ -moz-appearance:textfield; appearance:textfield; }
.kz-vtable input[type=number]::-webkit-outer-spin-button,
.kz-vtable input[type=number]::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }
/* Project & Channel — a project spans itself plus its wings (owner ask 2026-08-14).
   A left rule at each project's first column is what makes the grouping readable once a
   client runs several sites: without it, "A Wing" under Skyline and "A Wing" under Emerald
   are two identical-looking columns in one long row. */
.kz-vtable .kz-vgrp{ text-align:center; border-left:2px solid var(--hairline-2); }
.kz-vtable .kz-vsub{ border-left:1px solid var(--hairline); font-weight:600; }
.kz-vtable .kz-vgrp + .kz-vgrp{ border-left:2px solid var(--hairline-2); }
/* the project's own column reads as the anchor; its wings sit lighter beside it */
.kz-vtable .kz-vsub:first-of-type{ border-left:2px solid var(--hairline-2); }
.fin-table tbody tr:nth-child(even) td{ background:color-mix(in srgb, var(--fg) 3%, transparent); }
.fin-table.hover tbody tr:hover td, .fin-table tbody tr.hoverable:hover td{ background:color-mix(in srgb, var(--kz) 6%, transparent); }
.fin-table tr.tot td, .fin-table tfoot tr td{ background:color-mix(in srgb, var(--kz) 9%, transparent); font-weight:700; color:var(--fg); border-top:1.5px solid var(--hairline-2); border-bottom:0; }
.fin-table tr.grp td{ background:var(--surface-sunken); font-weight:700; text-transform:uppercase; font-size:var(--fs-label); letter-spacing:.05em; color:var(--fg-muted); }
.fin-table td.pos{ color:var(--good); font-weight:700; } .fin-table td.neg{ color:var(--bad); font-weight:700; }
.fin-bar{ position:relative; height:8px; border-radius:5px; background:var(--surface-sunken); min-width:110px; overflow:hidden; }
.fin-bar > i{ position:absolute; inset:0 auto 0 0; border-radius:5px; }
.fin-bar > i.over{ background:var(--bad); } .fin-bar > i.under{ background:var(--good); } .fin-bar > i.on{ background:var(--kz); }
.fin-var{ font-weight:700; font-variant-numeric:tabular-nums; white-space:nowrap; }
.fin-var.up{ color:var(--bad); } .fin-var.down{ color:var(--good); }
/* .flat = no zebra (for tables with interleaved collapse / rowspan / detail rows).
   Row states win over hover/zebra via td-level bg. */
.fin-table.flat tbody tr:nth-child(even) td{ background:transparent; }

/* 🔴 A FROZEN COLUMN INSIDE `.fin-table` GOES SEE-THROUGH ON EVERY EVEN ROW, and the
   page cannot fix it in its own <style>: the stripe above is `.fin-table tbody
   tr:nth-child(even) td` at (0,2,2) and a page's `.vnd-tbl .vnd-frz` is (0,2,0), so the
   stripe wins WHEREVER it is written and paints a 3%-alpha tint over nothing. The column
   still pins — the scrolling columns simply slide visibly through the frozen text.
   The Account Checklist hit this and fixed it for its own four classes; that left the next
   page (the vendor summary, 76 see-through cells) to rediscover it. So this is the SHARED
   answer: add `kz-frz` to any frozen cell and it stays opaque, striped or hovered, in both
   themes — `--surface-card` and `--surface-raised` are already theme-aware, so there is no
   dark-mode pairing to forget. Guard: testing/frozen_columns.py. */
.fin-table tbody tr td.kz-frz,
.fin-table tbody tr:nth-child(even) td.kz-frz{ background:var(--surface-card); }
.fin-table tbody tr:hover td.kz-frz{ background:var(--surface-raised, var(--surface-card)); }
.fin-table tr.row-warn td{ background:var(--warn-bg); }
.fin-table tr.row-mut td{ color:var(--fg-muted); }

/* --- Disclosure note: the detail a caption is NOT allowed to carry ---------------
   A table's subheading is one line about PURPOSE (kizuna-ui §9b1). The accounting
   mechanics, the import rules, the "where does this come from" — real information,
   just not caption material — live in here, shut by default and one click away.
   Usage: <details class="kz-note"><summary>How this is worked out</summary>…</details> */
.kz-note{ font-size:.85rem; color:var(--fg-muted); margin:.35rem 0 0; }
.kz-note > summary{ cursor:pointer; width:fit-content; list-style:none; color:var(--kz);
  font-weight:600; padding:2px 0; border-radius:6px; }
.kz-note > summary::-webkit-details-marker{ display:none; }
.kz-note > summary::before{ content:"\203A"; display:inline-block; margin-right:.4em;
  transition:transform var(--dur-fast) var(--ease); }
.kz-note[open] > summary::before{ transform:rotate(90deg); }
.kz-note > summary:hover{ text-decoration:underline; }
.kz-note__body{ padding:.4rem 0 .1rem; line-height:1.55; max-width:78ch; }
/* Rhythm inside a disclosure. Without these the last paragraph adds a stray gap and a list
   sits flush against the paragraph above it. */
.kz-note__body > :last-child{ margin-bottom:0; }
.kz-note__body ol, .kz-note__body ul{ margin-bottom:.65rem; }

/* ==== A disclosure that holds a BRIEF, not a footnote (2026-09-23) ====================
   `.kz-note` is 0.85rem muted text with no surface — right for a one-line aside, wrong for
   four steps of onboarding, which is what the owner was looking at: loose text floating
   beside an empty half-page while every other block on the screen sat in a card. */
/* 🔴 IT USES THE WHOLE PAGE, AND THE COLUMNS ARE WHAT MAKE THAT READABLE (owner,
   2026-09-23: "get the lines covers the page"). A full-width single column at this size is a
   95-character line, which is past the point where the eye reliably finds the next one; a
   narrow capped card left the right half of the page empty. Two columns give both — the
   panel fills its row and every line stays around 55 characters.
   ⚠ `auto-fit` + `minmax` rather than a media query, so it follows the CONTAINER: the page
   column narrows when the WhatsApp dock or a drawer opens WITHOUT a viewport resize, and a
   media query never fires for that (§`#main[data-w]`). One column on a phone, free. */
.kz-note--brief{ background:var(--surface-card); border:1px solid var(--border);
  border-radius:var(--radius); padding:1rem 1.15rem; box-shadow:var(--shadow-sm);
  font-size:.92rem; color:var(--fg); }
.kz-note--brief > summary{ font-size:1rem; }
.kz-note--brief .kz-note__body{ padding-top:.75rem; line-height:1.75; max-width:none; }
.kz-note--brief .kz-note__body p{ margin-bottom:.75rem; }
/* \U0001F534 THE CAP COMES OFF THE BODY AND GOES ON THE BODY'S OWN CHILDREN. Lifting it and
   putting nothing in its place is how two panels came to run at 166 characters \u2014 about
   twice what anyone tracks. A panel that declares `.kz-brief-cols` is exempt: its children
   sit inside that grid, so the COLUMN width is the measure and this selector cannot reach
   them. One rule, both directions. (36rem measures ~80 characters here; see the note below
   on why the cap is in `rem` and never `ch`.) */
/* \U0001F534 `:not(.kz-steps)` — THE CAP IS FOR A LINE OF TEXT, AND `.kz-steps` IS A GRID
   that happens to be an <ol>. Without this the 36rem meant for a measure applied to the
   whole two-column step layout and held it to 461px inside a 1641px card, wrapping every
   step after five words while the paragraphs below it filled the row (owner-reported on
   production, 2026-09-24). Anything that LAYS OUT ITS CHILDREN is exempt by the same
   reasoning; add it here rather than removing the cap. */
.kz-note--brief .kz-note__body > p,
.kz-note--brief .kz-note__body > ul,
.kz-note--brief .kz-note__body > ol:not(.kz-steps){ max-width:36rem; }
/* The prose columns, paired with `.kz-steps` below.
   \u26a0 `minmax(0,1fr)`, NEVER a bare `1fr`, and `min-width:0` on the children (\u00a79q). A
   `1fr` track keeps `min-width:auto` and refuses to shrink below its content's min-content
   width \u2014 a `white-space:pre` example inside one is a 60-character line that cannot wrap,
   and it pushed this grid 119px past a 390px phone. Third time this rule has been re-learned
   here. */
.kz-brief-cols{ display:grid; grid-template-columns:minmax(0,1fr); gap:0 2.5rem;
  align-items:start; }
.kz-brief-cols > *{ min-width:0; }

/* ==== Numbered steps with a real gutter ==============================================
   \U0001F534 THE NUMERAL, THE TITLE AND THE BODY EACH GET THEIR OWN PLACE. A plain <ol> runs
   all three together on one line, so there is no left edge to read down and a step's wrapped
   second line starts under its own title. Here the numeral sits in a fixed gutter, the bold
   lead-in is its own line, and every step's text begins at the same x. */
.kz-steps{ list-style:none; counter-reset:kzstep; margin:0 0 1rem; padding:0;
  display:grid; grid-template-columns:1fr; gap:0 2.5rem; align-items:start; }
.kz-steps > li{ counter-increment:kzstep; position:relative; padding-left:2.4rem;
  margin-bottom:.9rem; }
.kz-steps > li:last-child{ margin-bottom:.1rem; }
.kz-steps > li::before{ content:counter(kzstep); position:absolute; left:0; top:0;
  width:1.6rem; height:1.6rem; border-radius:999px; display:grid; place-content:center;
  font-size:.74rem; font-weight:700; line-height:1;
  color:var(--kz); background:color-mix(in srgb, var(--kz) 13%, transparent); }
.kz-steps > li > b:first-child, .kz-steps > li > strong:first-child{
  display:block; color:var(--fg); margin-bottom:.1rem; }
html[data-theme="dark"] .kz-steps > li::before{ color:#C7D2FE; background:rgba(165,180,252,.18); }

/* A control with a sentence explaining it: the sentence is a BLOCK beside the button, so it
   wraps as its own column instead of starting on the button's line and flowing under it. */
/* \U0001F534 TWO COLUMNS ACROSS THE FULL PAGE — the owner's own pick from three mock-ups.
   The panel fills its row and each line still lands near 80 characters, because the column
   width sets the measure rather than a cap on a full-width line. One full-width column at
   this page size is 153 characters, which is roughly twice what anyone tracks comfortably.
   \u26a0 Driven by `#main[data-w]`, never `auto-fit` + `minmax`: auto-fit put THREE tracks in
   this column (four steps in a ragged 3 + 1) and its minimum track cannot shrink, so a phone
   scrolled sideways. `data-w` follows the page column's own width, so the WhatsApp dock or a
   drawer narrowing the page folds this to one column — which a media query never sees. */
#main[data-w="wide"] .kz-steps,
#main[data-w="wide"] .kz-brief-cols{ grid-template-columns:minmax(0,1fr) minmax(0,1fr); }
/* ⚠ In ONE column (tablet, or the page narrowed by the dock) the line has to be capped
   or it runs to 98 characters. 36rem measures at 80 on the page — the same as the two-column
   case, so the panel reads identically at every width. A phone is 44 either way.
   🔴 The cap is in `rem`, NOT `ch`: `1ch` is the width of the ZERO glyph (9.66px here
   against a 14.7px font) while an average letter in prose is about half the font size. So
   `max-width:72ch` is ~97 characters of real text — it once equalled the container exactly
   and silently did nothing, while computed style dutifully reported the rule as applied. */
/* \u26a0 `> *`, not `> p`. A column may be a WRAPPER holding a paragraph, an example and a
   list \u2014 which is what a long explainer needs \u2014 and a `> p` selector caps nothing in that
   case, so the single-column layout silently ran to full width.
   \U0001F534 CORRECTION (2026-09-24): this block used to end "the two existing callers pass
   paragraphs directly and are unaffected". That was wrong and shipped a visible fault. The
   body-level cap above reaches `.kz-steps` \u2014 an <ol> that is a GRID \u2014 and squeezed the
   Process Sheet's four steps into a third of their card. A cap for a measure must only
   reach a text container; see the `:not(.kz-steps)` note above. */
#main:not([data-w="wide"]) .kz-steps > li,
#main:not([data-w="wide"]) .kz-brief-cols > *{ max-width:36rem; }

/* A marker list inside a brief. \u26a0 The class exists so the sizing is not an inline style
   on the template \u2014 an inline `font-size` cannot be answered by the dark-mode or print
   layers, and it is invisible to any sweep that reads the stylesheet. */
.kz-marks{ padding-left:1.15rem; margin-bottom:.35rem; }
.kz-marks > li{ margin-bottom:.4rem; }
.kz-marks > li:last-child{ margin-bottom:0; }
/* The worked example. It is CODE, so it never wraps \u2014 a soft-wrapped sample line looks
   like two steps, which is the one confusion this panel exists to prevent. */
.kz-eg{ font-size:.8rem; line-height:1.7; background:var(--surface-sunken);
  border:1px solid var(--hairline); border-radius:10px; padding:9px 11px;
  overflow-x:auto; white-space:pre; margin-bottom:.75rem; }
html[data-theme="dark"] .kz-eg{ background:var(--surface-raised);
  border-color:var(--hairline-2); }

/* A table's control bar: the search field keeps its width and no label may break mid-phrase.
   \u26a0 `flex-shrink-0` on the GROUP is not enough — it stops the group wrapping and does
   nothing about the text inside each child, so "8 total" still broke into two lines. */
/* ==== The confirmation dialog — `.kz-confirm` (2026-09-24) =============================
   Every destructive action in the product goes through this one component, and until now it
   had NO styling of its own: `.modal-content` straight from Bootstrap, a loose warning glyph
   and two default buttons. It is the most-seen surface after the sidebar.
   \U0001F534 IT HAS A TONE. A delete and a sign-out are not the same question; a dialog that
   looks identical for both teaches people to click through it without reading. `data-tone`
   is set from the trigger (an outline-danger button, a `.ge-del`, or wording like "cannot be
   undone") and drives the capsule, the glyph and the primary button.
   \u26a0 Behaviour is untouched — `.cm-msg` and `.cm-ok` are the same hooks base.html has
   always used. This is the design, not a rewrite of the control that guards every delete. */
.kz-confirm .modal-dialog{ max-width:452px; }
.kz-confirm .modal-content{
  position:relative; border:1px solid var(--hairline); border-radius:18px;
  background:var(--surface-card); padding:22px 22px 18px;
  box-shadow:0 1px 2px rgba(10,14,25,.06), 0 18px 48px -12px rgba(10,14,25,.28); }
.kz-confirm .kzc-grab{ display:none; }
.kz-confirm .kzc-body{ display:flex; gap:14px; align-items:flex-start; }
.kz-confirm .kzc-ic{
  flex:0 0 44px; width:44px; height:44px; border-radius:14px;
  display:grid; place-content:center; font-size:1.15rem; }
.kz-confirm .kzc-txt{ min-width:0; }
.kz-confirm .kzc-title{
  font-family:var(--font-display); font-size:1.06rem; font-weight:700; letter-spacing:-.01em;
  color:var(--fg); margin:1px 0 5px; }
/* \u26a0 A measure on the message. Left to the dialog's full width a two-line question sets
   as one long line and reads as a warning banner rather than a question. */
.kz-confirm .kzc-msg{
  font-size:.92rem; line-height:1.55; color:var(--fg-muted); margin:0; max-width:44ch; }
.kz-confirm .kzc-foot{ display:flex; justify-content:flex-end; gap:8px; margin-top:20px; }
.kz-confirm .kzc-foot .btn{ min-width:96px; font-weight:600; }
.kz-confirm .kzc-cancel{
  background:var(--surface-sunken); border:1px solid var(--border); color:var(--fg); }
@media (hover:hover){ .kz-confirm .kzc-cancel:hover{ border-color:var(--hairline-2); } }

/* the tones */
.kz-confirm[data-tone="warn"] .kzc-ic{
  background:color-mix(in srgb, var(--warn) 15%, transparent); color:var(--warn); }
.kz-confirm[data-tone="danger"] .kzc-ic{
  background:color-mix(in srgb, var(--bad) 15%, transparent); color:var(--bad); }
.kz-confirm[data-tone="ask"] .kzc-ic{
  background:color-mix(in srgb, var(--kz) 14%, transparent); color:var(--kz); }
/* \U0001F534 A destructive primary must LOOK destructive. The brand gradient on a Delete
   button reads as the safe choice, which is the opposite of true. */
.kz-confirm[data-tone="danger"] .cm-ok{
  background:var(--bad); border-color:var(--bad); color:#fff; background-image:none; }
@media (hover:hover){
  .kz-confirm[data-tone="danger"] .cm-ok:hover{ filter:brightness(1.06); } }

/* the scrim, and an entrance that settles rather than pops */
.kz-confirm.modal .modal-dialog{ transform:translateY(8px) scale(.985); }
.kz-confirm.modal.show .modal-dialog{ transform:none; }
.kz-confirm .modal-dialog{ transition:transform .19s cubic-bezier(.2,.8,.2,1); }
.modal-backdrop.show{ opacity:1; background:rgba(9,12,22,.5); backdrop-filter:blur(5px); }
@media (prefers-reduced-motion:reduce){
  .kz-confirm .modal-dialog{ transition:none; transform:none !important; }
  .modal-backdrop.show{ backdrop-filter:none; } }

html[data-theme="dark"] .kz-confirm .modal-content{
  background:var(--surface-raised); border-color:var(--hairline-2);
  box-shadow:0 1px 2px rgba(0,0,0,.4), 0 22px 56px -14px rgba(0,0,0,.66); }
html[data-theme="dark"] .kz-confirm .kzc-cancel{
  background:rgba(255,255,255,.05); border-color:var(--hairline-2); }
/* 🔴 `--bad` IN DARK MODE IS A TEXT COLOUR, NOT A BUTTON FILL. It is a light red
   chosen to read ON a dark surface; used as a background behind white text it came out pale
   pink at 2.0:1 -- a destructive button that looks weaker than Cancel, which is the exact
   opposite of what it must convey. A filled destructive control needs a deep red in both
   themes; only the TINT behind the icon keeps the light token. */
html[data-theme="dark"] .kz-confirm[data-tone="danger"] .cm-ok{
  background:#C9342F; border-color:#C9342F; color:#fff; }
html[data-theme="dark"] .modal-backdrop.show{ background:rgba(4,7,14,.62); }

/* On a phone it docks to the bottom edge with a grab handle, which is how every current
   mobile product asks this question — a centred box there is a desktop habit. */
@media (max-width:480px){
  .kz-confirm .modal-dialog{ margin:0; position:fixed; left:0; right:0; bottom:0;
    max-width:none; transform:translateY(14px); }
  .kz-confirm.modal.show .modal-dialog{ transform:none; }
  .kz-confirm .modal-content{ border-radius:20px 20px 0 0; padding:16px 18px calc(18px + env(safe-area-inset-bottom)); }
  .kz-confirm .kzc-grab{ display:block; width:38px; height:4px; border-radius:99px;
    background:var(--border); margin:0 auto 14px; }
  .kz-confirm .kzc-foot{ margin-top:18px; }
  .kz-confirm .kzc-foot .btn{ flex:1; }
}

/* ==== The Financial Management System's four sheets — `.fss-tbl` (2026-09-24) ========
   Week 9 hands the client four printed sheets. These are those sheets on screen, built on
   the `.tb-tbl` frozen grid every finance register already uses — same header band, same
   sticky first column, same money face — so a client moving between the Trial Balance and
   the F.S.S. is not learning a second table.
   🔴 THE PLAN/ACTUAL PAIR IS THE WHOLE POINT, so it is drawn as a pair: one tint across
   both cells of a week, a hairline between the weeks and not inside them. Tinting Pln and
   Act separately made them read as eight unrelated columns rather than four comparisons. */
.fss-wrap{ position:relative; }
.fss-tbl{ table-layout:fixed; width:max-content; min-width:100%; }
.fss-tbl th, .fss-tbl td{ padding:5px 8px; font-size:.82rem; }
/* ⚠ The label column is FROZEN, so its width and this offset are one decision. A
   statement is unreadable the moment "Revenue" scrolls off — you are then looking at a
   number with nothing to say what it is. */
.fss-tbl .fss-c1{
  position:sticky; left:0; z-index:3; width:212px; min-width:212px; max-width:212px;
  text-align:left; font-weight:600; font-size:.84rem;
  background:var(--surface-card); overflow-wrap:anywhere;
  box-shadow:1px 0 0 var(--hairline); }
.fss-tbl thead .fss-c1{ z-index:5; background:var(--surface-sunken); }
html[data-theme="dark"] .fss-tbl .fss-c1{ background:var(--surface-raised); }
html[data-theme="dark"] .fss-tbl thead .fss-c1{ background:var(--surface-sunken); }
.fss-tbl td, .fss-tbl th.num{ text-align:left; }
.fss-tbl td.v{ font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  white-space:nowrap; }
.fss-tbl .mcol{ width:104px; min-width:104px; }
.fss-tbl .pcol{ width:98px; min-width:98px; }
/* the pair tint: one wash per WEEK, the rule between weeks */
.fss-tbl .wk-a{ background:color-mix(in srgb, var(--kz) 5%, transparent); }
.fss-tbl .wk-s{ box-shadow:inset 1px 0 0 var(--hairline); }
/* row roles — a band names a block, a strong row is where a column reaches a conclusion */
.fss-tbl tr.band td{
  background:color-mix(in srgb, var(--kz) 11%, var(--surface-card));
  font-weight:700; text-transform:uppercase; letter-spacing:.05em; font-size:.72rem; }
.fss-tbl tr.band .fss-c1{ background:color-mix(in srgb, var(--kz) 11%, var(--surface-card)); }
html[data-theme="dark"] .fss-tbl tr.band td,
html[data-theme="dark"] .fss-tbl tr.band .fss-c1{
  background:color-mix(in srgb, var(--kz) 22%, var(--surface-raised)); }
.fss-tbl tr.sub .fss-c1{ font-weight:700; }
.fss-tbl tr.strong td{ font-weight:700;
  background:color-mix(in srgb, var(--fg) 5%, transparent); }
.fss-tbl tr.strong .fss-c1{ background:color-mix(in srgb, var(--fg) 5%, var(--surface-card)); }
html[data-theme="dark"] .fss-tbl tr.strong .fss-c1{
  background:color-mix(in srgb, var(--fg) 5%, var(--surface-raised)); }
.fss-tbl tr.ind .fss-c1{ padding-left:22px; font-weight:500; }
.fss-tbl .neg{ color:var(--bad); }
.fss-tbl input{ width:100%; border:0; background:transparent; padding:2px 4px;
  font-family:var(--font-mono); font-size:.8rem; text-align:left; border-radius:6px; }
/* \U0001F534 ...BUT A CELL YOU ARE MEANT TO FILL MUST LOOK FILLABLE, IN BOTH THEMES. The
   seamless rule above is right for a sheet that should read like paper \u2014 and in LIGHT mode
   it made every editable box invisible: white on white, so the client saw an empty column
   and nothing to click ("there no box is visible to enter date", owner-reported
   2026-09-25). In DARK it happened to look fine, because the page's own fill showed through.
   A faint tint and a dashed underline keep the sheet calm and still say "write here"; a
   genuinely read-only cell keeps the flat look, so the two now differ on purpose rather
   than by theme. */
.fss-tbl input:not([readonly]):not([disabled]):not([type="checkbox"]){
  background:color-mix(in srgb, var(--kz) 5%, transparent);
  border-bottom:1px dashed var(--hairline);
  border-radius:5px; }
@media (hover:hover){
  .fss-tbl input:not([readonly]):not([disabled]):not([type="checkbox"]):hover{
    background:color-mix(in srgb, var(--kz) 10%, transparent);
    border-bottom-color:var(--kz); } }
.fss-tbl input:not([readonly]):not([disabled]):focus{
  background:var(--surface-card);
  outline:2px solid var(--kz); outline-offset:-1px; border-bottom-color:transparent; }
/* the hint in an empty cell is a hint, not a value */
.fss-tbl input::placeholder{ color:var(--fg-faint); opacity:1; font-style:italic; }
.fss-tbl input:focus{ background:var(--surface-card); outline:2px solid var(--kz);
  outline-offset:-2px; }
html[data-theme="dark"] .fss-tbl input:focus{ background:var(--surface-sunken); }
/* the quarter and year roll-ups sit apart from the months they summarise */
.fss-tbl .qcol{ background:color-mix(in srgb, var(--accent) 7%, transparent);
  box-shadow:inset 1px 0 0 var(--hairline-2); font-weight:600; }
.fss-tbl .ycol{ background:color-mix(in srgb, var(--kz) 9%, transparent);
  box-shadow:inset 1px 0 0 var(--hairline-2); font-weight:700; }
.fss-tbl thead th.qcol, .fss-tbl thead th.ycol{ font-weight:700; }
/* a note under a sheet, for what the numbers cannot say themselves */
.fss-note{ font-size:.82rem; color:var(--fg-muted); }
.fss-note b{ color:var(--fg); }

/* The variance chip — plan against actual, on a row that has both. */
.fss-var{ font-family:var(--font-mono); font-size:.72rem; font-weight:700;
  padding:1px 5px; border-radius:6px; white-space:nowrap; }
.fss-var.good{ color:var(--good); background:color-mix(in srgb, var(--good) 14%, transparent); }
.fss-var.bad{ color:var(--bad); background:color-mix(in srgb, var(--bad) 14%, transparent); }
.fss-var.flat{ color:var(--fg-muted); background:var(--surface-sunken); }

@media (max-width:991.98px){
  /* One frozen column is a help; 212px of frozen label on a 390px phone covers the screen
     and the figures it exists to explain become unreachable. */
  .fss-tbl .fss-c1{ width:150px; min-width:150px; max-width:150px; }
}

/* ==== The process builder — `.pb-*` (2026-09-24) ====================================
   Write the whole process in one box, with the flowchart redrawing beside you.
   🔴 The two columns are driven by `#main[data-w]`, NOT by a viewport media query: the
   WhatsApp dock and the booking drawer narrow the page column WITHOUT a viewport resize, so
   a media query never fires and the editor would keep a two-column layout at 420px. */
.pb-grid{ display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:0; align-items:stretch; border-top:1px solid var(--hairline); }
#main[data-w="mid"] .pb-grid, #main[data-w="narrow"] .pb-grid{ grid-template-columns:minmax(0,1fr); }
@media (max-width:991.98px){ .pb-grid{ grid-template-columns:minmax(0,1fr); } }
.pb-edit{ display:flex; flex-direction:column; min-width:0; padding:14px 16px; }
.pb-view{ display:flex; flex-direction:column; min-width:0;
  border-left:1px solid var(--hairline); background:var(--surface-sunken); }
#main[data-w="mid"] .pb-view, #main[data-w="narrow"] .pb-view{
  border-left:0; border-top:1px solid var(--hairline); }
@media (max-width:991.98px){ .pb-view{ border-left:0; border-top:1px solid var(--hairline); } }

/* ⚠ The box is MONOSPACED and does not wrap. A step is one line, and a line that soft-wraps
   looks exactly like two steps — which, in an editor where a line IS a step, is the one
   confusion that costs real work. It scrolls sideways instead. */
.pb-box{ width:100%; min-height:340px; resize:vertical;
  font-family:var(--font-mono); font-size:.86rem; line-height:1.75;
  white-space:pre; overflow-wrap:normal; overflow-x:auto;
  border:1px solid var(--border); border-radius:var(--radius-sm);
  background:var(--surface-card); color:var(--fg); padding:12px 14px; }
.pb-box:focus{ outline:none; border-color:var(--kz);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--kz) 22%, transparent); }
html[data-theme="dark"] .pb-box{ background:var(--surface-raised); border-color:var(--hairline-2); }

/* The shape toolbar. It acts on the line the cursor is on, so the client writes prose and
   presses a button — `[]` and `|` stay the file format and stop being a user interface. */
.pb-tools{ display:flex; flex-wrap:wrap; align-items:center; gap:.35rem;
  margin-bottom:.6rem; }
.pb-tools__l{ font-size:.78rem; font-weight:700; color:var(--fg-muted);
  text-transform:uppercase; letter-spacing:.04em; margin-right:.2rem; }
.pb-tools__k{ font-size:.72rem; color:var(--fg-faint); margin-left:auto; white-space:nowrap; }
.pb-shape{ display:inline-flex; align-items:center; gap:.35rem;
  border:1px solid var(--border); background:var(--surface-card); color:var(--fg-muted);
  border-radius:999px; padding:.3rem .65rem; font-size:.78rem; font-weight:600;
  cursor:pointer; white-space:nowrap; }
@media (hover:hover){ .pb-shape:hover{ border-color:var(--kz); color:var(--kz); } }
/* \U0001F534 `aria-pressed` IS the styling hook, so the control cannot LOOK selected while
   telling a screen reader it is not. One attribute, one appearance. */
.pb-shape[aria-pressed="true"]{
  background:color-mix(in srgb, var(--kz) 14%, var(--surface-card));
  border-color:var(--kz); color:var(--kz); }
html[data-theme="dark"] .pb-shape{ background:var(--surface-raised);
  border-color:var(--hairline-2); }
html[data-theme="dark"] .pb-shape[aria-pressed="true"]{
  background:rgba(165,180,252,.18); border-color:#A5B4FC; color:#C7D2FE; }

/* The shape itself, drawn rather than iconised — no sprite symbol to fail to resolve. */
.pb-ico{ width:13px; height:11px; flex:0 0 13px; display:block;
  border:1.5px solid currentColor; background:transparent; }
.pb-ico--start, .pb-ico--finish{ border-radius:999px; }
.pb-ico--decision{ border:0; background:currentColor; height:12px;
  clip-path:polygon(50% 0, 100% 50%, 50% 100%, 0 50%); }
.pb-ico--document{ border-radius:2px 2px 0 0;
  border-bottom-style:dashed; }
.pb-ico--delay{ border-radius:0 999px 999px 0; }
.pb-ico--subprocess{ border-left-width:3.5px; border-right-width:3.5px; }

.pb-inferred{ font-size:.8rem; color:var(--fg-muted); margin:10px 0 0;
  padding:7px 10px; border-radius:8px;
  background:color-mix(in srgb, var(--info) 11%, transparent); }
.pb-inferred b{ color:var(--fg); }

@media (max-width:575.98px){
  .pb-tools__k{ display:none; }          /* a shortcut hint on a phone is noise */
  .pb-shape{ padding:.28rem .5rem; font-size:.74rem; }
}

.pb-stats{ display:flex; flex-wrap:wrap; gap:.4rem .9rem; margin-top:10px;
  font-size:.8rem; color:var(--fg-muted); }
.pb-stat b{ color:var(--fg); font-family:var(--font-mono); }
.pb-issues{ margin-top:10px; display:flex; flex-direction:column; gap:5px; }
.pb-issue{ font-size:.8rem; color:var(--fg-muted); padding:5px 9px; border-radius:8px;
  background:var(--surface-sunken); }
html[data-theme="dark"] .pb-issue{ background:var(--surface-raised); }
/* 🔴 A structural fault is NOT an unfinished field, and colouring them alike is what
   makes a panel like this ignorable — the same rule `.pr-issues` states. */
.pb-issue.is-bad{ color:var(--bad); font-weight:600;
  background:color-mix(in srgb, var(--bad) 12%, transparent); }

.pb-viewhead{ display:flex; align-items:center; justify-content:space-between; gap:.5rem;
  padding:10px 16px; border-bottom:1px solid var(--hairline);
  font-size:.78rem; font-weight:700; text-transform:uppercase; letter-spacing:.05em;
  color:var(--th-fg); }
.pb-live{ font-weight:500; text-transform:none; letter-spacing:0; color:var(--fg-faint); }
/* The chart column follows the page rather than the text: a long process must not make the
   editor scroll past the drawing it is describing. */
.pb-canvas{ padding:12px; overflow:auto; max-height:min(72vh, 720px); }
.pb-canvas svg{ display:block; }

@media (max-width:575.98px){
  .pb-edit{ padding:12px; }
  .pb-box{ min-height:260px; font-size:.82rem; }
  .pb-canvas{ max-height:52vh; }
}

.kz-tablebar{ display:flex; align-items:center; gap:.5rem; flex-wrap:wrap; }
/* \U0001F534 `white-space:nowrap` on the CHILDREN is what stops "8 total" breaking in half.
   `flex-shrink:0` on the BAR does nothing for that and is actively harmful: it held the bar
   at its 543px content width on a 375px phone and pushed the page 66px sideways. */
.kz-tablebar > .form-control{ flex:1 1 200px; min-width:0; max-width:260px; }
.kz-tablebar .btn, .kz-tablebar .kz-count{ white-space:nowrap; flex:none; }
.kz-count{ font-size:.82rem; color:var(--fg-muted); }
/* A row hidden by a status chip. Its OWN class, never the search primitive's `kz-fl-hide`:
   a row is on screen only when NEITHER has hidden it. */
tr.pr-chip-hide{ display:none; }

.kz-actline{ display:flex; align-items:flex-start; gap:.75rem; flex-wrap:wrap; }
.kz-actline > .btn{ flex:none; }
.kz-actline__t{ flex:1 1 18rem; min-width:0; font-size:.86rem; color:var(--fg-muted);
  align-self:center; }
html[data-theme="dark"] .kz-note > summary{ color:#A5B4FC; }
@media (prefers-reduced-motion:reduce){ .kz-note > summary::before{ transition:none; } }

/* --- Skeleton loader --- */
.skeleton{ display:block; border-radius:8px; background:var(--surface-sunken); position:relative; overflow:hidden; }
.skeleton::after{ content:""; position:absolute; inset:0; transform:translateX(-100%); background:linear-gradient(90deg, transparent, color-mix(in srgb, var(--fg) 7%, transparent), transparent); }
@media (prefers-reduced-motion:no-preference){ .skeleton::after{ animation:kz-shimmer 1.3s ease-in-out infinite; } }
@keyframes kz-shimmer{ 100%{ transform:translateX(100%); } }

/* ============================================================================
   NATIVE BROWSER FURNITURE -> OURS (owner ask 2026-09-01)
   Chrome's own scrollbars, file button, spinners, selection and focus ring are
   the loudest "this is a web page, not a product" signals in the whole app —
   they appear on EVERY screen and no template can fix them. All token-built, so
   dark mode comes free and one edit retunes the system.
   ============================================================================ */

/* --- Scrollbars. The default is a chunky grey slab that dates the whole UI. --- */
*{ scrollbar-width:thin; scrollbar-color:color-mix(in srgb, var(--fg) 22%, transparent) transparent; }
::-webkit-scrollbar{ width:11px; height:11px; }
::-webkit-scrollbar-track{ background:transparent; }
::-webkit-scrollbar-thumb{ background:color-mix(in srgb, var(--fg) 20%, transparent);
  border-radius:99px; border:3px solid transparent; background-clip:content-box; }
::-webkit-scrollbar-thumb:hover{ background:color-mix(in srgb, var(--kz) 55%, var(--fg));
  border:3px solid transparent; background-clip:content-box; }
::-webkit-scrollbar-corner{ background:transparent; }

/* --- Checkboxes, radios, progress: one line, brand ink everywhere. --- */
:root{ accent-color:var(--kz); }

/* --- The file input. Chrome's grey "Choose File" slab is unmistakably default. --- */
input[type=file]{ padding:5px 8px; }
input[type=file]::file-selector-button{
  font:inherit; font-weight:600; font-size:.82rem; cursor:pointer; margin-inline-end:10px;
  padding:5px 12px; border-radius:9px; border:1px solid var(--hairline);
  background:var(--surface-sunken); color:var(--fg);
  transition:background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }
input[type=file]::file-selector-button:hover{
  background:color-mix(in srgb, var(--kz) 10%, var(--surface-card));
  border-color:var(--kz); color:var(--kz); }

/* --- Number spinners: they crowd a money cell and nothing in this app uses them. --- */
input[type=number]{ -moz-appearance:textfield; }
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }

/* --- Text selection in the brand, not the browser's blue. --- */
::selection{ background:color-mix(in srgb, var(--kz) 24%, transparent); color:var(--fg); }

/* --- A native <details> marker is a black triangle; ours is a chevron (see .kz-note). --- */
details > summary::-webkit-details-marker{ display:none; }

/* ============================================================================
   MOTION — the difference between "a page appeared" and "a product responded".
   Everything here is gated on prefers-reduced-motion and costs one class.
   ============================================================================ */
/* 🔴 NO PAGE-ENTRY ANIMATION, and this is a decision, not an omission (2026-09-01).
   A staggered `kz-rise` on `.page-wrap > *` was built here and removed the same day for two
   faults, either of which is worse than the polish was worth:
   1. An element with a filling transform/opacity animation is a STACKING CONTEXT, so a
      dropdown opened inside a card could not escape it and the NEXT card painted over the
      menu — owner-reported with a screenshot of the Services picker sliced in half.
   2. Chrome PAUSES animations in a background tab. With any fill-mode the cards hold the
      from-state (`opacity:0`), so a page opened in a background tab renders BLANK until it
      is focused. Measured: `opacity:0` still computed 1.4s after load in a hidden tab.
   Motion in this product lives on things the client actually drives — hover, focus, the
   dropdown opening, the tooltip, the button press. Not on arrival. */
/* A card answers the pointer. Hover only — never on touch, where there is no hover. */
@media (hover:hover){
  .card{ transition:box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease), border-color var(--dur) var(--ease); }
  .card:hover{ box-shadow:var(--elev-2); }
}

/* ============================================================================
   ACCENTED SURFACES — colour that MEANS something, not a wash.
   `.kz-accent` puts a 3px brand gradient on a card's top edge: use it on the one
   card in a view that is the point of the view. `.kz-accent--good/warn/bad` tint
   it for a state. Deliberately sparing: if every card is accented, none is.
   ============================================================================ */
.kz-accent{ position:relative; overflow:hidden; }
.kz-accent::before{ content:""; position:absolute; inset:0 0 auto 0; height:3px;
  background:var(--kz-grad); }
.kz-accent--good::before{ background:linear-gradient(90deg, var(--good), color-mix(in srgb, var(--good) 40%, var(--accent))); }
.kz-accent--warn::before{ background:linear-gradient(90deg, var(--warn), color-mix(in srgb, var(--warn) 45%, var(--bad))); }
.kz-accent--bad::before{ background:linear-gradient(90deg, var(--bad), color-mix(in srgb, var(--bad) 45%, var(--warn))); }

/* A service name that opens the product (Service Logins, 2026-09-01). */
.svc-open{ color:var(--fg); text-decoration:none; border-radius:7px;
  transition:color var(--dur-fast) var(--ease); }
.svc-open:hover, .svc-open:focus-visible{ color:var(--kz); text-decoration:underline; }
html[data-theme="dark"] .svc-open:hover, html[data-theme="dark"] .svc-open:focus-visible{ color:#A5B4FC; }

/* --- The grid action bar: Edit / Save / Select (owner ask 2026-09-01) ---
   The row under an editable grid carries the four controls that decide what happens to the
   client's data. It was a bare right-aligned button row; it is a TOOLBAR now, and it says
   which mode the grid is in — an editable grid that looks identical whether it is locked or
   not is the reason "press Done then Save" went wrong before (§5). `.kz-gridbar` is applied
   by ux.js to whatever container holds the toggle, so no template needed changing. */
/* 🔴 `flex-wrap` belongs HERE, not in each template. The Sales register's bar had none and
   its four controls collided on a phone (owner-reported at 430px); the Trial Balance's was
   fine only because that template happened to include the class. One rule, every bar. */
.kz-gridbar{ flex-wrap:wrap; border-radius:14px; padding:8px 10px; background:var(--surface-sunken);
  border:1px solid transparent; transition:background var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.kz-gridbar.is-editing{ background:color-mix(in srgb, var(--kz) 7%, transparent);
  border-color:color-mix(in srgb, var(--kz) 26%, transparent); }
/* The pencil sits ON the button's baseline, not floating above it. */
.kz-editbtn{ display:inline-flex; align-items:center; gap:7px; font-weight:600; }
.kz-editbtn .ki{ width:1em; height:1em; }
.kz-gridbar .btn{ display:inline-flex; align-items:center; gap:6px; }
/* While a grid is unlocked, its own frame says so — you can see you are in edit mode from
   anywhere on the page, not only from the button. */
.grid-editing{ box-shadow:0 0 0 2px color-mix(in srgb, var(--kz) 22%, transparent); border-radius:12px; }
@media (prefers-reduced-motion:reduce){ .kz-gridbar{ transition:none; } }

/* The scroll hint. `ux.js` adds `.kz-scrollx` to any scroller that actually overflows and
   keeps `.kz-atend` in step, so the hint states a fact rather than a guess — and it stops
   being shown the moment the client has scrolled to the end. */
.kz-swipe{ display:none; }
.kz-scrollx + .kz-swipe{ display:flex; align-items:center; justify-content:center; gap:7px;
  padding:7px 10px; font-size:.76rem; font-weight:600; color:var(--kz);
  background:color-mix(in srgb, var(--kz) 7%, transparent);
  border-top:1px solid var(--hairline); }
.kz-scrollx.kz-atend + .kz-swipe{ display:none; }
html[data-theme="dark"] .kz-scrollx + .kz-swipe{ color:#A5B4FC;
  background:color-mix(in srgb, #A5B4FC 10%, transparent); }
/* An edge shade, so the table LOOKS like it continues rather than like it was cut off. */
.kz-scrollx{ position:relative; }
.kz-scrollx::after{ content:""; position:sticky; top:0; right:0; float:right; width:26px;
  height:100%; margin-left:-26px; pointer-events:none;
  background:linear-gradient(90deg, transparent, color-mix(in srgb, var(--fg) 13%, transparent)); }
.kz-scrollx.kz-atend::after{ display:none; }
@media (prefers-reduced-motion:no-preference){
  .kz-scrollx + .kz-swipe .ki{ animation:kz-nudge 1.6s var(--ease) infinite; }
  @keyframes kz-nudge{ 0%,100%{ transform:translateX(0) } 55%{ transform:translateX(4px) } }
}

/* --- KPI cards: ONE PER ROW on a phone (owner-reported 2026-09-02) ---
   Measured at 430px with the owner's real figure: the value box is 121px and
   "₹ 5,36,00,00,000" needs 148px — so every card on the dashboard was cutting the number
   it exists to show. §8 forbids wrapping a grouped rupee figure and no readable font size
   fits twelve digits into 121px, so the column has to give way, not the number.
   Done with `:has()` on the COLUMN so all 38 templates that use `stat_card` are fixed
   without editing one; the card also gets tighter so eight of them are not eight screens. */
@media (max-width:480px){
  .row > [class*="col-"]:has(> .stat-card),
  .row > [class*="col-"]:has(> a.stat-link),
  .row > [class*="col-"]:has(> .kz-kpi){ flex:0 0 100%; max-width:100%; }
  .stat-card{ padding:11px 13px; gap:11px; }
  .stat-card .ic{ width:36px; height:36px; flex:0 0 36px; border-radius:11px; font-size:1rem; }
  .stat-card .v{ font-size:1.12rem; }
  /* A heading and its caption side by side run off the edge; let them stack. */
  .d-flex.align-items-baseline{ flex-wrap:wrap; }
}

/* --- Hover-revealed affordances on a device with NO hover (2026-09-01) ---
   🔴 `opacity:.45, brighten on hover` is a desktop pattern. On a touch screen the hover
   never comes, so the control stays dimmed permanently and reads as disabled. Caught on
   the To-Do rows in a phone screenshot. Anything that reveals on hover needs this branch. */
@media (hover:none){
  .todo-act{ opacity:.8; }
  .todo-item .todo-act{ opacity:.8; }
  .acc-todo__go{ opacity:1; }
  .kz-um-go{ opacity:1; }
}

/* --- TOUCH TARGETS (owner ask 2026-09-01, measured) ---
   The audit found 264 controls under 30px at 360px wide — `.btn-sm` at 29, the mobile
   search at 26, a close button at 23. They were all sized for a cursor, which is correct
   on the desktop the product is designed on and wrong the moment a thumb arrives. WCAG
   2.5.8 asks 24px; 38 is the practical floor for something you actually aim at.

   🔴 Scoped to POINTER:COARSE, so the desktop stays exactly as dense as it is — this is
   not a global size bump, it is a different input device getting a different target.
   ⚠ Dense data-entry grids are EXCLUDED: a finance month-grid is a spreadsheet whose row
   height carries the frozen-column offsets (§9t), and growing every cell there would move
   the very geometry those columns are pinned against. They stay as they are, inside their
   own horizontal scroller, which is how a spreadsheet behaves on a phone anyway. */
@media (pointer:coarse){
  .btn:not(.btn-close),
  .kzs__btn,
  .form-control:not(.grid-input):not(.tb-amt),
  .form-select{
    min-height:38px;
  }
  .btn-sm, .btn-group-sm > .btn{ min-height:38px; padding-block:.34rem; }
  /* A close button is a small square by convention — make the SQUARE bigger, not the glyph. */
  .btn-close{ width:34px; height:34px; background-size:12px; }
  /* The compact search twin that replaces the search field below md. */
  #cmdkBtnSm{ min-width:38px; min-height:38px; }
  /* Checkboxes and radios are aimed at too. 24px is WCAG 2.5.8's floor — the first cut
     of this rule used 19 and the audit rightly kept reporting it. */
  .form-check-input, input[type=checkbox], input[type=radio],
  .rc-flip, .ac-tick, .bulk-chk, .bulk-all{ min-width:24px; min-height:24px; }
  /* A row of quiet icon actions (To-Do) needs real boxes on a phone. */
  .todo-act, .todo-tick{ min-width:34px; min-height:34px; }
  /* A target needs BOTH dimensions: min-height alone left icon-only small buttons 22px
     wide. min-width cannot widen a button that already has text in it. */
  .btn-sm, .btn-group-sm > .btn, .btn{ min-width:38px; }
  /* A bare <button> carries no .btn class, so nothing above reached it. */
  button:not(.btn):not(.btn-close):not(.kzs__btn):not(.seg-tab){ min-height:34px; }
  /* A small link sitting in a card header ("view all") is a target, not prose — give it a
     hit area without changing the line it sits on. */
  a.small, .card .page-title a, .kz-sec a{ padding-block:5px; }

  /* The dense MONTH grids keep their density — see the note above. ⚠ `.tb-tbl` only:
     `.fin-table` is also an ordinary report table, and zeroing it took the floor away from
     the delete button in the Working-Capital party list (measured 22px wide). */
  .tb-tbl .form-control, .tb-tbl .form-select, .tb-tbl .grid-input{ min-height:0; }
  .tb-tbl button{ min-height:0; min-width:0; }
}

/* --- Keyboard shortcuts (2026-09-01): the key caps, the cheat sheet, the chord hint ---
   A shortcut nobody can discover is a shortcut nobody uses, so the key appears in the
   hover tooltip of the control it drives and `?` opens the full list. */
.kbd{ display:inline-block; font-family:var(--font-mono); font-size:.7rem; font-weight:600;
  line-height:1.5; padding:1px 6px; border-radius:6px; color:var(--fg-muted);
  background:var(--surface-sunken); border:1px solid var(--hairline);
  box-shadow:0 1px 0 var(--hairline); }
.kbd--tip{ margin-left:6px; background:color-mix(in srgb, var(--fg) 10%, transparent);
  border-color:transparent; color:inherit; box-shadow:none; }
.kbd-cols{ display:grid; grid-template-columns:repeat(auto-fit, minmax(min(100%, 280px), 1fr)); gap:18px 26px; }
.kbd-col__t{ font-size:.7rem; font-weight:700; text-transform:uppercase;
  letter-spacing:var(--tracking-caps); color:var(--fg-faint); margin-bottom:6px; }
.kbd-row{ display:flex; align-items:center; gap:12px; padding:6px 0;
  border-top:1px solid var(--hairline); font-size:.86rem; }
.kbd-row > span:first-child{ flex:1 1 auto; min-width:0; }
.kbd-keys{ flex:0 0 auto; display:flex; align-items:center; gap:4px; }
.kbd-keys i{ font-style:normal; font-size:.68rem; color:var(--fg-faint); }
/* A chord is a MODE — an unannounced mode is a trap, so say it is waiting for the next key. */
.kz-chord{ position:fixed; left:50%; bottom:26px; transform:translateX(-50%); z-index:1092;
  background:var(--surface-raised); color:var(--fg); border:1px solid var(--hairline);
  border-radius:11px; padding:7px 13px; font-size:.8rem; box-shadow:var(--elev-3); }
.kz-chord span{ color:var(--fg-muted); margin-left:6px; font-size:.74rem; }
html[data-theme="dark"] .kz-chord{ background:#243246; }
@media (prefers-reduced-motion:no-preference){
  .kz-chord{ animation:kz-tip-in .12s var(--ease) both; }
}
/* Touch devices have no keyboard — the cheat sheet is still reachable from the menu. */
@media (hover:none){ .kbd--tip{ display:none; } }

/* The account chip and its menu show the person's own picture when they have one; the
   initials remain the fallback, so nothing changes for a client who has not uploaded. */
.avatar.has-pic{ padding:0; overflow:hidden; background:var(--surface-sunken); }
.avatar.has-pic img{ width:100%; height:100%; object-fit:cover; display:block; }
/* 🔴 A person's picture is the one thing on the top bar that identifies WHO is signed in —
   at 30px behind a grey chip it read as an ornament (owner ask 2026-09-01: "get bolder the
   profile picture portion"). Bigger, with a brand ring and a real edge, so it is the first
   thing the eye finds in that corner. */
.topbar .avatar{ width:34px; height:34px; font-size:.84rem;
  box-shadow:0 0 0 2px var(--surface-card), 0 0 0 3.5px color-mix(in srgb, var(--kz) 42%, transparent); }
.topbar .dropdown-toggle{ padding-block:4px; }
.dropdown-menu .avatar{ width:42px; height:42px; font-size:1rem;
  box-shadow:0 0 0 2px var(--surface-card), 0 0 0 3.5px color-mix(in srgb, var(--kz) 34%, transparent); }
html[data-theme="dark"] .topbar .avatar,
html[data-theme="dark"] .dropdown-menu .avatar{
  box-shadow:0 0 0 2px #1E293B, 0 0 0 3.5px color-mix(in srgb, #A5B4FC 45%, transparent); }
/* The account page's picture leads that card — give it the same ring, larger. */
.pic--lg{ box-shadow:0 0 0 3px var(--surface-card), 0 0 0 5px color-mix(in srgb, var(--kz) 30%, transparent); }
html[data-theme="dark"] .pic--lg{ box-shadow:0 0 0 3px #151C2C, 0 0 0 5px color-mix(in srgb, #A5B4FC 40%, transparent); }

/* Two columns that always end level: each is a flex stack whose last card grows. */
.acc-grid > [class*="col-"]{ display:flex; flex-direction:column; }
.acc-grid > [class*="col-"] > .card:last-child{ flex:1 1 auto; }

/* A logo in the chrome: it fills the tile the icon used to sit in, contained (never
   cropped — a client's mark must not be cut), on white so a transparent PNG reads. */
.topbar__icon.has-logo{ background:#fff; padding:3px; }
.topbar__icon img{ width:100%; height:100%; object-fit:contain; display:block; border-radius:8px; }
.firmsw__ic.has-logo{ background:#fff; padding:3px; }
.firmsw__ic img{ width:100%; height:100%; object-fit:contain; display:block; border-radius:6px; }
.firmsw__lg{ flex:0 0 auto; width:22px; height:22px; border-radius:6px; display:flex;
  align-items:center; justify-content:center; background:rgba(255,255,255,.10);
  color:#cbd5f5; margin-top:1px; overflow:hidden; }
.firmsw__lg.has-logo{ background:#fff; }
.firmsw__lg img{ width:100%; height:100%; object-fit:contain; display:block; }
.firmsw__lg .ki{ width:.8em; height:.8em; }

/* --- Profile picture / company logo upload (2026-09-01) ---
   The image IS the control: a label over a hidden file input, with a camera veil on hover
   so it reads as changeable. Formats and the size cap are printed beside it, never left
   for the client to discover from a rejection. */
.pic-wrap{ margin:0; flex:0 0 auto; }
.pic{ position:relative; display:grid; place-items:center; overflow:hidden; cursor:pointer;
  width:72px; height:72px; border-radius:18px; background:var(--kz-grad); color:#fff;
  border:1px solid var(--hairline); }
.pic--lg{ width:76px; height:76px; }
.pic--logo{ width:104px; height:104px; border-radius:20px; background:var(--surface-sunken);
  color:var(--fg-muted); }
.pic img{ width:100%; height:100%; object-fit:cover; display:block; }
.pic--logo img{ object-fit:contain; padding:6px; background:#fff; }
.pic__ini{ font-weight:700; font-size:1.4rem; letter-spacing:.02em; }
.pic--logo .pic__ini{ font-size:1.8rem; }
.pic__ov{ position:absolute; inset:0; display:grid; place-items:center; color:#fff;
  background:rgba(15,23,42,.55); opacity:0; transition:opacity var(--dur-fast) var(--ease); }
.pic:hover .pic__ov, .pic:focus-within .pic__ov{ opacity:1; }
.pic:focus-within{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb, var(--kz) 30%, transparent); }
.pic-tools{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-top:12px; }
.pic-tools form{ margin:0; }
.pic-hint{ flex-basis:100%; font-size:.72rem; color:var(--fg-muted); }
.pic-err{ flex-basis:100%; font-size:.76rem; font-weight:600; color:var(--bad); }

/* --- My Account: the completeness read (2026-09-01) ---
   A conic-gradient ring + an actionable list of what is missing. Built from tokens; the
   ring's fill is one custom property (`--pct`) written by the template. */
.acc-meter{ display:flex; align-items:center; gap:12px; margin-top:16px; }
.acc-ring{ flex:0 0 auto; width:54px; height:54px; border-radius:50%; display:grid;
  place-items:center; position:relative;
  background:conic-gradient(var(--kz) calc(var(--pct,0) * 1%), var(--surface-sunken) 0); }
.acc-ring::after{ content:""; position:absolute; inset:5px; border-radius:50%;
  background:var(--surface-card); }
.acc-ring__v{ position:relative; z-index:1; font-family:var(--font-mono); font-weight:700;
  font-size:.86rem; color:var(--fg); }
.acc-ring__v i{ font-style:normal; font-size:.6rem; color:var(--fg-muted); }
.acc-meter__t{ font-weight:700; font-size:.9rem; color:var(--fg); line-height:1.25; }
.acc-meter__s{ font-size:.76rem; color:var(--fg-muted); }
.acc-todo{ list-style:none; margin:12px 0 0; padding:0; display:grid; gap:6px; }
.acc-todo__b{ display:flex; align-items:flex-start; gap:9px; width:100%; text-align:start;
  padding:8px 10px; border-radius:11px; border:1px dashed var(--hairline-2, var(--border));
  background:transparent; color:var(--fg); font-size:.8rem; line-height:1.35;
  transition:border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease); }
.acc-todo__b:hover{ border-style:solid; border-color:var(--kz);
  background:color-mix(in srgb, var(--kz) 6%, transparent); }
.acc-todo__b > .bi:first-child{ color:var(--kz); margin-top:1px; }
.acc-todo__b span{ flex:1 1 auto; min-width:0; display:block; }
.acc-todo__b em{ display:block; font-style:normal; font-size:.73rem; color:var(--fg-muted); }
.acc-todo__go{ opacity:0; color:var(--kz); margin-top:1px;
  transition:opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease); }
.acc-todo__b:hover .acc-todo__go{ opacity:1; transform:translateX(2px); }
html[data-theme="dark"] .acc-todo__b:hover{ border-color:#A5B4FC; }
html[data-theme="dark"] .acc-todo__b > .bi:first-child,
html[data-theme="dark"] .acc-todo__go{ color:#A5B4FC; }
/* The field a checklist item sent you to, marked for a moment so you can see where you landed. */
.kz-flash{ animation:kz-flash 1.4s var(--ease) 1; border-radius:12px; }
@keyframes kz-flash{ 0%,100%{ background:transparent } 18%,60%{ background:color-mix(in srgb, var(--kz) 12%, transparent) } }
@media (prefers-reduced-motion:reduce){ .kz-flash{ animation:none; outline:2px solid var(--kz); outline-offset:3px; } }

/* --- My Account: identity rail + facts (2026-09-01) ---
   The page was one 760px card with three tabs, two holding a ~420px form. The facts a
   client comes here to CHECK (email on file, last login, whether recovery is set up) were
   buried at the bottom of a tab; they are a definition list in the rail now. */
.acc-name{ font-family:var(--font-display); font-weight:700; font-size:1.05rem;
  letter-spacing:-.015em; color:var(--fg); margin:0; line-height:1.25;
  overflow-wrap:anywhere; }
.acc-facts{ margin:16px 0 0; padding:0; display:grid; gap:0; }
.acc-facts > div{ display:flex; align-items:baseline; gap:10px; padding:7px 0;
  border-top:1px solid var(--hairline); }
.acc-facts dt{ flex:0 0 42%; font-size:.75rem; font-weight:600; color:var(--fg-muted); margin:0; }
.acc-facts dd{ flex:1 1 auto; min-width:0; margin:0; font-size:.83rem; color:var(--fg);
  overflow-wrap:anywhere; }
/* A labelled rule between two halves of one form — clearer than a bare <hr>. */
.acc-sep{ display:flex; align-items:center; gap:10px; margin-top:6px; padding-top:12px;
  border-top:1px solid var(--hairline); }

/* --- To-Do board (2026-09-01) — three lists side by side ---
   Replaces a 760px centred card with two of its three lists hidden behind tabs. Each
   column is a full card with its own progress; the action buttons are QUIET until the row
   is hovered or focused, so a list of ten tasks reads as ten tasks and not thirty buttons. */
.todo-col{ display:flex; flex-direction:column; overflow:hidden; }
.todo-col__h{ display:flex; align-items:center; gap:10px; padding:14px 16px 10px; }
.todo-col__ic{ flex:0 0 auto; }
.todo-col__t{ font-family:var(--font-display); font-weight:700; font-size:1.02rem;
  letter-spacing:-.015em; color:var(--fg); margin:0; line-height:1.2; }
.todo-col__sub{ font-size:.78rem; color:var(--fg-muted); }
.todo-col__pct{ font-size:1.15rem; font-weight:700; color:var(--kz); font-family:var(--font-mono); }
html[data-theme="dark"] .todo-col__pct{ color:#A5B4FC; }
.todo-bar{ height:4px; background:var(--surface-sunken); margin:0 16px 12px; border-radius:99px; overflow:hidden; }
.todo-bar > i{ display:block; height:100%; background:var(--kz-grad); border-radius:99px;
  transition:width var(--dur) var(--ease); }
.todo-add{ display:flex; gap:7px; padding:0 16px 12px; }
.todo-col__body{ display:flex; flex-direction:column; flex:1 1 auto; min-height:0; }
.todo-list{ list-style:none; margin:0; padding:0 8px; flex:1 1 auto; }
.todo-item{ display:flex; align-items:center; gap:8px; padding:5px 8px; border-radius:10px;
  position:relative; }
.todo-item + .todo-item{ box-shadow:0 -1px 0 var(--hairline); }
@media (hover:hover){ .todo-item:hover{ background:var(--surface-sunken); box-shadow:none; } }
.todo-item:focus-within{ background:var(--surface-sunken); }
.todo-item.is-urgent::before{ content:""; position:absolute; left:0; top:6px; bottom:6px;
  width:3px; border-radius:0 3px 3px 0; background:var(--bad); }
/* The tick is the row's primary control, so it is a real target, not a 12px icon. */
.todo-tick{ flex:0 0 auto; width:22px; height:22px; border-radius:7px; padding:0;
  border:1.5px solid var(--hairline-2, var(--border)); background:var(--surface-card);
  color:transparent; display:flex; align-items:center; justify-content:center; font-size:.8rem;
  transition:background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }
.todo-tick:hover{ border-color:var(--kz); color:color-mix(in srgb, var(--kz) 45%, transparent); }
.todo-tick.on{ background:var(--kz-grad); border-color:transparent; color:#fff; }
.todo-txt{ flex:1 1 auto; min-width:0; border:0; background:transparent; color:var(--fg);
  font-size:.86rem; padding:3px 2px; border-radius:6px; }
.todo-txt:focus{ outline:none; background:var(--surface-card);
  box-shadow:0 0 0 2px color-mix(in srgb, var(--kz) 30%, transparent); }
.todo-item.is-done .todo-txt{ text-decoration:line-through; color:var(--fg-faint); }
/* Quiet until wanted — but ALWAYS reachable by keyboard (opacity, never display). */
.todo-act{ flex:0 0 auto; width:26px; height:26px; padding:0; border:0; border-radius:8px;
  background:transparent; color:var(--fg-muted); font-size:.8rem; opacity:.45;
  transition:opacity var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease); }
.todo-item:hover .todo-act, .todo-item:focus-within .todo-act, .todo-act:focus-visible{ opacity:1; }
.todo-act:hover{ background:var(--surface-sunken); color:var(--fg); }
.todo-act.on{ opacity:1; color:var(--bad); }
.todo-act--del:hover{ color:var(--bad); }
.todo-col__f{ display:flex; align-items:center; gap:8px; padding:10px 16px 14px; margin-top:auto; }
.todo-col__empty{ padding:6px 16px 18px; }
@media (max-width:991.98px){ .todo-col{ margin-bottom:0; } }

/* --- Billing plan card: the folded module list (2026-09-01) ---
   Modules read as CHIPS, not as one bullet per line — a 20-item manifest is a wall in a
   card the client is trying to compare against its neighbour. */
.plan-more{ width:100%; text-align:start; margin-top:auto; }
.plan-more > summary{ font-size:.78rem; }
.plan-xtra{ display:flex; gap:7px; align-items:flex-start; margin:3px 0; }
.plan-xtra .bi{ color:var(--good); margin-top:2px; }
.plan-mods{ display:flex; flex-wrap:wrap; gap:5px; margin-top:7px; }
.plan-mod{ font-size:.71rem; font-weight:600; padding:2px 8px; border-radius:999px;
  background:color-mix(in srgb, var(--kz) 9%, transparent); color:var(--kz);
  border:1px solid color-mix(in srgb, var(--kz) 18%, transparent); }
html[data-theme="dark"] .plan-mod{ color:#A5B4FC;
  background:color-mix(in srgb, #A5B4FC 12%, transparent);
  border-color:color-mix(in srgb, #A5B4FC 24%, transparent); }

/* ============================================================================
   .kzs — THE DROPDOWN (owner ask 2026-09-01)
   An open native <select> is drawn by the OS: grey, square, un-themed, and it
   ignores every token here. This is the same control in the product's own voice
   — and the native <select> is still underneath, still what submits.
   ============================================================================ */
.kzs{ position:relative; display:block; width:100%; }
.kzs > select.kzs__native{ position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }
.kzs__btn{ display:flex; align-items:center; gap:8px; width:100%; text-align:start;
  background:var(--surface-card); color:var(--fg); border:1px solid var(--border);
  border-radius:10px; padding:.42rem .7rem; font-size:.9rem; line-height:1.45;
  transition:border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease); }
.kzs__btn:hover:not(:disabled){ border-color:color-mix(in srgb, var(--kz) 45%, var(--border)); }
.kzs__btn:focus-visible, .kzs.is-open .kzs__btn{ outline:none; border-color:var(--kz);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--kz) 22%, transparent); }
.kzs__btn:disabled{ opacity:.62; cursor:not-allowed; background:var(--surface-sunken); }
.kzs__v{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* ⚠ --fg-faint here measured 3.91:1 on the dark card — under AA. A "placeholder" in this
   control is not hint text: it is the CURRENT selection ("Any time", "All statuses"), which
   the client reads as data, so it takes the muted ink that passes rather than the faint one. */
.kzs__v.is-ph{ color:var(--fg-muted); }
.kzs__car{ flex:0 0 auto; width:.7em; height:.7em; opacity:.6;
  transition:transform var(--dur-fast) var(--ease); }
.kzs.is-open .kzs__car{ transform:rotate(180deg); }
/* small variant, matching .form-select-sm's density in the dense grids */
.kzs--sm .kzs__btn{ padding:.24rem .55rem; font-size:.82rem; border-radius:8px; }

/* The panel is a child of <body> so a table's overflow cannot clip it. */
.kzs-pop{ position:fixed; z-index:1085; min-width:150px; max-width:min(94vw, 460px);
  background:var(--surface-card); border:1px solid var(--hairline); border-radius:13px;
  box-shadow:var(--elev-3); padding:5px; overflow:hidden;
  display:flex; flex-direction:column; }
@media (prefers-reduced-motion:no-preference){
  .kzs-pop{ animation:kzs-in .13s var(--ease) both; }
  .kzs-pop.is-up{ animation-name:kzs-in-up; }
  @keyframes kzs-in{ from{ opacity:0; transform:translateY(-5px) scale(.985) } to{ opacity:1; transform:none } }
  @keyframes kzs-in-up{ from{ opacity:0; transform:translateY(5px) scale(.985) } to{ opacity:1; transform:none } }
}
.kzs-pop__srch{ flex:0 0 auto; padding:3px 4px 6px; }
.kzs-pop__srch input{ width:100%; font-size:.83rem; padding:.32rem .6rem; border-radius:8px;
  border:1px solid var(--hairline); background:var(--surface-sunken); color:var(--fg); }
.kzs-pop__srch input:focus{ outline:none; border-color:var(--kz);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--kz) 18%, transparent); }
.kzs-pop__list{ flex:1 1 auto; overflow-y:auto; max-height:min(46vh, 330px); }
.kzs-pop__grp{ font-size:.66rem; font-weight:700; text-transform:uppercase;
  letter-spacing:var(--tracking-caps); color:var(--fg-faint); padding:8px 10px 3px; }
.kzs-o{ display:flex; align-items:center; gap:8px; padding:.4rem .6rem; border-radius:9px;
  font-size:.86rem; color:var(--fg); cursor:pointer; white-space:normal;
  overflow-wrap:anywhere; line-height:1.35; }
.kzs-o__t{ flex:1 1 auto; min-width:0; }
.kzs-o__ck{ flex:0 0 auto; width:.85em; height:.85em; opacity:0; color:var(--kz); }
/* One highlight class for BOTH pointer and keyboard, so the row the mouse is on and the
   row Enter would choose can never be two different rows. */
.kzs-o.is-act{ background:color-mix(in srgb, var(--kz) 11%, transparent); }
.kzs-o.is-sel{ font-weight:600; }
.kzs-o.is-sel .kzs-o__ck{ opacity:1; }
.kzs-o.is-dis{ opacity:.45; cursor:not-allowed; }
.kzs-pop__none{ padding:14px 10px; text-align:center; color:var(--fg-muted); font-size:.83rem; }
html[data-theme="dark"] .kzs-o.is-act{ background:color-mix(in srgb, #A5B4FC 15%, transparent); }
html[data-theme="dark"] .kzs-o.is-sel .kzs-o__ck{ color:#A5B4FC; }
/* On paper, print the CHOSEN value as text — a button with a chevron is screen furniture. */
@media print{ .kzs__btn{ border:0; padding:0; background:none; } .kzs__car{ display:none; } }

/* --- .kz-tip — our tooltip, replacing Chrome's black OS bubble ---------------------
   Every icon-only control carries a `title`; the native rendering is a slow, square,
   un-themed box in a font used nowhere else in the product. Same information, our voice,
   and it appears immediately on hover AND on keyboard focus (the native one never shows
   for a keyboard user at all). */
.kz-tip{ position:fixed; z-index:1090; pointer-events:none; max-width:260px;
  background:var(--surface-raised); color:var(--fg); border:1px solid var(--hairline);
  border-radius:9px; padding:5px 9px; font-size:.76rem; font-weight:500; line-height:1.4;
  box-shadow:var(--elev-2); }
html[data-theme="dark"] .kz-tip{ background:#243246; }
@media (prefers-reduced-motion:no-preference){
  .kz-tip{ animation:kz-tip-in .11s var(--ease) both; }
  @keyframes kz-tip-in{ from{ opacity:0; transform:translateY(3px) } to{ opacity:1; transform:none } }
}

/* --- Identity bar + section nav (detail-page chrome, 2026-09-01) ---
   .kz-idbar  — the at-a-glance facts strip under a detail page's title: status pills
                plus small "label value" pairs, one wrapping row. Frees the cards below
                from repeating status/plan/counts.
   .kz-secnav — sticky in-page anchor pills for a long multi-zone detail page, parked
                just under the sticky .topbar; ux.js highlights the zone on screen.
   .kz-zone   — one section of such a page; scroll-margin clears the sticky chrome.
   Tokens only, so dark mode needs no extra pairing (one hover accent aside). */
.kz-idbar{ display:flex; flex-wrap:wrap; align-items:center; gap:8px 18px; margin:2px 0 0; }
.kz-idbar__f{ display:flex; align-items:baseline; gap:6px; font-size:.83rem; color:var(--fg-muted); white-space:nowrap; }
.kz-idbar__f b{ color:var(--fg); font-weight:600; }
.kz-secnav{ position:sticky; top:62px; z-index:5; display:flex; gap:8px; overflow-x:auto;
  padding:10px 2px; margin:6px -2px 10px; background:var(--surface-ground); scrollbar-width:none; }
.kz-secnav::-webkit-scrollbar{ display:none; }
.kz-secnav a{ white-space:nowrap; padding:5px 13px; border-radius:999px; font-size:.8rem;
  font-weight:600; color:var(--fg-muted); background:var(--surface-card);
  border:1px solid var(--hairline); text-decoration:none;
  transition:color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease); }
.kz-secnav a:hover{ color:var(--kz); border-color:var(--kz); }
.kz-secnav a.on{ color:#fff; background:var(--kz-grad); border-color:transparent; }
html[data-theme="dark"] .kz-secnav a:hover{ color:#A5B4FC; border-color:#A5B4FC; }
html[data-theme="dark"] .kz-secnav a.on{ color:#fff; }
.kz-zone{ display:block; scroll-margin-top:118px; margin-bottom:30px; }
.kz-zone > .kz-sec{ margin-bottom:12px; }
body {
  background:#F8FAFC;
  font-family:var(--bs-body-font-family);
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
  letter-spacing:.005em; line-height:1.55;
}
/* ---- Default 80% zoom-out on laptop/desktop (owner ask 2026-08-05) ---- */
/* A more compact, information-dense default view on devices with a real cursor
   (laptop/desktop). Touch phones & tablets (no hover) stay at 100% where the app
   is already tuned for touch, and print is never zoomed (only `screen`). We use
   `zoom` — not `transform: scale()` — so the layout reflows normally and fixed /
   sticky chrome (sidebar, topbar) keeps its correct position. Users can still
   override this with their own browser zoom. Change the one value to retune. */
@media screen and (min-width:1024px) and (hover:hover) and (pointer:fine) {
  /* App pages zoom; EVERY auth page opts out, and the sign-up page opts back IN by
     name (owner ask 2026-08-21). The first cut of this zoomed all five centred card
     pages — the owner narrowed it to the three a visitor actually meets on the way in:
     /register, /demo and /contact. Password-recovery pages are the same component but
     are not part of that path, so `.kz-auth--center` is the wrong discriminator and an
     explicit `.kz-auth--zoom` marker is used instead: a page zooms because it says so,
     not because it happens to share a layout class with one that does.
     (The split-screen logins were never in scope — at 80% their panels pull apart and
     leave a large empty middle.) */
  :root:not(:has(.kz-auth)),
  :root:has(.kz-auth--zoom) { zoom:0.8; }
  /* The two consequences of zooming that surface — the `100vh` centring box and the
     card's `backdrop-filter` glass — are handled in `auth.css`, where that surface
     lives (§1), under the SAME media query. Keep the two in step if this one changes. */
  /* `backdrop-filter` rendering is unreliable when an ancestor is scaled with `zoom`
     (GPU/driver-dependent): the 82%-opaque glass topbar then lets scrolled content
     (chart cards, sticky grid headers) bleed through as a sharp translucent band.
     Make the bar FULLY OPAQUE while zoomed — it looks identical at rest but can no
     longer bleed. Light + dark. The glass look is kept for touch/100% views. */
  .topbar { background:#fff !important; -webkit-backdrop-filter:none !important; backdrop-filter:none !important; }
  html[data-theme="dark"] .topbar { background:#1E293B !important; }
  /* `vh` does NOT account for `zoom`: the `height:100vh` fixed sidebar renders at
     only 80% of the screen height, leaving a white gap under it. Scale the height
     up by the same factor so it covers the full viewport (keep 0.8 in sync with the
     zoom above). */
  .sidebar { height:calc(100vh / 0.8) !important; }
  /* Same `vw`/`vh`-ignores-`zoom` trap for full-screen FIXED overlays: the modal
     backdrop + modal container + command-palette overlay use 100vw/100vh (or inset:0)
     and so only cover 80% of the screen, leaving the page's right/bottom edge
     un-dimmed behind a modal (and the centred dialog offset). Scale them to the full
     viewport. Body is overflow-hidden while a modal is open, so the extra size can't
     add a scrollbar. */
  .modal-backdrop, .modal, .cmdk-overlay {
    width:calc(100vw / 0.8) !important; height:calc(100vh / 0.8) !important;
    right:auto !important; bottom:auto !important;
  }
}
/* Typography: General Sans display headings + Inter body — unified with the marketing site.
   Confident, tightly-tracked headings + semibold UI text. Colours & layout unchanged. */
h1,h2,h3,h4,h5,h6,.page-title { font-family:var(--font-display); font-weight:700; letter-spacing:-.022em; line-height:1.18; }
.display-1,.display-2,.display-3,.display-4,.display-5,.display-6 { font-family:var(--font-display); letter-spacing:-.03em; }
.btn { font-weight:600; letter-spacing:.005em; }
.form-label { font-weight:600; }
.badge { font-weight:600; letter-spacing:.02em; }
.table thead th { font-weight:700; }
.navlink { letter-spacing:.005em; }
.lead { font-weight:400; }

/* ============ Phase 0 — design system tokens & primitives ============ */
:root{
  --radius:16px; --radius-sm:12px; --radius-lg:22px;
  --logo:40px;            /* the brand mark in the app sidebar (2026-08-12: 26 → 30; 2026-09-09: → 36 then 40, owner: larger, then "congested") */
  /* Finance data tables read at ONE size (owner ask 2026-08-12). Change here, not per grid. */
  --fs-table:.9rem; --fs-table-head:var(--fs-label);
  --shadow-sm:0 1px 2px rgba(15,23,42,.05), 0 2px 8px -2px rgba(15,23,42,.07);
  --shadow:0 10px 26px -12px rgba(15,23,42,.20); --shadow-lg:0 26px 52px -18px rgba(15,23,42,.30);
  --border:#e7ebf3; --muted:#64748B; --ink:#0F172A;
  /* KPI accent set — the SIX colours a stat-card icon may use, beside the three
     brand tokens (--kz, --kz2, --accent) the dashboards already use.
     ⚠ These are not new colours. Every one is a value ALREADY in the product; this
     only gives them names so ten ad-hoc hex literals stop drifting. The owner asked
     specifically not to change the product's colouring, so four near-duplicates
     collapse onto their nearest sibling (#b8860b->ochre, #1d6fb8->blue,
     #566 and #7d8590 -> slate) and nothing else moves at all. */
  --acc-teal:#157a6e; --acc-purple:#6a1b9a; --acc-clay:#c1503a;
  --acc-ochre:#9a6a16; --acc-blue:#1565a6; --acc-slate:#64748b;
  --ok:#157a5b; --ok-bg:#e6f6ef; --warn:#9a6a16; --warn-bg:#fbf1dc;
  --bad:#c4362f; --bad-bg:#fdeceb; --info:#1565a6; --info-bg:#e8f1fb; --mut-bg:#eef1f5;

  /* ── WS-1 scales (2026-07-23) — one vocabulary for spacing, type, motion, elevation.
     ADDITIVE: existing rules keep their hard-coded px until they are touched; new work
     and refactors use these so the product stops drifting a pixel at a time. Values
     chosen to MATCH what is already common in the codebase, not to restyle it. */
  /* 8px spacing scale */
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px;
  --sp-6:24px; --sp-7:32px; --sp-8:44px;
  /* type scale (rem) + line-heights */
  --fs-xs:.74rem; --fs-sm:.84rem; --fs-base:.94rem; --fs-md:1.02rem;
  --fs-lg:1.22rem; --fs-xl:1.55rem; --fs-2xl:2rem;
  --lh-tight:1.2; --lh-snug:1.35; --lh-normal:1.55;
  /* motion — every transition/animation should reference these */
  --dur-fast:.12s; --dur-base:.18s; --dur-slow:.32s;
  --ease-out:cubic-bezier(.22,.61,.36,1); --ease-spring:cubic-bezier(.34,1.56,.64,1);
  /* elevation ladder: rest -> hover -> overlay (aliases onto the shadows above) */
  --elev-1:var(--shadow-sm); --elev-2:var(--shadow); --elev-3:var(--shadow-lg);
}

/* ⚠ Reduced-motion is a HARD contract for everything WS-6 adds. Any new transition or
   animation must be neutralised here, or it ships an accessibility regression. */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration:.001ms !important; animation-iteration-count:1 !important;
    transition-duration:.001ms !important; scroll-behavior:auto !important;
  }
}
/* surfaces */
.card{ border:1px solid var(--border); border-radius:var(--radius); box-shadow:var(--shadow-sm); }
.card:hover{ box-shadow:var(--shadow); transition:box-shadow .18s ease; }
.btn{ border-radius:var(--radius-sm); font-weight:600; }
.btn-kz{ box-shadow:0 8px 20px -10px rgba(79,70,229,.6); transition:transform .14s ease, box-shadow .2s ease, background-position .4s ease; }
.btn-kz:hover{ transform:translateY(-1px); box-shadow:0 12px 26px -10px rgba(79,70,229,.7); }
.form-control,.form-select,.input-group-text{ border-radius:var(--radius-sm); border-color:var(--border); }
.form-control:focus,.form-select:focus{ border-color:var(--kz); box-shadow:0 0 0 4px rgba(79,70,229,.15); }

/* ==== Phase 1 — Core components ("not-Google" pass): premium buttons, inputs,
   dropdowns. Refinements layered on the existing classes; later in the cascade so
   they win. Presentation-only. (2026-08-08 UI/UX uplift) ==== */
/* Buttons — a subtle top inner-highlight, crisp edge, tactile press. */
.btn-kz{ box-shadow: inset 0 1px 0 rgba(255,255,255,.20), 0 8px 20px -10px rgba(79,70,229,.55); }
.btn-kz:hover{ box-shadow: inset 0 1px 0 rgba(255,255,255,.24), 0 12px 26px -10px rgba(79,70,229,.7); }
.btn-kz:active{ transform:translateY(0); box-shadow: inset 0 1px 2px rgba(0,0,0,.16), 0 4px 12px -8px rgba(79,70,229,.5); }
.btn:active:not(.is-loading){ transform:translateY(.5px); }
.btn:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb, var(--kz) 32%, transparent); }
/* Inputs — crisper branded focus ring (was a soft 4px glow) + a quiet hover cue. */
.form-control:hover:not(:focus):not([readonly]), .form-select:hover:not(:focus){ border-color:color-mix(in srgb, var(--kz) 34%, var(--border)); }
.form-control:focus,.form-select:focus{ border-color:var(--kz); box-shadow:0 0 0 3px color-mix(in srgb, var(--kz) 20%, transparent); }
/* Dropdown menus — off the stock-Bootstrap look: rounded, soft-elevated, tidy rows. */
.dropdown-menu{ border:1px solid var(--hairline, var(--border)); border-radius:var(--radius-sm); padding:6px;
  box-shadow:var(--elev-2, 0 18px 40px -18px rgba(15,23,42,.28)); }
.dropdown-item{ border-radius:9px; padding:8px 10px; font-weight:500; }
.dropdown-item:hover, .dropdown-item:focus{ background:color-mix(in srgb, var(--kz) 8%, transparent); color:var(--kz); }
.dropdown-item:active{ background:color-mix(in srgb, var(--kz) 14%, transparent); color:var(--kz); }
.dropdown-divider{ margin:5px 2px; border-color:var(--hairline, var(--border)); }
html[data-theme="dark"] .dropdown-menu{ background:#1E293B; border-color:#334155; }
html[data-theme="dark"] .dropdown-item{ color:#E2E8F0; }
html[data-theme="dark"] .dropdown-item:hover, html[data-theme="dark"] .dropdown-item:focus{ background:#243246; color:#A5B4FC; }

/* ==== Phase 2 — Numbers as first-class. Money/ratio figures render in the
   monospaced numerate face (Geist Mono) — tabular by construction, so every
   column lines up to the rupee. Applies to the money SURFACES only (KPI values,
   .num data cells, the .fig/.tnum utilities, month grids, money inputs) — NOT
   labels/headers/body text. Presentation-only. (2026-08-08 UI/UX uplift) ==== */
.fig, .tnum,
.kz-kpi__v, .stat-card .v,
.fin-table td.num, .table td.num,
.tb-tbl td.tb-m,
input.kz-inr, input.tb-amt{
  font-family:var(--font-mono);
  font-variant-numeric:tabular-nums;
  font-feature-settings:"tnum" 1;
  letter-spacing:-.012em;
}
/* Guard: the mono numeric face is for NUMBER cells only — never let a label/header
   inherit it from a container that carries .tnum (e.g. a statement <tbody class="tnum">). */
.tnum td:not(.num):not(.tnum), .tnum th:not(.num):not(.tnum){ font-family:var(--bs-body-font-family); text-align:start; }
/* Overview P&L mini-statement — labels left (body font), figures tight to the right. */
.fin-pl-mini td:first-child{ text-align:left; }
.fin-pl-mini td.num{ width:1%; white-space:nowrap; padding-left:22px; }

/* ==== Phase 3 — Navigation & identity chrome: a calmer, more premium rail +
   a refined active state, cohesive with the button language. Presentation-only.
   (2026-08-08 UI/UX uplift) ==== */
.sidebar a.navlink.active{
  background:linear-gradient(100deg, rgba(79,70,229,.82), rgba(37,99,235,.62) 55%, rgba(6,182,212,.44));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.18), 0 8px 18px -10px rgba(37,99,235,.75);
  font-weight:600;
}
.sidebar a.navlink:hover, .sidebar .nav-collapse > summary.grp-toggle:hover{ background:rgba(255,255,255,.11); }
.sidebar .grp{ color:#b4bffa; letter-spacing:.11em; font-weight:600; opacity:.9; }
.sidebar a.navlink .ki, .sidebar summary.grp-toggle .ki{ width:1.12em; height:1.12em; opacity:.94; }
.sidebar a.navlink.active .ki{ opacity:1; }
/* Topbar — a seamless search field (icon merges into the input) + quieter controls. */
.topbar .input-group .input-group-text{ background:transparent; border-right:0; color:var(--muted); padding-right:4px; }
.topbar .input-group .form-control{ border-left:0; padding-left:2px; }
/* page header primitive */
.page-header{ display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; margin-bottom:18px; }
.page-header .ph-title{ display:flex; align-items:center; gap:10px; margin:0; }
/* stat-card primitive (dashboards) */
/* The KPI card. Refined 2026-09-02 — the icon ANCHORS the card (centred against the text
   block, not pinned to its top), the value is unmistakably the largest thing in it, and the
   tile has a top highlight + a shadow in its own colour so it reads as an object rather
   than a coloured square. Same markup at all ~129 call sites. */
.stat-card{ border:1px solid var(--border); border-radius:var(--radius);
  background:linear-gradient(180deg, var(--surface-card), color-mix(in srgb, var(--fg) 2%, var(--surface-card)));
  padding:15px 17px; box-shadow:var(--shadow-sm); display:flex; gap:14px; align-items:center;
  height:100%; position:relative; overflow:hidden;
  transition:transform .16s var(--ease), box-shadow .18s var(--ease), border-color .18s var(--ease); }
.stat-card::after{ content:""; position:absolute; right:-18px; top:-18px; width:92px; height:92px; border-radius:50%; background:radial-gradient(circle, rgba(79,70,229,.10), transparent 70%); pointer-events:none; }
.stat-card .ic{ width:42px; height:42px; border-radius:13px; display:flex; align-items:center;
  justify-content:center; font-size:1.12rem; flex:0 0 42px; color:#fff;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.26), 0 6px 14px -9px rgba(15,23,42,.6);
  transition:transform .18s var(--ease); }
@media (hover:hover){ .stat-card:hover .ic{ transform:scale(1.06) rotate(-2deg); } }
@media (prefers-reduced-motion:reduce){ .stat-card, .stat-card .ic{ transition:none; } }
/* let the text column shrink so a long crore-scale value wraps instead of being
   clipped by the card's overflow:hidden (flex children default to min-width:auto). */
.stat-card > :not(.ic){ min-width:0; }
/* Caption -> VALUE -> context: three sizes far enough apart that the eye lands on the
   figure first. The value keeps `nowrap` — a grouped rupee figure must never break (§8). */
.stat-card .l{ font-size:.7rem; color:var(--fg-muted); text-transform:uppercase;
  letter-spacing:.075em; font-weight:700; line-height:1.3; }
.stat-card .v{ font-size:1.3rem; font-weight:700; line-height:1.2; color:var(--fg);
  white-space:nowrap; overflow-wrap:normal; letter-spacing:-.02em; margin-top:1px; }
.stat-card .d{ font-size:.75rem; font-weight:500; color:var(--fg-muted); margin-top:2px; }
.stat-card:hover{ transform:translateY(-2px); box-shadow:var(--shadow);
  border-color:color-mix(in srgb, var(--kz) 40%, var(--border)); }
/* A card that DRILLS DOWN says so on hover; a static one must not pretend to. */
a.stat-link{ text-decoration:none; display:block; height:100%; }
a.stat-link:focus-visible{ outline:none; border-radius:var(--radius);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--kz) 26%, transparent); }
.page-hero{ position:relative; overflow:hidden; border-radius:var(--radius-lg); border:1px solid var(--border); background:linear-gradient(135deg, rgba(79,70,229,.08), rgba(6,182,212,.06)); box-shadow:var(--shadow-sm); }
.page-hero::after{ content:""; position:absolute; inset:auto -8% -40% auto; width:220px; height:220px; border-radius:50%; background:radial-gradient(circle, rgba(79,70,229,.16), transparent 70%); pointer-events:none; }
.hero-chip{ display:inline-flex; align-items:center; gap:8px; padding:6px 10px; border-radius:999px; background:#fff; color:var(--kz); font-size:.78rem; font-weight:700; letter-spacing:.03em; border:1px solid rgba(79,70,229,.16); box-shadow:0 8px 18px -12px rgba(79,70,229,.35); }
.hero-actions{ display:flex; gap:10px; flex-wrap:wrap; }
.section-card .section-heading{ display:flex; align-items:flex-start; justify-content:space-between; gap:12px; flex-wrap:wrap; }
.quick-link{ display:flex; align-items:center; gap:10px; padding:11px 12px; border:1px solid var(--border); border-radius:12px; text-decoration:none; color:var(--ink); font-weight:600; background:linear-gradient(180deg, rgba(255,255,255,.7), rgba(248,250,252,.95)); transition:transform .14s ease, border-color .14s ease, box-shadow .14s ease; }
.quick-link:hover{ transform:translateY(-1px); border-color:var(--kz2); box-shadow:0 10px 20px -14px rgba(79,70,229,.35); color:var(--kz); text-decoration:none; }
.quick-link .bi{ color:var(--kz2); }
/* ---- Topbar search --------------------------------------------------------
   ONE control where there used to be two (a records box + a ⌘K button). It is a
   <button> that LOOKS like a field, because it opens the command palette — an input the
   user cannot type into would be a lie. The ⌘K hint lives inside it, so the shortcut is
   still discoverable without a second control competing for the same word. */
.topbar__search{ display:flex; align-items:center; gap:9px; width:clamp(220px, 26vw, 380px);
  padding:7px 12px; border:1px solid var(--border); border-radius:999px;
  background:var(--surface-sunken); color:var(--fg-muted); font-size:.88rem; text-align:start;
  transition:border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease),
             box-shadow var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }
.topbar__search:hover{ background:var(--surface-card); border-color:var(--kz); color:var(--fg); }
/* It opens a dialog, so it takes a real focus ring — a control that looks like a field and
   shows nothing on Tab reads as decoration. */
.topbar__search:focus-visible{ outline:none; background:var(--surface-card); color:var(--fg);
  border-color:var(--kz); box-shadow:0 0 0 3px color-mix(in srgb, var(--kz) 20%, transparent); }
.topbar__search:hover .ki, .topbar__search:focus-visible .ki{ color:var(--kz); }
.topbar__search .ki{ width:1.05em; height:1.05em; flex:0 0 auto; }
.topbar__search-t{ flex:1 1 auto; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.topbar__search kbd{ background:var(--surface-card); border:1px solid var(--hairline);
  border-radius:6px; padding:1px 6px; font-size:.7rem; font-weight:600; font-family:inherit;
  color:var(--fg-muted); flex:0 0 auto; box-shadow:0 1px 0 var(--hairline); }
html[data-theme="dark"] .topbar__search{ border-color:#334155; background:rgba(255,255,255,.05); }
html[data-theme="dark"] .topbar__search:hover{ background:rgba(255,255,255,.09); border-color:#A5B4FC; }
html[data-theme="dark"] .topbar__search kbd{ color:#CBD5E1; background:rgba(255,255,255,.12); }
@media (prefers-reduced-motion: reduce){ .topbar__search{ transition:none; } }

.topbar__title{ display:flex; align-items:center; gap:10px; min-width:0; }
.topbar__icon{ width:38px; height:38px; border-radius:11px; display:flex; align-items:center; justify-content:center; background:linear-gradient(135deg, rgba(79,70,229,.16), rgba(6,182,212,.16)); color:var(--kz); border:1px solid rgba(79,70,229,.16); }
.topbar__copy{ min-width:0; }
.topbar__eyebrow{ font-size:.68rem; text-transform:uppercase; letter-spacing:.08em; color:var(--muted); line-height:1; margin-bottom:2px; }
/* 🔴 The workspace name answers "whose books am I about to change?" — on a client running
   several firms that is the most consequential fact on the screen, and it was set at the
   same weight and size as the labels around it. It is the display face now, larger and
   tighter, with the eyebrow quieted so the NAME is what the eye lands on. */
.topbar__label{ font-family:var(--font-display); font-weight:700; font-size:1.02rem;
  letter-spacing:-.02em; color:var(--fg); line-height:1.15; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; }
.topbar__eyebrow{ opacity:.75; }

/* (Legacy .auth-* split-screen styles removed — all auth pages now use auth.css .kz-auth) */

/* ===================== Account dropdown menu ===================== */
/* A settings/detail column that should not run the full width of a wide screen — and should
   sit CENTRED rather than stranded against the left edge with a screenful of dead space
   beside it (owner: "well positioned", 2026-08-20). Replaces a `style="max-width:760px"`
   that was copy-pasted into four templates, so the width is now one number in one place. */
.kz-narrow{ width:100%; max-width:760px; margin-inline:auto; }

.kz-usermenu{ min-width:280px; max-width:320px; border:1px solid var(--border); border-radius:16px;
  padding:7px; box-shadow:var(--shadow-lg); margin-top:8px; }
/* The header is a profile CARD: the avatar is aligned to the top of the text block, not
   centred against it, so a one-line and a three-line card both read as deliberate. */
/* The header is a LINK now (owner ask 2026-09-01) — it behaves like one, and its text
   block is CENTRED against the avatar rather than pinned to the top, which is what made a
   one-or-two-line block beside a 42px picture look misaligned. */
.kz-um-head{ display:flex; align-items:center; gap:12px; padding:11px 12px 13px;
  margin-bottom:5px; border-bottom:1px solid var(--border); text-decoration:none;
  color:inherit; border-radius:11px 11px 0 0;
  transition:background var(--dur-fast) var(--ease); }
.kz-um-head:hover, .kz-um-head:focus-visible{ background:var(--surface-sunken); color:inherit; }
.kz-um-head .avatar{ width:42px; height:42px; font-size:.94rem; flex:0 0 42px; margin-top:0; }
.kz-um-go{ margin-left:auto; flex:0 0 auto; color:var(--fg-faint); display:flex;
  transition:transform var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }
.kz-um-go .ki{ width:.9em; height:.9em; }
.kz-um-head:hover .kz-um-go{ color:var(--kz); transform:translateX(2px); }
html[data-theme="dark"] .kz-um-head:hover .kz-um-go{ color:#A5B4FC; }
.kz-um-id{ min-width:0; display:flex; flex-direction:column; gap:2px; }
.kz-um-name{ font-weight:700; font-size:.96rem; line-height:1.25; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; }
.kz-um-role{ font-weight:600; font-size:.86rem; line-height:1.25; color:var(--fg);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* An email is long and is the one line allowed to wrap rather than vanish behind an
   ellipsis — you look at it to check WHICH account you are signed in as. */
.kz-um-sub{ color:var(--muted); font-size:.76rem; line-height:1.3; overflow-wrap:anywhere; }
.kz-um-firm{ display:flex; align-items:center; gap:5px; }
.kz-um-firm .ki{ width:.85em; height:.85em; flex:0 0 auto; opacity:.8; }

.kz-usermenu .dropdown-item{ display:flex; align-items:center; gap:11px; border-radius:11px;
  padding:9px 12px; font-weight:500; font-size:.9rem; color:var(--ink); }
/* 🔴 BOTH icon families. These rules named `.bi` only, but the menu was migrated to the
   sprite macro (`kzi.icon` → `<svg class="ki">`) — so since that migration the width,
   the muted ink and the hover colour applied to NOTHING: the icons rendered full-contrast
   black, at their own sizes, out of line with each other. Style `.ki` alongside `.bi`. */
.kz-usermenu .dropdown-item .bi,
.kz-usermenu .dropdown-item .ki{ font-size:1.05rem; width:18px; height:1.05em; flex:0 0 18px;
  text-align:center; color:var(--muted); }
.kz-usermenu .dropdown-item:hover, .kz-usermenu .dropdown-item:focus{ background:rgba(79,70,229,.08); color:var(--kz); }
.kz-usermenu .dropdown-item:hover .bi, .kz-usermenu .dropdown-item:focus .bi,
.kz-usermenu .dropdown-item:hover .ki, .kz-usermenu .dropdown-item:focus .ki{ color:var(--kz); }
.kz-usermenu .dropdown-item.kz-um-out{ color:#dc2626; }
.kz-usermenu .dropdown-item.kz-um-out .bi, .kz-usermenu .dropdown-item.kz-um-out .ki{ color:#dc2626; }
.kz-usermenu .dropdown-item.kz-um-out:hover{ background:rgba(220,38,38,.09); color:#dc2626; }
.kz-usermenu .dropdown-item.kz-um-out:hover .bi,
.kz-usermenu .dropdown-item.kz-um-out:hover .ki{ color:#dc2626; }
.kz-usermenu hr.dropdown-divider{ margin:5px 6px; border-color:var(--border); }
html[data-theme="dark"] .kz-usermenu{ background:#1E293B; border-color:#334155; }
html[data-theme="dark"] .kz-um-head{ border-bottom-color:#334155; }
/* The header's border was darkened when dark mode landed; the divider above Sign out was
   missed and stayed on the LIGHT hairline token — a near-white rule across a #1E293B menu.
   Measured, not eyeballed: rgb(231,235,243) on rgb(30,41,59). */
html[data-theme="dark"] .kz-usermenu hr.dropdown-divider{ border-color:#334155; opacity:1; }
html[data-theme="dark"] .kz-usermenu .dropdown-item{ color:#E2E8F0; }
html[data-theme="dark"] .kz-usermenu .dropdown-item:hover, html[data-theme="dark"] .kz-usermenu .dropdown-item:focus{ background:#243246; color:#A5B4FC; }
html[data-theme="dark"] .kz-usermenu .dropdown-item:hover .bi,
html[data-theme="dark"] .kz-usermenu .dropdown-item:focus .bi,
html[data-theme="dark"] .kz-usermenu .dropdown-item:hover .ki,
html[data-theme="dark"] .kz-usermenu .dropdown-item:focus .ki{ color:#A5B4FC; }
html[data-theme="dark"] .kz-um-role{ color:#E2E8F0; }
html[data-theme="dark"] .kz-um-sub{ color:#8a97a8; }
/* Sign-out red must stay legible on the dark surface — #dc2626 is 3.2:1 there. */
html[data-theme="dark"] .kz-usermenu .dropdown-item.kz-um-out,
html[data-theme="dark"] .kz-usermenu .dropdown-item.kz-um-out .ki,
html[data-theme="dark"] .kz-usermenu .dropdown-item.kz-um-out:hover{ color:#F87171; }
html[data-theme="dark"] .kz-usermenu .dropdown-item.kz-um-out:hover{ background:rgba(248,113,113,.14); }

/* clickable stat cards (drill into the data) */
a.stat-link{ display:block; height:100%; text-decoration:none; color:inherit; }
a.stat-link:hover .stat-card{ box-shadow:0 6px 18px rgba(13,59,102,.16); transform:translateY(-1px); border-color:var(--kz); }
a.stat-link:hover .stat-card .v{ text-decoration:underline; }
a.stat-link .stat-card{ transition:transform .08s ease, box-shadow .08s ease, border-color .08s ease; }
/* status badges */
.badge-status{ display:inline-block; font-weight:600; font-size:.72rem; padding:.3em .6em; border-radius:999px; letter-spacing:.01em; line-height:1.4; }
.bs-ok{background:var(--ok-bg);color:var(--ok);} .bs-warn{background:var(--warn-bg);color:var(--warn);}
/* \U0001F534 THE WARNING BADGE'S INK IN LIGHT MODE measured **4.21** on its own tint
   (rgb(154,106,22) on rgb(251,241,220)) where AA wants 4.5 \u2014 and this badge carries the
   one word on a sheet the client is being asked to act on. The product already darkens
   exactly this badge to #7d5511 for PRINT, having judged the lighter ink too weak on
   paper; the same judgement applies on screen. The INK moves, the tint does not: the tint
   is what makes it read as a warning at a glance. */
html:not([data-theme="dark"]) .badge-status.bs-warn,
html:not([data-theme="dark"]) .kz-pill.bs-warn{ color:#7d5511; }
.bs-bad{background:var(--bad-bg);color:var(--bad);} .bs-info{background:var(--info-bg);color:var(--info);}
.bs-mut{background:var(--mut-bg);color:#566;}
/* empty state */
.empty-state{ text-align:center; padding:44px 20px; color:var(--muted); }
.empty-state .es-ic{ font-size:2.6rem; color:#c4cfdc; line-height:1; }
.empty-state h6{ color:var(--ink); margin:12px 0 4px; font-weight:700; }
/* dashboard components: KPI trend chip, sparkline, segment bar */
.kz-delta{ display:inline-flex; align-items:center; gap:1px; font-size:.7rem; font-weight:700; padding:.1em .42em; border-radius:999px; vertical-align:middle; margin-left:5px; letter-spacing:.01em; line-height:1.3; }
.kz-delta .bi{ font-size:.72rem; }
.kz-delta.up{ background:var(--ok-bg); color:var(--ok); }
.kz-delta.down{ background:var(--bad-bg); color:var(--bad); }
.kz-delta.flat{ background:var(--mut-bg); color:#566; }
.kz-spark{ display:block; width:100%; height:32px; margin-top:9px; }
.kz-segbar{ display:flex; height:12px; border-radius:999px; overflow:hidden; background:var(--mut-bg); }
.kz-segbar > span{ display:block; height:100%; min-width:2px; }
.kz-seglegend{ display:flex; flex-wrap:wrap; gap:10px 16px; margin-top:11px; }
.kz-seglegend .it{ display:inline-flex; align-items:center; gap:6px; font-size:.8rem; color:var(--muted); }
.kz-seglegend .dot{ width:9px; height:9px; border-radius:3px; flex:0 0 auto; }
html[data-theme="dark"] .kz-delta.flat{ background:#334155; color:#c3ccd8; }
html[data-theme="dark"] .kz-segbar{ background:#334155; }
html[data-theme="dark"] .kz-seglegend .it{ color:#9aa7b8; }
/* drag-to-scroll: a wide table/grid the mouse can grab and slide (ux.js adds the classes) */
.kz-dragscroll{ cursor:grab; }
.kz-dragscroll.kz-dragging{ cursor:grabbing; user-select:none; }

/* Bulk-select drag column (ux.js bulk primitive). The whole cell is the handle, so it
   gets a pointer cursor and a roomier hit area than the checkbox alone; while a sweep is
   in progress nothing on the page may select, or the drag paints a blue text highlight. */
.bulk-cell{ cursor:pointer; text-align:center; }
.bulk-cell input{ cursor:pointer; }
body.kz-bulkdrag{ user-select:none; -webkit-user-select:none; }
/* Required-field marker. A form that marks nothing forces the visitor to submit and find
   out; a form that marks the wrong things is worse. Use `.req` on the LABEL of a field that
   actually carries `required`, and nowhere else. aria-hidden because the input's own
   `required` already tells assistive tech. */
.req{ color:var(--bad,#c4362f); font-weight:700; margin-left:1px; }
html[data-theme="dark"] .req, [data-bs-theme="dark"] .req{ color:#f87171; }

/* Honeypot on the sign-up form — see .hp in site.css for why it is off-screen
   rather than display:none. */
.kz-hp{position:absolute!important;left:-9999px!important;width:1px;height:1px;overflow:hidden}

/* Jump-to-match notice (ux.js): the banner is clickable, so it must LOOK clickable, and the
   row it lands on must announce itself — a smooth scroll alone leaves you unsure which row
   was meant. The flash is gated on prefers-reduced-motion. */
.kz-jump{ cursor:pointer; }
.kz-jump:focus-visible{ outline:2px solid var(--kz); outline-offset:2px; }
.dup-jump{ display:inline-block; margin-top:.15rem; font-weight:600; text-decoration:underline; }
@keyframes kzJumpHit{ 0%,100%{ box-shadow:inset 0 0 0 0 rgba(196,54,47,0); }
                      35%{ box-shadow:inset 0 0 0 3px rgba(196,54,47,.85); } }
@media (prefers-reduced-motion: no-preference){
  tr.kz-jump-hit > td{ animation:kzJumpHit 1.6s ease-in-out 1; }
}
@media (prefers-reduced-motion: reduce){
  tr.kz-jump-hit > td{ box-shadow:inset 0 2px 0 0 var(--bad,#c4362f), inset 0 -2px 0 0 var(--bad,#c4362f); }
}

/* Wing-status dropdown (Inventory ▸ Work in Progress). It replaced a pair of buttons, so it
   has to carry the same at-a-glance state the badge did — tinted green when the wing is
   Completed, amber while it is still in progress. Dark-paired. */
.wing-sel{ font-weight:600; }
.wing-sel.is-done{ color:var(--ok,#157a5b); border-color:rgba(21,122,91,.45); background-color:rgba(21,122,91,.07); }
.wing-sel.is-wip{ color:var(--warn,#9a6a16); border-color:rgba(154,106,22,.45); background-color:rgba(154,106,22,.07); }
html[data-theme="dark"] .wing-sel.is-done,
[data-bs-theme="dark"] .wing-sel.is-done{ color:#6ee7b7; border-color:rgba(110,231,183,.45); background-color:rgba(110,231,183,.10); }
html[data-theme="dark"] .wing-sel.is-wip,
[data-bs-theme="dark"] .wing-sel.is-wip{ color:#fcd34d; border-color:rgba(252,211,77,.45); background-color:rgba(252,211,77,.10); }

/* Duplicate row — a record that clashes with another on its identity fields. Imported
   rather than merged away (the client decides which to keep), so it must READ as a problem
   at a glance: red wash + a red left marker + a warning glyph by the serial number.
   Works on any table, plain .table or .fin-table. Tinting the <td> (not the <tr>) is what
   makes it beat Bootstrap's row striping/hover, and `background-clip:padding-box` on the
   inputs keeps the wash visible behind an editable grid instead of white boxes hiding it. */
tr.row-dup > td{ background:rgba(196,54,47,.10) !important; }
tr.row-dup:hover > td{ background:rgba(196,54,47,.16) !important; }
tr.row-dup > td:first-child{ box-shadow:inset 3px 0 0 var(--bad, #c4362f); }
tr.row-dup .form-control, tr.row-dup .form-select{
  background-color:transparent; background-clip:padding-box; border-color:rgba(196,54,47,.35);
}
tr.row-dup .form-control:focus, tr.row-dup .form-select:focus{ background-color:var(--surface-card, #fff); }
.dup-tag{ color:var(--bad, #c4362f); margin-left:.25rem; font-size:.8em; vertical-align:baseline; }
html[data-theme="dark"] tr.row-dup > td,
[data-bs-theme="dark"] tr.row-dup > td{ background:rgba(248,113,113,.16) !important; }
html[data-theme="dark"] tr.row-dup:hover > td,
[data-bs-theme="dark"] tr.row-dup:hover > td{ background:rgba(248,113,113,.22) !important; }
html[data-theme="dark"] tr.row-dup > td:first-child,
[data-bs-theme="dark"] tr.row-dup > td:first-child{ box-shadow:inset 3px 0 0 #f87171; }
html[data-theme="dark"] .dup-tag,
[data-bs-theme="dark"] .dup-tag{ color:#f87171; }
html[data-theme="dark"] tr.row-dup .form-control,
html[data-theme="dark"] tr.row-dup .form-select,
[data-bs-theme="dark"] tr.row-dup .form-control,
[data-bs-theme="dark"] tr.row-dup .form-select{ border-color:rgba(248,113,113,.45); }
/* single Edit toggle for grids: .ge-view shows normally, .ge-edit shows in edit mode.
   The [data-grid-edit] button (ux.js) toggles .grid-editing on the grid + enables inputs.
   ⚠ The `!important`s are LOAD-BEARING, not laziness. A per-table rule like
   `.re-fit .ge-view{display:inline-block}` has the SAME specificity (0,2,0) as
   `.grid-editing .ge-view` and, being defined later in the file, silently won — so in edit
   mode the read-only value stayed on screen NEXT TO its input and the cell showed the same
   figure twice in two formats ("753" beside "753.1571"). It looked like the grid was showing
   two different values. View/edit visibility is the toggle's contract; no table may override
   it. Any new per-table `.ge-view`/`.ge-edit` styling must therefore avoid `display`. */
.ge-edit{ display:none; }
.grid-editing .ge-view{ display:none !important; }
.grid-editing .ge-edit{ display:block !important; width:100%; }
.ge-del{ display:none; }
.grid-editing .ge-del{ display:inline-flex; }
.grid-editing .ge-col{ display:table-cell; }        /* a column that only exists in edit mode */
.ge-col{ display:none; }
/* 🔴 `.ge-inp` — the edit-mode face of a cell, HIDDEN in view mode but never DISABLED.
   Why it exists (2026-08-28, owner-reported on the construction Sales register): an
   `<input>` CLIPS its value and cannot wrap, so a customer name in a fixed-width frozen
   column was silently cut off — "Chandrakant Pentay" with no way to read the rest, and it
   looked like a tidy table. The value has to render as TEXT in view mode (`.ge-view`) and
   the input only while editing. `.ge-edit` would be the usual partner, but the toggle
   DISABLES a `.ge-edit` control, and these registers save with a blind `UPDATE … SET
   every_field=request.form.get(...)` — a disabled control posts nothing, so a view-mode
   Save would have blanked the column on every row. `.ge-inp` is hidden, not disabled: it
   still submits its current value, so the save path is untouched.
   **On a NEW grid use `.ge-edit` + a resilient save (kizuna-finance §5b).** Reach for this
   only when the save is a blind UPDATE you are deliberately not rewriting in the same edit. */
.ge-inp{ display:none; }
/* The text face of a register cell (the `ui.txtcell` macro): it WRAPS, so a long
   name is never cut off. ⚠ Sets no `display` — a per-table rule on a `.ge-view` is
   the collision that once showed a value beside its own input (see above). */
.kz-txtcell{ white-space:normal; overflow-wrap:break-word; line-height:1.35; }
/* ⚠ The FITTED grids (`.re-fit`, the flats/units registers) deliberately hold their
   `.ge-view` values on one line — 16 columns squeezed into the viewport, so a wrap would
   make every row three deep. A REMARK is the exception: it is a sentence, and a sentence
   nobody can read is not worth a tidy row. Specificity has to clear
   `.re-fit:not(.grid-editing) .ge-view` (0,3,0); this sets ONLY white-space, never
   `display`, which is the collision the `.ge-*` block warns about. */
.re-fit:not(.grid-editing) .ge-view.kz-txtcell{ white-space:normal; }
.grid-editing .ge-inp{ display:block !important; width:100%; }
/* amount cells are locked (read-only) until Edit: they read as plain text — no box, no
   caret — so the whole grid looks like a report until the user opts into editing.
   Specific enough to beat `.tb-tbl .tb-m .form-control` (0,0,3,0). */
.tb-tbl .tb-m .tb-amt[readonly], .tb-tbl .tb-m .grid-input[readonly],
.tb-amt[readonly], .grid-input[readonly]{
  background:transparent !important; border-color:transparent !important; box-shadow:none !important; cursor:default;
}
/* A locked (readonly) amount cell must show NO focus affordance — no indigo ring AND no
   blue focus outline — until the grid's Edit button unlocks it. Editable cells keep both. */
.tb-amt[readonly]:focus, .grid-input[readonly]:focus,
.tb-tbl .tb-m .tb-amt[readonly]:focus, .tb-tbl .tb-m .grid-input[readonly]:focus{ outline:none !important; box-shadow:none !important; }
html[data-theme="dark"] .tb-tbl .tb-m .tb-amt[readonly],
html[data-theme="dark"] .tb-tbl .tb-m .grid-input[readonly]{ background:transparent !important; border-color:transparent !important; }
/* tables */
table.table-sort th.sortable{ cursor:pointer; user-select:none; white-space:nowrap; }
table.table-sort th.sortable .si{ opacity:.35; font-size:.68rem; margin-left:2px; }
table.table-sort th.sortable.asc .si-up, table.table-sort th.sortable.desc .si-dn{ opacity:1; }
table.table-sort th.sortable.asc .si-dn, table.table-sort th.sortable.desc .si-up{ display:none; }
/* 🔴 MONEY READS FROM THE LEFT (owner decision 2026-09-07). Reverses the earlier
   right-aligned convention. The rule that survives is that a heading and its figures
   share ONE edge — both are left now, never one of each, which is the defect the
   owner reported in August. Digits stay tabular so they remain the same width. */
td.num,th.num{ text-align:left; font-variant-numeric:tabular-nums; }
/* sticky form action bar */
.form-actionbar{ position:sticky; bottom:0; background:#fff; border-top:1px solid var(--border); padding:12px 0 2px; margin-top:6px; }
html[data-theme="dark"] .card{ border-color:#334155; }
html[data-theme="dark"] .form-actionbar{ background:#1E293B; border-top-color:#334155; }
html[data-theme="dark"] .stat-card{ background:#1E293B; border-color:#334155; }
html[data-theme="dark"] .stat-card .v{ color:#E2E8F0; }
html[data-theme="dark"] .empty-state h6{ color:#E2E8F0; }
html[data-theme="dark"] .empty-state .es-ic{ color:#56657a; }
html[data-theme="dark"] .page-hero{ background:linear-gradient(135deg, rgba(79,70,229,.22), rgba(6,182,212,.14)); border-color:#334155; }
html[data-theme="dark"] .hero-chip{ background:#243246; color:#C7D2FE; border-color:#334155; }
html[data-theme="dark"] .quick-link{ background:linear-gradient(180deg, rgba(30,41,59,.82), rgba(15,23,42,.95)); color:#E2E8F0; border-color:#334155; }
html[data-theme="dark"] .quick-link:hover{ border-color:var(--kz2); }
html[data-theme="dark"] .topbar__icon{ background:linear-gradient(135deg, rgba(79,70,229,.26), rgba(6,182,212,.2)); border-color:#334155; color:#C7D2FE; }
html[data-theme="dark"] .topbar__label{ color:#E2E8F0; }
/* Bootstrap fixed-light utilities don't auto-adapt — make them dark-aware (covers all module/grid pages) */
html[data-theme="dark"] .table-light{ --bs-table-bg:#243246; --bs-table-color:#E2E8F0; --bs-table-border-color:#334155; }
html[data-theme="dark"] .table-secondary{ --bs-table-bg:#262f3f; --bs-table-color:#E2E8F0; --bs-table-border-color:#334155; }
html[data-theme="dark"] .table-active{ --bs-table-bg:#243246; --bs-table-color:#E2E8F0; }
html[data-theme="dark"] .bg-light:not(.badge){ background-color:#1E293B !important; }
html[data-theme="dark"] .bg-white:not(.badge){ background-color:#1E293B !important; color:#E2E8F0; }
/* status badges use light/coloured bg (bg-warning/info/light) — keep their text dark for contrast */
html[data-theme="dark"] .badge.text-dark{ color:#1f2733 !important; }
html[data-theme="dark"] .border{ border-color:#334155 !important; }
html[data-theme="dark"] .list-group-item{ background:#1E293B; color:#E2E8F0; border-color:#334155; }
html[data-theme="dark"] hr{ border-color:#334155; }
/* Inline brand-navy text (topbar tenant name, auth brand marks) is invisible on dark — force it light.
   Overrides inline style="color:var(--kz)" which CSS can't otherwise beat. */
html[data-theme="dark"] [style*="color:var(--kz)"]{ color:#A5B4FC !important; }
html[data-theme="dark"] .topbar .fw-semibold{ color:#C7D2FE !important; }
html[data-theme="dark"] .stat-card .l, html[data-theme="dark"] .stat-card .d{ color:#9aa7b8; }
/* avatar (initials) */
.avatar{ display:inline-flex; align-items:center; justify-content:center; border-radius:50%;
  background:linear-gradient(135deg,var(--kz),var(--kz2)); color:#fff; font-weight:700; line-height:1;
  width:30px; height:30px; font-size:.78rem; letter-spacing:.02em; flex:0 0 auto; }
.avatar.lg{ width:64px; height:64px; font-size:1.4rem; }
.sidebar { width:250px; height:100vh; background:linear-gradient(185deg,#312E81 0%,#1E1B4B 60%,#0F172A 100%); position:fixed; top:0; left:0; overflow-y:auto; overscroll-behavior:contain; z-index:1040; transition:transform .25s ease; border-right:1px solid rgba(255,255,255,.06); }
/* slim scrollbar for the sidebar */
.sidebar::-webkit-scrollbar { width:7px; }
.sidebar::-webkit-scrollbar-thumb { background:rgba(255,255,255,.22); border-radius:4px; }
.sidebar::-webkit-scrollbar-thumb:hover { background:rgba(255,255,255,.38); }
.sidebar .brand { color:#fff; font-weight:700; padding:16px 20px; font-size:1.15rem; letter-spacing:-.02em; font-family:var(--font-display); border-bottom:1px solid rgba(255,255,255,.1); }
/* the lockup (2026-09-09): 'Logic' carries the brand gradient, same as the marketing site and the login panel */
.sidebar .brand .lite{ color:inherit; }
/* Owner, 2026-09-09: "logo seems congested". The lockup is a flex row — mark, a real gap,
   wordmark — instead of an inline image hanging off the text baseline. Uppercase needs
   tracking (.06em), the mark gets a hairline ring and a soft drop so it reads as an app icon,
   and the whole block gets 18px of air top and bottom. The mark's SIZE still comes from
   `--logo` (one number). */
.sidebar .brand{ display:flex; align-items:center; gap:13px; padding:18px 20px 17px;
  text-transform:uppercase; letter-spacing:.06em; font-size:.98rem; line-height:1.1; min-width:0; }
.sidebar .brand img{ flex:0 0 auto; border-radius:11px;
  box-shadow:0 0 0 1px rgba(255,255,255,.16), 0 6px 18px rgba(0,0,0,.28); }
.sidebar .brand .brand__w{ display:block; white-space:nowrap; }
/* The mark is sized from ONE token (--logo) so "make the logo bigger" stays a one-number
   change instead of a hunt through four stylesheets. The width/height ATTRIBUTES in the
   markup are kept in step with it — they reserve the space before CSS loads (no layout
   shift); CSS is what actually sizes it. */
.sidebar .brand img { width:var(--logo); height:var(--logo); }
/* ---- Firm switcher (sidebar, under the brand) ------------------------------
   A client with more than one firm switches workspace here. It sits in the sidebar
   rather than on each page because it has to be reachable from EVERY service section
   — the point of it is comparing the same screen across firms. Authored on the dark
   sidebar gradient, so it takes its colours from that surface, not the page tokens
   (which is why it needs no html[data-theme=dark] pairing — the sidebar is always dark). */
.firmsw { padding:10px 10px 4px; }
.firmsw__btn { display:flex; align-items:center; gap:10px; width:100%; text-align:start;
  background:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.14); color:#fff;
  border-radius:12px; padding:8px 10px; transition:background .14s var(--ease,ease), border-color .14s ease; }
.firmsw__btn:hover { background:rgba(255,255,255,.14); border-color:rgba(255,255,255,.24); }
.firmsw__btn[aria-expanded="true"] { background:rgba(255,255,255,.16); border-color:#a5b4fc; }
.firmsw__ic { display:flex; align-items:center; justify-content:center; width:28px; height:28px;
  flex:0 0 28px; border-radius:9px; background:linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent)); color:#fff; }
.firmsw__ic .ki { width:1em; height:1em; }
.firmsw__txt { min-width:0; flex:1 1 auto; }
.firmsw__l { display:block; font-size:.62rem; text-transform:uppercase; letter-spacing:.11em;
  color:#a5b4fc; font-weight:700; line-height:1.2; }
/* The firm name is the identity of everything on screen — never let it wrap or clip
   silently to something that reads like a different firm. One line, ellipsis, full
   value in the button's aria-label. */
.firmsw__v { display:block; font-size:.88rem; font-weight:600; color:#fff; line-height:1.3;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.firmsw__car { display:flex; color:#cbd5f5; opacity:.8; flex:0 0 auto; }
.firmsw__car .ki { width:.85em; height:.85em; }
.firmsw__btn[aria-expanded="true"] .firmsw__car { transform:rotate(180deg); }
/* The menu is capped at the sidebar's width because .sidebar is overflow-y:auto, which
   clips an absolutely-positioned child horizontally — a wider menu would be cut off. */
.firmsw__menu { width:calc(250px - 20px); max-height:60vh; overflow-y:auto; }
.firmsw__menu form { margin:0; }
.firmsw__item { display:flex; align-items:flex-start; gap:8px; width:100%; }
/* 🔴 The name WRAPS here rather than ellipsing. This is the list you pick a firm FROM, so
   a truncated "Skyline De…" beside "Shreeji Realty LLP" is the one place a client could
   choose the wrong workspace. The collapsed button above still ellipses — it is showing a
   name you already chose, and it carries the full value in its aria-label. */
.firmsw__nm { flex:1 1 auto; min-width:0; white-space:normal; overflow-wrap:anywhere; line-height:1.35; }
.firmsw__item.is-on { font-weight:700; }
.firmsw__tag { flex:0 0 auto; font-size:.62rem; text-transform:uppercase; letter-spacing:.07em;
  font-weight:700; color:var(--kz); background:var(--info-bg); border-radius:6px; padding:1px 6px;
  margin-top:2px; }
.firmsw__tick { flex:0 0 auto; display:flex; color:var(--kz); margin-top:3px; }
.firmsw__tick .ki { width:.9em; height:.9em; }
html[data-theme="dark"] .firmsw__tag { color:#c7d2fe; }
@media (prefers-reduced-motion: reduce) { .firmsw__btn, .firmsw__car { transition:none; } }

.sidebar .grp { color:#a5b4fc; font-size:.7rem; text-transform:uppercase; letter-spacing:1px; padding:14px 20px 5px; opacity:.85; }
.sidebar a.navlink { display:flex; align-items:center; gap:11px; color:#cbd5f5; padding:9px 16px; margin:1px 10px; border-radius:11px; text-decoration:none; font-size:.92rem; transition:background .14s ease, color .14s ease; }
.sidebar a.navlink:hover { background:rgba(255,255,255,.09); color:#fff; }
.sidebar a.navlink.active { background:linear-gradient(100deg, rgba(79,70,229,.65), rgba(6,182,212,.45)); color:#fff; box-shadow:0 6px 16px -8px rgba(6,182,212,.6); font-weight:600; }
/* A LEFT RAIL on the active item (2026-09-01). The gradient fill alone reads as "slightly
   lighter" at a glance down a 20-item menu; a hard 3px bar reads as "you are here" without
   being read. Drawn with ::before so it needs no markup and cannot shift the row. */
.sidebar a.navlink{ position:relative; }
.sidebar a.navlink.active::before{ content:""; position:absolute; left:-6px; top:50%;
  transform:translateY(-50%); width:3px; height:19px; border-radius:0 3px 3px 0;
  background:linear-gradient(180deg,#A5B4FC,#22D3EE); }
/* The row answers the pointer by leaning a hair toward the content it opens. */
@media (hover:hover) and (prefers-reduced-motion:no-preference){
  .sidebar a.navlink{ transition:background .14s ease, color .14s ease, transform .14s var(--ease); }
  .sidebar a.navlink:hover{ transform:translateX(2px); }
}
/* Collapsible nav groups (Business Tools + services) */
.sidebar .nav-collapse { margin:1px 10px; }
.sidebar .nav-collapse > summary.grp-toggle { display:flex; align-items:center; gap:11px; color:#cbd5f5; padding:9px 16px; border-radius:11px; font-size:.92rem; cursor:pointer; list-style:none; user-select:none; transition:background .14s ease, color .14s ease; }
.sidebar .nav-collapse > summary.grp-toggle::-webkit-details-marker { display:none; }
.sidebar .nav-collapse > summary.grp-toggle:hover { background:rgba(255,255,255,.09); color:#fff; }
.sidebar .nav-collapse > summary.grp-toggle .chev { margin-left:auto; font-size:.8rem; transition:transform .18s ease; opacity:.75; }
.sidebar .nav-collapse[open] > summary.grp-toggle .chev { transform:rotate(180deg); }
.sidebar .nav-collapse[open] > summary.grp-toggle { color:#fff; }
.sidebar .nav-sub { margin:2px 0 4px; padding-left:8px; border-left:2px solid rgba(255,255,255,.10); }
.sidebar .nav-sub a.navlink { margin:1px 4px; font-size:.88rem; }
.sidebar a.navlink .ext, .sidebar summary .ext { margin-left:auto; font-size:.72rem; opacity:.6; }
.sidebar a.navlink .ext { margin-left:6px; }
.content { margin-left:250px; }
.topbar { position:sticky; top:0; z-index:1030; background:rgba(255,255,255,.82); backdrop-filter:saturate(1.4) blur(10px); -webkit-backdrop-filter:saturate(1.4) blur(10px); border-bottom:1px solid var(--border); display:flex; align-items:center; gap:10px; padding:11px 24px; }
.page-wrap { padding:24px 30px; }
.page-title { font-weight:700; color:var(--ink); }
/* A card title starts at the reading edge. It used to be `justify-content:space-between`,
   which only LOOKED right while the title was shrink-to-fit: give it any spare width and the
   icon and the words fly to opposite ends of the card ("🏷      Clients by plan"). That is
   exactly what happened once titles gained a caption underneath and were wrapped in a div.
   ⚠ text-align cannot fix this and cannot detect it either — on a flex container the property
   does nothing, which is why an audit that measured text-align reported it clean. Measure the
   left EDGE of the title against its caption instead.
   Trailing bits (a "view all" link, a count) still sit right: the last ELEMENT child gets an
   auto margin, so a title that is just icon + words has nothing to push. */
.card .page-title { display:flex; align-items:center; justify-content:flex-start; gap:.75rem; }
.card .page-title > :last-child:not(:first-child) { margin-left:auto; }
.card .page-title .small { margin:0; }
.card { border:1px solid var(--border); box-shadow:var(--shadow-sm); }
.kpi { border-radius:var(--radius); color:#fff; }
/* Unified table header (2026-08-08) — one calm, BOLDER micro-caps band for every table
   (matches .fin-table), so no header reads brighter than its content. */
.table thead th { background:var(--surface-sunken); color:var(--th-fg); font-weight:700; font-size:var(--fs-label,.74rem); text-transform:uppercase; letter-spacing:var(--tracking-caps,.06em); border-bottom:1px solid var(--hairline); }
.btn-kz { background:linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent)); background-size:160% auto; color:#fff; border:none; }
.btn-kz:hover { background-position:right center; color:#fff; }
.grid-input { border:1px solid #d4dde8; border-radius:4px; padding:4px 6px; width:100%; font-size:.85rem; }
/* keep numeric grid cells tight: hide the browser spinner arrows (input stays number-only) */
.grid-input[type=number] { -moz-appearance:textfield; appearance:textfield; }
.grid-input[type=number]::-webkit-outer-spin-button,
.grid-input[type=number]::-webkit-inner-spin-button { -webkit-appearance:none; margin:0; }
/* To-Do List — a task flagged urgent gets a red left accent and a red-bordered
   field so it stands out in the list. */
.todo-urgent > td:first-child { box-shadow: inset 3px 0 0 var(--bad); }
.todo-urgent .grid-input { border-color: var(--bad); }

/* Segmented tabs — buttons at the top switch between panels (CSS-only: hidden
   radios drive which panel shows; the server pre-checks the active one so the
   choice survives a save). Used by To-Do List (#tab-*) and Scheduling (#stab-*). */
.seg-tabin { position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }
.seg-tabbar { display:flex; gap:.4rem; flex-wrap:wrap; margin-bottom:1.1rem; }
.seg-tab { cursor:pointer; padding:.45rem .95rem; border:1px solid var(--border); border-radius:999px;
  font-weight:600; font-size:.85rem; color:var(--muted); background:#fff; user-select:none; white-space:nowrap; }
.seg-tab:hover { border-color:var(--kz); color:var(--kz); }
.seg-panel { display:none; }
/* active-tab pill */
#tab-daily:checked    ~ .seg-tabbar label[for="tab-daily"],
#tab-weekly:checked   ~ .seg-tabbar label[for="tab-weekly"],
#tab-monthly:checked  ~ .seg-tabbar label[for="tab-monthly"],
#stab-daily:checked   ~ .seg-tabbar label[for="stab-daily"],
#stab-weekly:checked  ~ .seg-tabbar label[for="stab-weekly"] {
  background:linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent)); color:#fff; border-color:transparent; }
#tab-daily:focus-visible   ~ .seg-tabbar label[for="tab-daily"],
#tab-weekly:focus-visible  ~ .seg-tabbar label[for="tab-weekly"],
#tab-monthly:focus-visible ~ .seg-tabbar label[for="tab-monthly"],
#stab-daily:focus-visible  ~ .seg-tabbar label[for="stab-daily"],
#stab-weekly:focus-visible ~ .seg-tabbar label[for="stab-weekly"] { outline:2px solid var(--kz); outline-offset:2px; }
/* panel show */
#tab-daily:checked   ~ #panel-daily,
#tab-weekly:checked  ~ #panel-weekly,
#tab-monthly:checked ~ #panel-monthly,
#stab-daily:checked  ~ #spanel-daily,
#stab-weekly:checked ~ #spanel-weekly { display:block; }
/* A SERVER-RENDERED segmented control. The pills above are driven by `:checked ~`, which
   only works for the CSS-only radio variant; a page whose panes contain forms must let the
   server decide the active pane (§5), and it marks it with `.active`. Both variants share
   the resting look, so the two cannot drift apart visually. */
a.seg-tab{ text-decoration:none; display:inline-flex; align-items:center; gap:.35rem; }
.seg-tab.active{
  background:linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent));
  color:#fff; border-color:transparent; }
.seg-tab.active:hover{ color:#fff; border-color:transparent; }
.seg-tab:focus-visible{ outline:2px solid var(--kz); outline-offset:2px; }
/* \U0001F534 The dark RESTING rule above is (0,2,1) and this must out-rank it, or the
   active pill is indistinguishable from the inactive one in the DEFAULT theme. It is also a
   `background` SHORTHAND up there, which resets background-image \u2014 so restating the colour
   alone is not enough, the gradient has to be restated too. */
html[data-theme="dark"] .seg-tab.active{
  background:linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent));
  color:#fff; border-color:transparent; }
html[data-theme="dark"] .seg-tab.active:hover{ color:#fff; border-color:transparent; }

html[data-theme="dark"] .seg-tab { background:#1E293B; border-color:#334155; color:#8a97a8; }
html[data-theme="dark"] .seg-tab:hover { color:#A5B4FC; border-color:#A5B4FC; }
/* Construction Inventory Management — Finished goods / Raw materials / WIP tabs */
/* ⚠ A NEW TAB IS THREE RULES, NOT ONE — the active pill, the focus ring and the panel.
   Miss the last and the tab highlights while showing nothing. */
#inv-fg:checked ~ .seg-tabbar label[for="inv-fg"],
#inv-rm:checked ~ .seg-tabbar label[for="inv-rm"],
#inv-lo:checked ~ .seg-tabbar label[for="inv-lo"],
#inv-wip:checked ~ .seg-tabbar label[for="inv-wip"] {
  background:linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent)); color:#fff; border-color:transparent; }
#inv-fg:focus-visible ~ .seg-tabbar label[for="inv-fg"],
#inv-rm:focus-visible ~ .seg-tabbar label[for="inv-rm"],
#inv-lo:focus-visible ~ .seg-tabbar label[for="inv-lo"],
#inv-wip:focus-visible ~ .seg-tabbar label[for="inv-wip"] { outline:2px solid var(--kz); outline-offset:2px; }
#inv-fg:checked ~ #ipanel-fg,
#inv-rm:checked ~ #ipanel-rm,
#inv-lo:checked ~ #ipanel-lo,
#inv-wip:checked ~ #ipanel-wip { display:block; }
/* Construction Purchases — Raw Material / Services (owner ask 2026-08-14).
   Same primitive as Inventory above: the radios, .seg-tabbar and .seg-panel blocks must
   stay SIBLINGS (the `~` combinator), or the panel never shows. */
#pur-rm:checked ~ .seg-tabbar label[for="pur-rm"],
#pur-svc:checked ~ .seg-tabbar label[for="pur-svc"] {
  background:linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent)); color:#fff; border-color:transparent; }
#pur-rm:focus-visible ~ .seg-tabbar label[for="pur-rm"],
#pur-svc:focus-visible ~ .seg-tabbar label[for="pur-svc"] { outline:2px solid var(--kz); outline-offset:2px; }
#pur-rm:checked ~ #ppanel-rm,
#pur-svc:checked ~ #ppanel-svc { display:block; }
.seg-tab .ki { width:1em; height:1em; vertical-align:-2px; margin-right:.3rem; }
/* Construction month-wise statements (COGS / Gross Profit / Net Profit) */
/* ONE type size for finance data tables (owner ask 2026-08-12, "make font same").
   The construction section had five: .fin-table .9rem, .re-stmt .86rem, .re-fit .8rem,
   .re-fit-wide .74rem — and the two WIDEST registers (Purchases, Sales register) sat on a
   plain Bootstrap .table at the full 1rem, so the busiest screens carried the largest text.
   `.re-reg` puts those on the same scale as every other finance table.
   ⚠ `.re-fit` / `.re-fit-wide` keep their smaller sizes ON PURPOSE — they are the
   fit-to-viewport grids (kizuna-finance §10 ⭐); enlarging them re-introduces the sideways
   scroll the owner asked to remove. Their headers are aligned to the shared header size. */
.re-stmt{ font-size:var(--fs-table); }
.re-reg{ font-size:var(--fs-table); }
.re-reg thead th{ font-size:var(--fs-table-head); text-transform:uppercase;
  letter-spacing:var(--tracking-caps); color:var(--th-fg); font-weight:700; }
/* ⚠ `.re-fit` / `.re-fit-wide` are NOT brought onto the shared size. They are the
   fit-to-viewport grids and the Finished-goods table currently fits with ZERO slack
   (measured 1688px table in a 1688px wrapper) — one step up on the header font puts the
   sideways scroll straight back, which is the thing the owner asked to remove. Smaller
   there is a deliberate trade, not drift. */
/* 🔴 ONE WIDTH FOR EVERY MONEY COLUMN (2026-09-07, owner-reported).
   `min-width` is advisory in an auto-layout table: the browser sized each month by its
   content, so April (holding ₹1,63,31,250) rendered 94px and June 72px — below the
   90px asked for — and twelve months that are one repeating unit came out five different
   widths. Fixed widths plus a table `min-width` equal to their sum means the columns never
   negotiate. The table scrolls inside `.table-responsive`, as it already did. */
.re-stmt{ min-width:2230px; table-layout:fixed; }  /* 260 + 12×150 + 170 */
/* ⚠ Widened when the cells gained real padding: 14px a side comes out of the SAME
   budget as the figure, so a 132px column that used to hold ₹1,72,31,250 stopped
   holding it and 16 cells on MIS went back to spilling. Sized from the measurement
   — 81px of text + 28px of padding — not from a guess. */
/* ⚠ SPACING. These cells inherited Bootstrap's `.table-sm` 4px padding — the
   "congested" the owner named. Matched to `.fin-table` (11/12px × 14px) so a statement
   reads with the same air as every other data table. */
.re-stmt thead th{ padding:11px 14px; }
.re-stmt tbody td{ padding:11px 14px; }
/* ⚠ `table-layout:fixed` is the part that actually binds. Under the default `auto`,
   even an explicit `width` is a suggestion the browser overrides from content — the
   months went 72→90px but April still came out 94px and Total stayed 96px instead of
   the 130px asked for. Fixed layout takes the widths as declared. */
/* ⚠ Sized against the WIDEST figure these columns can hold, measured on screen rather
   than estimated: a negative crore value (`-₹1,98,13,142`) rendered 88px inside an 83px
   box and spilled into the month beside it, which is why the numbers looked shifted one
   column across. Remember the app is at `zoom:0.8` on desktop — these DECLARED widths
   render at 80%, so 132px on screen is ~106px. */
.re-stmt td.num, .re-stmt th.num{ width:150px; min-width:150px; }
.re-stmt td.re-cT, .re-stmt th.re-cT{ width:170px; min-width:170px; }
/* MIS Reporting draws the same month grid plus a "% Rev" column. It gets the modifier
   rather than a second set of rules, so the four month-wise statements cannot drift. */
.re-stmt td.re-cP, .re-stmt th.re-cP{ width:90px; min-width:90px; }
.re-stmt--pct{ min-width:2320px; }                /* 260 + 12×150 + 170 + 90 */

/* 🔴 The row label STICKS while you scroll sideways. A statement this wide is
   unreadable once "Particulars" leaves the screen — you are reading a number with
   nothing to say what it is. Each row variant repeats its own background, or the figures
   scroll visibly underneath the label. */
.re-stmt .re-c1{ width:260px; min-width:260px; font-weight:500;
  /* 🔴 WRAPS, not nowrap. With the column width now fixed, `nowrap` clipped the
     longer statement lines — "Less: Cost of Goods Sold" needed 335px in a 243px box and
     simply ran underneath the figures. A statement line that cannot be read is worse than
     one that takes two lines. */
  white-space:normal; overflow-wrap:anywhere; line-height:1.3;
  position:sticky; left:0; z-index:2; background:var(--surface-card); }
.re-stmt thead .re-c1{ background:var(--surface-sunken); z-index:3; }
.re-stmt tr.re-sub .re-c1, .re-stmt tr.re-tot .re-c1{ background:var(--surface-sunken); }
.re-stmt tbody tr:hover .re-c1{ background:var(--surface-raised); }
/* …but in the HEADER row it is a header like the months beside it, not a row label. */
.re-stmt thead .re-c1{ font-weight:700; }
.re-stmt td.num, .re-stmt th.num{ font-family:var(--font-mono); font-variant-numeric:tabular-nums; white-space:nowrap; }
.re-stmt .reneg{ color:var(--bad); }
.re-stmt tr.re-sub td{ font-weight:600; background:var(--surface-sunken); }
.re-stmt tr.re-tot td{ font-weight:700; background:var(--surface-sunken); border-top:2px solid var(--hairline); border-bottom:2px solid var(--hairline); }
.re-stmt tr.re-rev td{ font-weight:600; }
/* Fit-to-viewport wide data table (construction Finished-goods flats) — no side-scroll:
   fixed layout + proportional <colgroup> so all columns stay within the screen width,
   headers wrap, cells compact. Content that overflows a cell is clipped (input scrolls). */
/* ⚠ AUTO layout, not fixed (2026-08-11 owner ask: "well resized, proper fit-viewport, do
   not squeeze"). `table-layout:fixed` + a percentage <colgroup> forced all 16 columns into
   the viewport whatever their content, so headings broke mid-word ("PROJE CT NAME") and
   values were clipped to "Mou…" / "11th Floc". Now each <th> carries a readable min-width
   and the browser sizes columns to content: with room to spare the table fills the width
   (width:100%), and when there genuinely isn't room .table-responsive scrolls instead of
   crushing. Compact type is kept — that's what makes it fit in the first place. */
.re-fit{ table-layout:auto; width:100%; font-size:.8rem; }
.re-fit > thead > tr > th, .re-fit > tbody > tr > td{ padding:.34rem .34rem; vertical-align:middle; }
.re-fit thead th{ white-space:normal; font-size:.66rem; line-height:1.15; letter-spacing:0; word-break:keep-all; overflow-wrap:normal; hyphens:manual; }
.re-fit td.num{ white-space:nowrap; }
/* :not(.grid-editing) — the read-only display styling must not exist while editing, or it
   competes with the toggle's hide rule (that's the bug that showed "753" beside "753.1571"). */
.re-fit:not(.grid-editing) .ge-view{ display:inline-block; max-width:100%; white-space:nowrap; vertical-align:bottom; }
.re-fit .form-control, .re-fit .form-select{ font-size:.77rem; padding:.18rem .3rem; min-height:0; }
.re-fit .form-select-sm{ padding-right:1rem; background-position:right .25rem center; }
.re-fit .btn{ padding:.1rem .2rem; }
/* .re-fit-wide — same fit-to-viewport treatment for the VERY wide registers (Purchases
   ~22-col, Revenue ~26-col). Tighter base font, and numeric DISPLAY cells WRAP (not
   ellipsis) so a full ₹ figure stays readable in a narrow column; editable inputs keep
   their value and scroll on focus. (2026-08-08 — owner ask: fit, wrap, no side-scroll) */
.re-fit-wide{ font-size:.74rem; }
.re-fit-wide thead th{ font-size:.6rem; line-height:1.12; }
.re-fit-wide > tbody > tr > td{ padding:.28rem .24rem; }
.re-fit-wide td.num{ white-space:normal; text-overflow:clip; overflow-wrap:break-word; }
.re-fit-wide .form-control, .re-fit-wide .form-select{ font-size:.7rem; padding:.14rem .2rem; }
.re-fit-wide .form-select-sm{ padding-right:.85rem; background-position:right .18rem center; }
/* .re-fit fits 14 columns to the viewport on DESKTOP, but on a phone/tablet that
   squeezes each header down to one letter per line ("P R O J E C T"). Below lg, drop
   the fixed layout so the table takes its natural width and scrolls inside
   .table-responsive (headers on one line) — the same graceful pattern the 12-month /
   26-column tables already use. (2026-08-08 responsive QA) */
@media (max-width:991.98px){
  .re-fit{ table-layout:auto; width:auto; min-width:100%; }
  .re-fit thead th{ white-space:nowrap; }
  .re-fit .ge-view{ max-width:none; }
  .re-fit .form-control, .re-fit .form-select{ font-size:.82rem; }
}
/* KPI value must never wrap mid-number on a narrow card (₹50,00,000 → "₹50,00,\n000").
   .stat-card .v is now nowrap; shrink the value + icon on small cards so the whole
   figure still fits on one line instead of clipping. Project-wide (all industries). */
@media (max-width:575.98px){
  .stat-card{ padding:13px 13px; gap:10px; }
  .stat-card .ic{ width:34px; height:34px; flex:0 0 34px; font-size:1rem; }
  .stat-card .v{ font-size:1rem; }
  .stat-card .l{ font-size:.68rem; letter-spacing:.03em; }
}
@media (max-width:400px){
  .stat-card .v{ font-size:.9rem; }
  .stat-card .l{ font-size:.64rem; }
}
/* KPI ROWS REFLOW TO TWO-UP when the page is narrow — a tablet (≤991px), or a laptop with the
   conversation dock open (`body.kzd-open`, which narrows the page without changing the
   viewport, so Bootstrap's breakpoints never fire). Four crore-scale figures across a
   ~900px page cannot fit; two can. `:has()` on the column so the 38 stat_card callers and
   every kz-kpi grid get it without an edit (same technique as the ≤480px rule above). */
@media (max-width:991.98px){
  .row > [class*="col-"]:has(> .stat-card),
  .row > [class*="col-"]:has(> a.stat-link),
  .row > [class*="col-"]:has(> .kz-kpi){ flex:0 0 50%; max-width:50%; }
}
/* …and by the PAGE's own width, not the viewport's: the WhatsApp dock and the booking drawer
   narrow #main without a resize, so Bootstrap's breakpoints never fire. `ux.js` keeps
   `#main[data-w]` = narrow (<560px) · mid (<960px) · wide from a ResizeObserver; rules that
   must follow the page hang off it. (Caught 2026-09-09: four KPI tiles 90px wide beside the
   dock at 1024px, the figure column 23px.)

   🔴 @media screen — AND THE SCOPE IS THE POINT (2026-09-21). `data-w` is a measurement
   of the SCREEN page column, taken by a ResizeObserver, and it is still sitting on the
   element when the page prints. The owner printed the production MIS sheet and got the four
   headline figures **2×2 with half the width blank**, because their sidebar puts the column
   in `mid` — while every measurement taken in a headless context with no sidebar was
   `wide`, which is why it had never once shown up in testing.
   On paper there is no dock, no drawer and no observer: the width IS the sheet, it is
   known, and Bootstrap's own breakpoints read it correctly. A screen measurement must never
   decide a paper layout. This is project-wide — every page with KPI tiles or the Debtors
   aging chips carried the same latent fault; the MIS sheet is only where it was seen. */
@media screen{
  #main[data-w="mid"] .row > [class*="col-"]:has(> .stat-card),
  #main[data-w="mid"] .row > [class*="col-"]:has(> a.stat-link),
  #main[data-w="mid"] .row > [class*="col-"]:has(> .kz-kpi){ flex:0 0 50%; max-width:50%; }
  #main[data-w="narrow"] .row > [class*="col-"]:has(> .stat-card),
  #main[data-w="narrow"] .row > [class*="col-"]:has(> a.stat-link),
  #main[data-w="narrow"] .row > [class*="col-"]:has(> .kz-kpi){ flex:0 0 100%; max-width:100%; }
  /* the Debtors aging chips: two per line on a narrowed page, and the rupee never breaks */
  #main[data-w="mid"] .dbt-aging > [class*="col-"]{ flex:0 0 50%; max-width:50%; }
  #main[data-w="narrow"] .dbt-aging > [class*="col-"]{ flex:0 0 100%; max-width:100%; }
}
.dbt-aging .fw-semibold{ white-space:nowrap; }

/* Weekly Scheduling — drag a task from one slot to another (pure client-side reorder;
   the grid still saves by cell position, so this only moves the text between inputs). */
.sch-cell { padding:2px !important; }
.sch-wrap { display:flex; align-items:center; gap:2px; }
.sch-grip { flex:0 0 auto; width:15px; text-align:center; color:var(--muted); cursor:grab;
  opacity:0; touch-action:none; user-select:none; font-size:.85rem; line-height:1; }
.sch-cell.has-task .sch-grip { opacity:.55; }
.sch-grip:hover { opacity:1 !important; }
.sch-grip:active { cursor:grabbing; }
.sch-in { flex:1 1 auto; min-width:0; }
/* Task auto-filled from the weekly plan (not yet saved to this day) — subtle tint + dotted underline. */
.sch-weekly { color:var(--kz); font-style:italic; background:rgba(79,70,229,.05);
  border-bottom:1px dotted rgba(79,70,229,.5) !important; }
html[data-theme="dark"] .sch-weekly { color:#A5B4FC; background:rgba(165,180,252,.08);
  border-bottom-color:rgba(165,180,252,.5) !important; }
.sch-cell.sch-dragging { opacity:.4; }
.sch-cell.sch-over { outline:2px dashed var(--kz); outline-offset:-2px; background:rgba(79,70,229,.09); }
.sch-ghost { position:fixed; z-index:3000; left:0; top:0; transform:translate(-50%,-150%);
  pointer-events:none; background:var(--kz); color:#fff; padding:4px 11px; border-radius:999px;
  font-size:.75rem; font-weight:600; max-width:220px; white-space:nowrap; overflow:hidden;
  text-overflow:ellipsis; box-shadow:var(--shadow-lg); }
html[data-theme="dark"] .sch-grip { color:#8a97a8; }
html[data-theme="dark"] .sch-cell.sch-over { background:rgba(165,180,252,.14); outline-color:#A5B4FC; }
.sidebar-backdrop { display:none; position:fixed; inset:0; background:rgba(0,0,0,.4); z-index:1035; }
/* mobile: sidebar slides in */
@media (max-width: 991.98px) {
  .sidebar { transform:translateX(-100%); }
  body.sb-open .sidebar { transform:translateX(0); }
  body.sb-open .sidebar-backdrop { display:block; }
  .content { margin-left:0; }
}
/* Dark mode */
html[data-theme="dark"] body { background:#0F172A; color:#E2E8F0; }
html[data-theme="dark"] .card { background:#1E293B; color:#E2E8F0; box-shadow:0 1px 4px rgba(0,0,0,.4); }
html[data-theme="dark"] .topbar { background:#1E293B; border-bottom-color:#334155; }
html[data-theme="dark"] .table { color:#E2E8F0; }
html[data-theme="dark"] .table thead th { background:var(--surface-sunken); color:var(--th-fg); }
html[data-theme="dark"] .table-hover tbody tr:hover > * { background:#243246; }
html[data-theme="dark"] .form-control, html[data-theme="dark"] .form-select, html[data-theme="dark"] .grid-input { background:#0F172A; color:#E2E8F0; border-color:#334155; }
html[data-theme="dark"] .btn-light { background:#243246; color:#E2E8F0; border-color:#334155; }
/* ⚠ #8a97a8 clears AA on the card (#1E293B) but lands at 4.37:1 on the RAISED
   surface (#243246), which is where the Gap Analysis notes sit. Lifted to the same
   ink as --fg-muted so one muted colour serves every dark surface. */
html[data-theme="dark"] .text-muted { color:#9CA9BC !important; }
html[data-theme="dark"] .page-title { color:#A5B4FC; }
html[data-theme="dark"] .dropdown-menu { background:#1E293B; color:#E2E8F0; }
html[data-theme="dark"] .dropdown-item { color:#E2E8F0; }
html[data-theme="dark"] .dropdown-item:hover { background:#243246; }

/* ============ Phase 4 — accessibility (WCAG AA) ============ */
/* Visible keyboard focus ring on all interactive elements */
a:focus-visible, button:focus-visible, .navlink:focus-visible,
[tabindex]:focus-visible, .form-control:focus-visible, .form-select:focus-visible {
  outline:2px solid var(--kz2); outline-offset:2px;
}
/* Keyboard-accessible clickable table rows */
tr[data-href]{ cursor:pointer; }
tr[data-href]:hover > *{ background:rgba(29,111,184,.05); }
tr[data-href]:focus-visible{ outline:2px solid var(--kz2); outline-offset:-2px; }
html[data-theme="dark"] tr[data-href]:hover > *{ background:#243246; }
/* Respect users who prefer reduced motion */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after { transition:none !important; animation:none !important; scroll-behavior:auto !important; }
}

/* ============ Phase 5 — Command palette (Ctrl/Cmd + K) ============ */
.cmdk-overlay{ display:none; position:fixed; inset:0; background:rgba(16,24,40,.45); z-index:2050; padding-top:12vh; }
.cmdk-overlay.show{ display:block; }
.cmdk-box{ width:min(580px,92vw); margin:0 auto; background:#fff; border-radius:14px; box-shadow:var(--shadow-lg); overflow:hidden; }
.cmdk-search{ display:flex; align-items:center; gap:10px; padding:14px 16px; border-bottom:1px solid var(--border); color:var(--muted); }
.cmdk-input{ border:0; outline:0; flex:1; font-size:1rem; background:transparent; color:var(--ink); }
.cmdk-list{ list-style:none; margin:0; padding:6px; max-height:52vh; overflow:auto; }
.cmdk-sec{ font-size:.68rem; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); font-weight:700; padding:8px 12px 4px; }
.cmdk-item{ display:flex; align-items:center; gap:11px; padding:9px 12px; border-radius:8px; cursor:pointer; color:var(--ink); }
.cmdk-item .bi{ color:var(--muted); width:18px; text-align:center; }
.cmdk-item.sel, .cmdk-item:hover{ background:var(--info-bg); color:var(--kz); }
.cmdk-item.sel .bi{ color:var(--kz); }
.cmdk-empty{ padding:18px; text-align:center; color:var(--muted); }
.cmdk-foot{ display:flex; gap:16px; padding:8px 16px; border-top:1px solid var(--border); font-size:.74rem; color:var(--muted); }
.cmdk-foot kbd{ background:var(--mut-bg); border-radius:4px; padding:1px 5px; color:var(--ink); font-family:inherit; }
.cmdk-trigger kbd{ background:rgba(0,0,0,.06); border-radius:4px; padding:0 5px; font-size:.72rem; font-family:inherit; }
html[data-theme="dark"] .cmdk-box{ background:#1E293B; }
html[data-theme="dark"] .cmdk-search{ border-bottom-color:#334155; color:#8a97a8; }
html[data-theme="dark"] .cmdk-input{ color:#E2E8F0; }
html[data-theme="dark"] .cmdk-input::placeholder{ color:#8a97a8; }
html[data-theme="dark"] .cmdk-item{ color:#E2E8F0; }
html[data-theme="dark"] .cmdk-item .bi{ color:#8a97a8; }
html[data-theme="dark"] .cmdk-item.sel, html[data-theme="dark"] .cmdk-item:hover{ background:#243246; color:#C7D2FE; }
html[data-theme="dark"] .cmdk-item.sel .bi, html[data-theme="dark"] .cmdk-item:hover .bi{ color:#C7D2FE; }
html[data-theme="dark"] .cmdk-sec{ color:#8a97a8; }
html[data-theme="dark"] .cmdk-foot{ border-top-color:#334155; }
html[data-theme="dark"] .cmdk-foot kbd{ background:#334155; color:#E2E8F0; }
/* topbar/list search input-group icon box in dark */
html[data-theme="dark"] .input-group-text{ background:#243246; color:#C7D2FE; border-color:#334155; }

/* ============ Date picker (flatpickr) — dark theme ============ */
html[data-theme="dark"] .flatpickr-calendar{ background:#1E293B; color:#E2E8F0; box-shadow:var(--shadow-lg); border:1px solid #334155; }
html[data-theme="dark"] .flatpickr-calendar.arrowTop:before{ border-bottom-color:#334155; }
html[data-theme="dark"] .flatpickr-calendar.arrowTop:after{ border-bottom-color:#1E293B; }
html[data-theme="dark"] .flatpickr-calendar.arrowBottom:before{ border-top-color:#334155; }
html[data-theme="dark"] .flatpickr-calendar.arrowBottom:after{ border-top-color:#1E293B; }
html[data-theme="dark"] .flatpickr-months .flatpickr-month, html[data-theme="dark"] .flatpickr-current-month input.cur-year,
html[data-theme="dark"] .flatpickr-current-month .flatpickr-monthDropdown-months{ color:#E2E8F0; fill:#E2E8F0; }
html[data-theme="dark"] .flatpickr-monthDropdown-months{ background:#1E293B; }
html[data-theme="dark"] .flatpickr-weekday{ color:#8a97a8; }
html[data-theme="dark"] span.flatpickr-weekday{ color:#8a97a8; }
html[data-theme="dark"] .flatpickr-day{ color:#cdd7e3; }
html[data-theme="dark"] .flatpickr-day:hover, html[data-theme="dark"] .flatpickr-day:focus{ background:#243246; border-color:#243246; }
html[data-theme="dark"] .flatpickr-day.today{ border-color:var(--kz2); }
html[data-theme="dark"] .flatpickr-day.selected, html[data-theme="dark"] .flatpickr-day.selected:hover{ background:var(--kz2); border-color:var(--kz2); color:#fff; }
html[data-theme="dark"] .flatpickr-day.flatpickr-disabled, html[data-theme="dark"] .flatpickr-day.prevMonthDay, html[data-theme="dark"] .flatpickr-day.nextMonthDay{ color:#56657a; }
html[data-theme="dark"] .flatpickr-months .flatpickr-prev-month svg, html[data-theme="dark"] .flatpickr-months .flatpickr-next-month svg{ fill:#cdd7e3; }
html[data-theme="dark"] .flatpickr-months .flatpickr-prev-month:hover svg, html[data-theme="dark"] .flatpickr-months .flatpickr-next-month:hover svg{ fill:var(--kz2); }
html[data-theme="dark"] .numInputWrapper span.arrowUp:after{ border-bottom-color:#8a97a8; }
html[data-theme="dark"] .numInputWrapper span.arrowDown:after{ border-top-color:#8a97a8; }
html[data-theme="dark"] .numInputWrapper:hover{ background:#243246; }

/* ============ Phase 3b — onboarding checklist ============ */
.onboard-card{ border-left:3px solid var(--kz2); }
.onboard-step{ display:flex; align-items:center; gap:9px; padding:10px 12px; border:1px solid var(--border);
  border-radius:10px; text-decoration:none; color:var(--ink); font-size:.9rem; font-weight:600; background:#fff; }
.onboard-step:hover{ border-color:var(--kz2); background:var(--info-bg); color:var(--kz); }
.onboard-step .bi:first-child{ color:var(--kz2); font-size:1.05rem; }
.onboard-step.done{ color:var(--muted); font-weight:500; background:var(--mut-bg); }
.onboard-step.done .bi:first-child{ color:var(--ok); }
.onboard-step.done span{ text-decoration:line-through; }
html[data-theme="dark"] .onboard-step{ background:#243246; color:#E2E8F0; border-color:#334155; }

/* ============ Phase 3b — settings tabs ============ */
.settings-tabs .nav-link{ color:var(--muted); font-weight:600; border:0; border-bottom:2px solid transparent; }
.settings-tabs .nav-link.active{ color:var(--kz); background:transparent; border-bottom-color:var(--kz); }

/* ============ Phase 6 — loading states & micro-interactions ============ */
/* Top navigation progress bar (perceived speed) */
#navprogress{ position:fixed; top:0; left:0; height:3px; width:0; opacity:0; z-index:3000;
  background:linear-gradient(90deg,var(--kz2),#5aa9e6); box-shadow:0 0 8px rgba(29,111,184,.55);
  transition:width .25s ease, opacity .3s ease; }
#navprogress.go{ opacity:1; }
/* Button loading spinner + duplicate-submit guard */
.btn.is-loading{ color:transparent !important; pointer-events:none; position:relative; }
.btn.is-loading::after{ content:""; position:absolute; width:15px; height:15px; top:calc(50% - 7px); left:calc(50% - 7px);
  border:2px solid rgba(255,255,255,.5); border-top-color:#fff; border-radius:50%; animation:btnspin .6s linear infinite; }
.btn-outline-secondary.is-loading::after, .btn-light.is-loading::after, .btn-outline-primary.is-loading::after,
.btn-link.is-loading::after{ border-color:rgba(13,59,102,.3); border-top-color:var(--kz); }
@keyframes btnspin{ to{ transform:rotate(360deg); } }
@media (prefers-reduced-motion: reduce){ .btn.is-loading::after{ animation:none; } #navprogress{ transition:opacity .2s; } }

/* ── WS-6 motion & loading (2026-07-23) ────────────────────────────────────────
   All reference the WS-1 motion tokens and are neutralised by the reduced-motion
   guard near the top of this file — do not add a duration that bypasses it. */

/* Skeleton: a shimmering placeholder shaped like the content it stands in for. Use
   `.sk` on a block, or `.sk-line`/`.sk-line.sm` for text rows, while data loads. */
.sk, .sk-line{ position:relative; overflow:hidden; background:#eef1f6; border-radius:var(--radius-sm); }
.sk-line{ height:12px; margin:7px 0; border-radius:6px; }
.sk-line.sm{ height:9px; width:60%; }
.sk-line.lg{ height:20px; width:40%; }
.sk::after, .sk-line::after{ content:""; position:absolute; inset:0;
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.65), transparent);
  transform:translateX(-100%); animation:sk-shimmer 1.25s var(--ease-out) infinite; }
@keyframes sk-shimmer{ to{ transform:translateX(100%); } }
html[data-theme="dark"] .sk, html[data-theme="dark"] .sk-line{ background:#273244; }
html[data-theme="dark"] .sk::after, html[data-theme="dark"] .sk-line::after{
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.08), transparent); }

/* A saved/success tick that draws itself once, e.g. flashed after a form POST. */
.ok-tick{ display:inline-block; width:1em; height:1em; vertical-align:-.15em; }
.ok-tick circle{ fill:none; stroke:var(--ok); stroke-width:3; stroke-dasharray:76; stroke-dashoffset:76;
  animation:tick-ring var(--dur-slow) var(--ease-out) forwards; }
.ok-tick path{ fill:none; stroke:var(--ok); stroke-width:4; stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:26; stroke-dashoffset:26; animation:tick-check var(--dur-base) var(--ease-out) .28s forwards; }
@keyframes tick-ring{ to{ stroke-dashoffset:0; } }
@keyframes tick-check{ to{ stroke-dashoffset:0; } }

/* A KPI number counting up on first paint (JS adds .is-counting; see ux.js). The
   value is always in the DOM as text, so with JS off or reduced-motion it just shows. */
.stat-card .v.is-counting{ font-variant-numeric:tabular-nums; }

/* ============ Phase 7 — accessibility ============ */
/* Skip-to-content link (visible on keyboard focus) */
.skip-link{ position:fixed; top:-60px; left:12px; z-index:4000; background:var(--kz); color:#fff;
  padding:9px 16px; border-radius:8px; font-weight:600; text-decoration:none; transition:top .2s ease; }
.skip-link:focus{ top:12px; color:#fff; outline:2px solid #ffd166; outline-offset:2px; }

/* ============ Phase 8 — responsive data tables (mobile card view) ============ */
@media (max-width: 640px){
  .page-wrap{ padding:16px 14px; }
  .table-sort thead{ display:none; }
  .table-sort, .table-sort tbody{ display:block; }
  .table-sort tr[data-href]{ display:block; border:1px solid var(--border); border-radius:10px;
    margin-bottom:10px; box-shadow:var(--shadow-sm); overflow:hidden; background:#fff; }
  .table-sort tr[data-href] td:first-child, .table-sort tr[data-href] td:last-child{ display:none; }
  .table-sort tr[data-href] td[data-label]{ display:flex; justify-content:space-between; align-items:center;
    gap:12px; border:0; border-bottom:1px solid var(--border); padding:8px 13px; text-align:right; }
  .table-sort tr[data-href] td[data-label]:last-of-type{ border-bottom:0; }
  .table-sort tr[data-href] td[data-label]::before{ content:attr(data-label); font-weight:600;
    color:var(--muted); text-align:left; flex:0 0 auto; }
  html[data-theme="dark"] .table-sort tr[data-href]{ background:#1E293B; }
}

/* ============ Pricing cards — shared by client Billing + admin Plans console ============ */
.term-toggle{ display:inline-flex; background:var(--card,#fff); border:1px solid var(--border,#e7ebf3); border-radius:999px; padding:4px; gap:2px; }
.term-toggle button{ border:none; background:transparent; padding:.5rem 1.4rem; border-radius:999px; font-weight:700; font-size:.9rem; color:var(--muted,#64748B); cursor:pointer; transition:.15s; }
.term-toggle button.active{ background:linear-gradient(120deg,#4F46E5,#2563EB 60%,#06B6D4); color:#fff; box-shadow:0 8px 18px -8px rgba(79,70,229,.6); }
.term-toggle button:focus-visible{ outline:2px solid #4F46E5; outline-offset:2px; }
.plan-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(258px,1fr)); gap:20px; max-width:900px; margin:0 auto; align-items:stretch; }
.plan-grid.wide{ max-width:1200px; }
@media (max-width:575px){ .plan-grid{ grid-template-columns:1fr; gap:16px; } }
.plan-card{ position:relative; border:1px solid var(--border,#e7ebf3); border-radius:20px; padding:26px 24px; background:var(--card,#fff);
  box-shadow:0 18px 40px -26px rgba(15,23,42,.28); display:flex; flex-direction:column; text-align:left; }
@media (max-width:575px){ .plan-card{ padding:22px 18px; } }
.plan-card.featured{ border:2px solid transparent;
  background:linear-gradient(var(--card,#fff),var(--card,#fff)) padding-box, linear-gradient(120deg,#4F46E5,#06B6D4) border-box;
  box-shadow:0 26px 56px -24px rgba(79,70,229,.45); }
.plan-card.inactive{ opacity:.62; }
.plan-ribbon{ position:absolute; top:-13px; left:50%; transform:translateX(-50%); background:linear-gradient(120deg,#4F46E5,#06B6D4);
  color:#fff; font-size:.72rem; font-weight:700; letter-spacing:.06em; padding:4px 14px; border-radius:999px; text-transform:uppercase; white-space:nowrap; }
.plan-name{ font-size:1.15rem; font-weight:700; letter-spacing:-.01em; }
.plan-key{ font-size:.72rem; color:var(--muted,#94A3B8); font-weight:600; }
.plan-price{ font-size:2.3rem; font-weight:700; letter-spacing:-.02em; line-height:1; margin-top:6px;
  background:linear-gradient(120deg,#4F46E5,#06B6D4); -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent; }
@media (max-width:575px){ .plan-price{ font-size:2rem; } }
.plan-period{ color:var(--muted,#64748B); font-size:.85rem; font-weight:600; }
.plan-sub{ color:var(--muted,#64748B); font-size:.85rem; margin:2px 0 14px; }
.plan-feats{ list-style:none; padding:0; margin:0 0 18px; flex:1; }
.plan-feats li{ display:flex; gap:9px; align-items:flex-start; padding:5px 0; font-size:.9rem; }
.plan-feats li i{ color:#10B981; margin-top:2px; }
.plan-feats li.muted-feat{ color:var(--muted,#94A3B8); } .plan-feats li.muted-feat i{ color:#CBD5E1; }
.current-chip{ display:inline-block; background:#ECFDF5; color:#059669; border:1px solid #A7F3D0; font-weight:700; font-size:.72rem; padding:2px 10px; border-radius:999px; }
.status-pill{ display:inline-block; font-size:.7rem; font-weight:700; padding:2px 9px; border-radius:999px; }
.status-pill.on{ background:#ECFDF5; color:#059669; border:1px solid #A7F3D0; }
.status-pill.off{ background:#F1F5F9; color:#64748B; border:1px solid #E2E8F0; }
.plan-actions{ display:flex; flex-wrap:wrap; gap:6px; border-top:1px solid var(--border,#eef1f6); padding-top:12px; margin-top:2px; }
.plan-actions form{ margin:0; }

/* --- Pricing cards: hover & motion (mouse devices only; respects reduced-motion) --- */
.plan-card{ transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease, opacity .18s ease; }
@media (hover:hover) and (prefers-reduced-motion: no-preference){
  .plan-card:hover{ transform:translateY(-5px); box-shadow:0 30px 58px -22px rgba(79,70,229,.42); border-color:#C7D2FE; }
  .plan-card.featured:hover{ transform:translateY(-6px); box-shadow:0 36px 68px -20px rgba(79,70,229,.60); }
  .plan-card.inactive:hover{ opacity:1; }
}
.term-toggle button:not(.active):hover{ color:#4F46E5; background:rgba(79,70,229,.08); }
.plan-actions .btn{ transition: transform .12s ease; }
.plan-actions .btn:hover:not(:disabled){ transform:translateY(-1px); }

/* --- Pricing cards: dark theme (plan-card is not a .card, so it needs its own rules) --- */
html[data-theme="dark"] .plan-card{ background:#1E293B; border-color:#334155; color:#E2E8F0; box-shadow:0 18px 40px -26px rgba(0,0,0,.6); }
html[data-theme="dark"] .plan-card.featured{
  background:linear-gradient(#1E293B,#1E293B) padding-box, linear-gradient(120deg,#4F46E5,#06B6D4) border-box; }
html[data-theme="dark"] .term-toggle{ background:#1E293B; border-color:#334155; }
html[data-theme="dark"] .term-toggle button:not(.active){ color:#94A3B8; }
html[data-theme="dark"] .term-toggle button:not(.active):hover{ color:#A5B4FC; background:rgba(99,102,241,.15); }
html[data-theme="dark"] .current-chip{ background:rgba(16,185,129,.14); border-color:rgba(16,185,129,.4); color:#34D399; }
html[data-theme="dark"] .status-pill.on{ background:rgba(16,185,129,.14); border-color:rgba(16,185,129,.4); color:#34D399; }
html[data-theme="dark"] .status-pill.off{ background:#243246; color:#94A3B8; border-color:#334155; }
html[data-theme="dark"] .plan-actions{ border-top-color:#334155; }
/* was #64748B / #475569 — 3.24:1 on the dark plan card, under AA (dark sweep 2026-09-11) */
html[data-theme="dark"] .plan-feats li.muted-feat{ color:#94A3B8; }
html[data-theme="dark"] .plan-feats li.muted-feat i{ color:#64748B; }
@media (hover:hover) and (prefers-reduced-motion: no-preference){
  html[data-theme="dark"] .plan-card:hover{ border-color:#4F46E5; box-shadow:0 30px 58px -20px rgba(79,70,229,.5); }
}

/* --- Pricing cards: whole card is clickable (opens Edit on admin / checkout on billing) --- */
.plan-card.clickable{ cursor:pointer; }
.plan-card.clickable:focus-visible{ outline:3px solid rgba(79,70,229,.55); outline-offset:3px; }

/* --- A11y: contrast fixes (axe) --- */
.cmdk-trigger kbd{ color:#334155; }
html[data-theme="dark"] .cmdk-trigger kbd{ color:#CBD5E1; background:rgba(255,255,255,.12); }
.current-chip{ color:#047857; }
/* Inactive plans: grayscale instead of opacity-dimming (opacity killed text contrast — axe). */
.plan-card.inactive{ opacity:1; filter:grayscale(.65); background:#FAFBFC; }
@media (hover:hover) and (prefers-reduced-motion: no-preference){
  .plan-card.inactive:hover{ filter:none; background:var(--card,#fff); }
}
html[data-theme="dark"] .plan-card.inactive{ background:#1B2534; }
/* A11y contrast: status pills + outline action buttons (axe ≥4.5:1) */
.status-pill.on{ color:#047857; }
.status-pill.off{ color:#475569; }
.plan-actions .btn-outline-primary{ color:#0B5ED7; }
.plan-actions .btn-outline-success{ color:#146C43; border-color:#146C43; }
.plan-actions .btn-outline-warning{ color:#B45309; border-color:#D97706; }
.plan-actions .btn-outline-warning:hover{ background:#D97706; border-color:#D97706; color:#fff; }
/* ⚠ The three light inks above are EXPLICIT `color`s at (0,2,0), so they beat the app-wide dark
   pairing, which only sets --bs-btn-color. Measured 2.39-2.91:1 on the dark card (2026-09-11,
   found by adding the platform persona to testing/dark_mode.py). Same inks as the global pairing. */
html[data-theme="dark"] .plan-actions .btn-outline-primary{ color:#A5B4FC; border-color:#4A52A8; }
html[data-theme="dark"] .plan-actions .btn-outline-success{ color:#4CD6A0; border-color:#2F6B54; }
html[data-theme="dark"] .plan-actions .btn-outline-warning{ color:#E7B24A; border-color:#7A5E22; }
html[data-theme="dark"] .plan-actions .btn-outline-primary:hover{ background:#4A52A8; color:#fff; }
html[data-theme="dark"] .plan-actions .btn-outline-success:hover{ background:#2F6B54; color:#fff; }
html[data-theme="dark"] .plan-actions .btn-outline-warning:hover{ background:#7A5E22; border-color:#7A5E22; color:#fff; }
/* Contain absolutely-positioned children (e.g. .visually-hidden th text) inside the
   scroll wrapper — otherwise they escape the clip and cause phantom page overflow. */
.table-responsive{ position:relative; }

/* Select placeholder — a required select still on its empty "Select" option shows
   muted placeholder-style text (like input::placeholder) until a real choice is made. */
select.form-select:required:invalid{ color:#8A94A6; }
html[data-theme="dark"] select.form-select:required:invalid{ color:#7C8798; }
select.form-select:required:invalid option:not([value=""]){ color:var(--bs-body-color,#212529); }

/* Tall modals (e.g. the plan editor) must always keep the footer/Save button on
   screen on short laptop heights. Cap the body and let it scroll — id-specificity
   overrides Bootstrap's own scrollable rule if that ever fails to cap the dialog. */
#planModal .modal-body{ max-height:calc(100vh - 190px); overflow-y:auto; }

/* ============================================================================
   Finance & Accounts (Jeweller edition) — finance.kizunalogic.com
   Category (metal) tones are semantic to the subject; brand accent stays indigo→cyan.
   ========================================================================== */
:root{
  --fin-gold:#B8862B; --fin-silver:#7C8AA0; --fin-diamond:#1E96D6; --fin-fixed:#7C6FF0;
  /* shareholding donut slots (--sh-1..8), cycled per holder */
  --sh-1:#4F46E5; --sh-2:#06B6D4; --sh-3:#B8862B; --sh-4:#157a5b;
  --sh-5:#c4362f; --sh-6:#7C6FF0; --sh-7:#1565a6; --sh-8:#7C8AA0;
}
/* ═══════════════════════════════════════════════════════════════════════════
   🔴 DARK PAIRINGS FOR THE LEGACY BRAND TOKENS (2026-08-27)

   The Foundations layer (§2a) dark-paired its OWN set — --fg*, --surface-*,
   --hairline*, --good/--warn/--bad/--info. The older brand set beside it was
   never paired at all, and every one of them is a LIGHT value that carried
   straight into dark mode:

     --muted  #64748B  secondary text everywhere -> 3.07:1 on a dark card
     --ok     #157a5b  "Balance Sheet tallies"   -> 2.76:1
     --mut-bg #eef1f5  a near-WHITE fill         -> white text on it = 1.13:1
     --border #e7ebf3  a near-white hairline drawn across a dark card

   That is one omission producing findings on 44 pages — the muted token alone
   is most of the secondary text in the product. Paired here rather than per
   component, so every page inherits the fix at once (the golden rule, §0/§6).

   ⚠ --ink is deliberately NOT paired. §2a documents it as a fixed light-mode
   value precisely so that reaching for it on dark text is a mistake you find;
   flipping it here would hide that class of bug instead of surfacing it.
   ═══════════════════════════════════════════════════════════════════════════ */
html[data-theme="dark"]{
  /* matches --fg-muted, which is the theme-aware muted the Foundations layer
     already defines — one muted ink in dark, not two that disagree */
  --muted:#93A0B4;
  --border:#334155;
  --ok:#34D399; --ok-bg:rgba(16,185,129,.14);
  --mut-bg:rgba(148,163,184,.14);
  /* the six KPI accents are light-surface inks; lift them onto the dark ground */
  --acc-teal:#3FB8A6; --acc-purple:#B98BE0; --acc-clay:#E8836B;
  --acc-ochre:#E7B24A; --acc-blue:#7FB2E8; --acc-slate:#93A0B4;
}
/* Bootstrap's own semantic inks are fixed-light too, and they are used across the
   app for buttons and small print. Pair the ones the sweep actually found. */
html[data-theme="dark"]{
  --bs-secondary-color:#93A0B4;
}
html[data-theme="dark"] .text-secondary{ color:#93A0B4 !important; }
html[data-theme="dark"] .text-success{ color:#34D399 !important; }
html[data-theme="dark"] .text-danger{ color:var(--bad) !important; }
html[data-theme="dark"] .text-warning{ color:#E7B24A !important; }
html[data-theme="dark"] .btn-outline-secondary{ --bs-btn-color:#A7B3C4; --bs-btn-border-color:#4A5568;
  --bs-btn-hover-bg:#334155; --bs-btn-hover-border-color:#5A6678; --bs-btn-hover-color:#E9EDF6; }
html[data-theme="dark"] .btn-outline-danger{ --bs-btn-color:#F58A8A; --bs-btn-border-color:#8C3B3B;
  --bs-btn-hover-bg:#7F2E2E; --bs-btn-hover-border-color:#A04A4A; --bs-btn-hover-color:#fff; }
html[data-theme="dark"] .btn-outline-warning{ --bs-btn-color:#E7B24A; --bs-btn-border-color:#7A5E22; }
html[data-theme="dark"] .btn-outline-success{ --bs-btn-color:#4CD6A0; --bs-btn-border-color:#2F6B54; }
html[data-theme="dark"] .btn-outline-danger{ --bs-btn-disabled-color:#8E5A5A; --bs-btn-disabled-border-color:#5A3535; }
html[data-theme="dark"] .btn-outline-secondary{ --bs-btn-disabled-color:#5C6878; --bs-btn-disabled-border-color:#3A4454; }
html[data-theme="dark"] .btn-outline-primary{ --bs-btn-color:#A5B4FC; --bs-btn-border-color:#4A52A8;
  --bs-btn-hover-bg:#3A3F8F; --bs-btn-hover-border-color:#5C64C4; --bs-btn-hover-color:#fff; }
/* `.bs-mut`'s ink is a fixed light-mode slate (#566) — on the dark muted fill that is
   1.90:1, the worst remaining reading in the app. The badge is a NEUTRAL status, so it
   takes the muted ink rather than a colour of its own. */
html[data-theme="dark"] .bs-mut{ color:#B4C0D0; }
/* --bad on its OWN tinted surface. The dark red (#F27272) reads fine on a plain card, but
   `--bad-bg` warms the ground to ~rgb(60,61,61) and the pair lands at 3.83:1 — under AA on
   exactly the surfaces that carry it (the cost-reduction flag, an overdue debtor, a petty-cash
   overspend). Lifted so it clears AA on the tint AND stays comfortable on the card.
   ⚠ A semantic ink has to be judged against the tint it is PAIRED with, not the page. */
html[data-theme="dark"]{ --bad:#FF9B9B; }
/* Table headers sit on --surface-sunken/--surface-raised, which is lighter than the card, so
   a 62%-mixed header ink lands at 4.37:1 there. Raise the mix for dark only. */
html[data-theme="dark"]{ --th-fg:color-mix(in srgb, var(--fg) 78%, var(--fg-muted)); }
/* The settings tab's active ink is brand indigo, which is a LIGHT-surface brand colour;
   on dark it needs the brand-on-dark value (§2). The underline can stay full-strength. */
html[data-theme="dark"] .settings-tabs .nav-link.active{ color:#A5B4FC; border-bottom-color:#A5B4FC; }
html[data-theme="dark"] .settings-tabs .nav-link{ color:#93A0B4; }

html[data-theme="dark"]{
  --fin-gold:#D9A845; --fin-silver:#9AA8BE; --fin-diamond:#4FB6E8; --fin-fixed:#A99CF6;
  --sh-1:#A5B4FC; --sh-2:#4FB6E8; --sh-3:#D9A845; --sh-4:#4cc79c;
  --sh-5:#f08a82; --sh-6:#A99CF6; --sh-7:#69b4e6; --sh-8:#9AA8BE;
}
.fin-dot{ display:inline-block; width:10px; height:10px; border-radius:50%; flex:0 0 auto; background:var(--muted); }
.fin-gold{ background:var(--fin-gold); } .fin-silver{ background:var(--fin-silver); }
.sh-1{ background:var(--sh-1); } .sh-2{ background:var(--sh-2); } .sh-3{ background:var(--sh-3); }
.sh-4{ background:var(--sh-4); } .sh-5{ background:var(--sh-5); } .sh-6{ background:var(--sh-6); }
.sh-7{ background:var(--sh-7); } .sh-8{ background:var(--sh-8); }
.fin-diamond{ background:var(--fin-diamond); } .fin-fixed{ background:var(--fin-fixed); }
.fin-txt-gold{ color:var(--fin-gold); } .fin-txt-silver{ color:var(--fin-silver); }
.fin-txt-diamond{ color:var(--fin-diamond); } .fin-txt-fixed{ color:var(--fin-fixed); }

/* monthly bar chart (no JS/CDN) */
/* Planned-revenue column chart — Financial Dashboard (generic edition).
   🔴 RENAMED FROM `.fin-bar*` ON 2026-08-19, AND THE NAME IS THE WHOLE POINT.
   Two completely different components were both called `.fin-bar`: the budget-variance
   progress bar near the top of this file (`min-width:110px; height:8px`, with `> i`
   fills) and this chart's column. The variance rule's **`min-width:110px` was never
   overridden** — a later rule wins on `width`/`max-width`, but `min-width` is a separate
   property and beats `max-width` outright. So each of the twelve columns rendered 110px
   wide inside a ~690px card: 1386px of chart, and the page scrolled sideways by 400–500px
   with the bars running out past the card's edge. The same collision also gave every bar
   `height:8px`, so all twelve drew as identical stubs and the chart showed nothing at all.
   Never reuse a component class name; scope or rename. */
.plan-bars{ display:flex; align-items:flex-end; gap:6px; height:190px; padding-top:8px; }
/* ⚠ `flex:1 1 0` + `min-width:0` — a flex item defaults to `min-width:auto`, which
   refuses to shrink below its content and is the other way twelve columns burst a card. */
.plan-col{ flex:1 1 0; min-width:0; display:flex; flex-direction:column;
  align-items:center; justify-content:flex-end; height:100%; }
/* ⚠ The track needs a DEFINITE height or the bar's `height:<n>%` has nothing to resolve
   against and collapses to `min-height`. The old rule set `height:auto` + `min-height`
   on the container, which is exactly that bug. */
.plan-track{ flex:1 1 auto; min-height:0; width:100%; display:flex;
  align-items:flex-end; justify-content:center; }
.plan-bar{ width:100%; max-width:22px; border-radius:6px 6px 0 0; min-height:3px;
  background:linear-gradient(180deg, var(--kz), var(--accent)); transition:height .5s ease; }
.plan-bar.b2{ background:linear-gradient(180deg, #94a3b8, #cbd5e1); }
.plan-lbl{ font-size:.66rem; color:var(--muted); margin-top:6px; line-height:1;
  white-space:nowrap; }

/* donut (category mix) via conic-gradient set inline */
.fin-sales-grid{ display:grid; grid-template-columns:minmax(240px, 320px) minmax(320px, 1fr); gap:2rem; align-items:flex-start; min-height:260px; }
@media (max-width: 991.98px){ .fin-sales-grid{ grid-template-columns:1fr; } .fin-donut{ margin:0 auto; } }
.fin-donut{ width:230px; height:230px; border-radius:50%; position:relative; }
.fin-donut::after{ content:""; position:absolute; inset:28px; border-radius:50%; background:var(--card, #fff); }
html[data-theme="dark"] .fin-donut::after{ background:#1E293B; }
.fin-donut .c{ position:absolute; inset:0; display:grid; place-content:center; text-align:center; z-index:1; }
.fin-donut .c .cv{ font-weight:700; letter-spacing:-.02em; font-size:1.35rem; }
.fin-donut .c .cl{ font-size:.9rem; color:var(--muted); margin-top:.25rem; }
.fin-sales-list{ min-width:0; padding:1.25rem; display:grid; gap:0.9rem; background:rgba(248,250,252,.95); border-radius:20px; box-shadow:inset 0 1px 0 rgba(255,255,255,.8); }
@media (max-width: 767.98px){ .fin-sales-list{ padding:1rem; } }
.card .d-flex.align-items-center.gap-3.flex-wrap{ gap:2rem; align-items:flex-start; }
.card .d-flex.align-items-center.gap-2.py-1.small{ gap:1rem; }
.card .d-flex.align-items-center.gap-2.py-1.small .fin-dot{ width:12px; height:12px; }
.fin-sales-row{ display:grid; grid-template-columns:min-content minmax(0,1fr) min-content min-content; gap:.85rem 1rem; align-items:center; padding:1.1rem 0; border-bottom:1px solid rgba(15,23,42,.14); }
.fin-sales-row:last-child{ border-bottom:none; }
.fin-sales-label{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:.98rem; font-weight:600; }
.fin-sales-amount{ width:150px; white-space:nowrap; font-size:.9rem; color:var(--muted); justify-self:end; }
.fin-sales-row b{ white-space:nowrap; }
.card .page-title.finance-section-heading{ margin-bottom:1.75rem; }
.card .page-title.finance-section-heading span.text-muted{ margin-top:.2rem; }
.card .page-title.finance-section-heading i{ margin-right:.5rem; }
.card .page-title{ gap:1.1rem; }
.fin-sales-list .fin-sales-row span, .fin-sales-row b{ line-height:1.35; }
.fin-sales-row span.fin-sales-label{ color:var(--ink); }
.fin-sales-row .tnum{ color:#0f172a; }
.card .fin-sales-list{ transition:background .3s ease, transform .3s ease; }
.card .fin-sales-list:hover{ background:rgba(248,250,252,.99); transform:translateY(-1px); }
.card .page-title.finance-section-heading{ display:flex; align-items:center; justify-content:space-between; gap:1rem; }

/* Sales-by-category (Overview) — an interactive donut (Chart.js on top of a reliable
   CSS-ring fallback) with a padded centre that shows the ABBREVIATED total (so the
   figure never touches the ring), above a detailed legend where each category carries
   its own % bar + exact amount + margin. Consistent premium palette by rank. */
.scat{ display:flex; flex-direction:column; align-items:center; gap:1.35rem; }
.scat-chart{ position:relative; width:212px; height:212px; flex:0 0 212px; }
.scat-ring{ position:absolute; inset:0; border-radius:50%; }
.scat-ring::after{ content:""; position:absolute; inset:31px; border-radius:50%; background:#fff; }
html[data-theme="dark"] .scat-ring::after{ background:#1E293B; }
.scat-chart canvas{ position:absolute; inset:0; width:100% !important; height:100% !important; z-index:1; }
.scat-center{ position:absolute; inset:0; display:grid; place-content:center; text-align:center; z-index:2; pointer-events:none; padding:26px; }
.scat-total{ font-weight:700; letter-spacing:-.02em; font-size:1.55rem; line-height:1; }
.scat-sub{ font-size:.7rem; color:var(--muted); margin-top:6px; text-transform:uppercase; letter-spacing:.06em; }
.scat-legend{ width:100%; display:flex; flex-direction:column; gap:.95rem; }
.scat-top{ display:flex; align-items:center; gap:.55rem; }
.scat-dot{ width:12px; height:12px; border-radius:4px; flex:0 0 12px; }
.scat-name{ flex:1; min-width:0; font-weight:600; font-size:.93rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.scat-pct{ font-size:.9rem; font-weight:700; font-variant-numeric:tabular-nums; flex:0 0 auto; }
.scat-bar{ height:6px; border-radius:4px; background:var(--mut-bg); overflow:hidden; margin:7px 0 4px 27px; }
.scat-bar span{ display:block; height:100%; border-radius:4px; transition:width .5s ease; }
.scat-amt{ margin-left:27px; font-size:.8rem; color:var(--muted); font-variant-numeric:tabular-nums; }
.scat-sep{ opacity:.45; padding:0 .2rem; }
.scat-item{ transition:transform .12s ease; }
@media (hover:hover){ .scat-item:hover{ transform:translateX(2px); } }

/* Petty-cash receipt attachment — interactive drop zone + preview (in the Add/Edit modal). */
.pc-drop{ position:relative; border:1.5px dashed var(--border); border-radius:var(--radius-sm);
  background:var(--mut-bg); min-height:96px; display:flex; align-items:center; justify-content:center;
  text-align:center; padding:14px; transition:border-color .15s ease, background .15s ease; }
.pc-drop.drag{ border-color:var(--kz); background:rgba(79,70,229,.06); }
.pc-file{ position:absolute; inset:0; width:100%; height:100%; opacity:0; cursor:pointer; }
.pc-drop-empty{ color:var(--muted); display:flex; flex-direction:column; align-items:center; gap:3px; pointer-events:none; }
.pc-drop-empty .bi{ font-size:1.5rem; color:var(--kz); }
.pc-drop-picked{ display:flex; align-items:center; gap:12px; width:100%; text-align:left; }
.pc-preview{ width:64px; height:64px; object-fit:cover; border-radius:8px; border:1px solid var(--border); flex:0 0 auto; background:#fff; }
.pc-pdfic{ width:64px; height:64px; border-radius:8px; border:1px solid var(--border); display:grid; place-items:center; flex:0 0 auto; background:#fff; }
.pc-pdfic .bi{ font-size:1.9rem; color:var(--bad); }
.pc-pick-meta{ flex:1; min-width:0; }
.pc-drop-picked .btn{ position:relative; z-index:1; flex:0 0 auto; }
.pc-current{ font-size:.85rem; }
.pc-viewbtn .bi{ color:var(--kz); }
html[data-theme="dark"] .pc-drop{ background:#243246; }
html[data-theme="dark"] .pc-drop.drag{ background:rgba(79,70,229,.16); }
html[data-theme="dark"] .pc-preview, html[data-theme="dark"] .pc-pdfic{ background:#0F172A; }

/* Revenue & profit trend (Overview) — interactive Sales-bars + GP-line chart that flexes
   to fill the card, above a 4-up insights strip (best month / avg / GP / total). */
.rt-chart{ position:relative; flex:1 1 auto; min-height:210px; width:100%; }
.rt-chart canvas{ position:absolute; inset:0; width:100% !important; height:100% !important; }
.rt-stats{ display:grid; grid-template-columns:repeat(4,1fr); gap:.6rem; margin-top:1.1rem; }
@media (max-width:575.98px){ .rt-stats{ grid-template-columns:repeat(2,1fr); } }
.rt-stat{ display:flex; flex-direction:column; gap:1px; padding:.7rem .8rem; border:1px solid var(--border);
  border-radius:13px; background:var(--mut-bg); }
.rt-stat .k{ font-size:.66rem; text-transform:uppercase; letter-spacing:.04em; color:var(--muted); font-weight:700; }
.rt-stat .k i{ margin-right:2px; }
.rt-stat .v{ font-size:1.02rem; font-weight:700; letter-spacing:-.01em; margin-top:2px; }
.rt-stat .d{ font-size:.72rem; color:var(--muted); }
html[data-theme="dark"] .rt-stat{ background:#0f1a2e; border-color:#2a3852; }

/* Debtors — expanded per-customer detail. An inset panel (left accent + subtle fill +
   breathing room) with a LIGHT header, so the bills read as a nested detail rather than
   a second full table butting against the rows above/below. */
.dbt-detail{ padding:.5rem 1.25rem 1.1rem 2.4rem; background:var(--mut-bg); border-left:3px solid var(--kz); }
.dbt-detail-tbl{ background:transparent; margin:0; }
.dbt-detail-tbl thead th{ background:transparent !important; color:var(--muted) !important; font-weight:700;
  font-size:.68rem; letter-spacing:.04em; border-bottom:1px solid var(--border); padding:.55rem .55rem .6rem; }
.dbt-detail-tbl tbody td{ padding:.7rem .55rem; border-color:var(--border); vertical-align:middle; }
.dbt-detail-tbl tbody tr:last-child > td{ border-bottom:0; }
.dbt-detail-tbl .ps-4{ padding-left:.55rem !important; }
.dbt-detail-tbl .pe-3{ padding-right:.55rem !important; }
html[data-theme="dark"] .dbt-detail{ background:#131c2b; border-left-color:#6366f1; }

/* Daily Tracker — the owner's day-book. Emphasised time column (like a logbook rail),
   struck-through done rows, a left accent on hover. */
.dt-log .dt-time-h{ width:118px; }
.dt-time{ white-space:nowrap; }
.dt-time .dt-t{ font-weight:700; font-variant-numeric:tabular-nums; font-size:.95rem; }
.dt-time .dt-t-end{ display:block; font-size:.72rem; color:var(--muted); font-variant-numeric:tabular-nums; margin-top:1px; }
.dt-log tbody td:first-child{ border-left:3px solid transparent; }
.dt-log tbody tr:hover td:first-child{ border-left-color:var(--kz); }
.dt-log tr.dt-done .dt-act{ text-decoration:line-through; color:var(--muted); }
.dt-notes{ max-width:240px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* Daily Tracker — streak challenge (work-days only, 1 task done keeps the day). */
.dt-streak-head{ display:flex; align-items:baseline; gap:.5rem; flex-wrap:wrap; }
.dt-fire{ font-weight:700; font-size:1.55rem; letter-spacing:-.02em;
  background:linear-gradient(120deg,#f59e0b,#ef4444 70%); -webkit-background-clip:text; background-clip:text; color:transparent; }
.dt-fire .bi{ -webkit-text-fill-color:#f59e0b; }
.dt-streak-lbl{ color:var(--muted); font-size:.9rem; }
.dt-alive{ color:var(--warn); font-weight:600; }
.dt-next{ color:var(--muted); }
.dt-next .bi{ color:var(--kz); }
/* 7-day strip */
.dt-strip{ display:grid; grid-template-columns:repeat(7,1fr); gap:.4rem; margin:.25rem 0 .9rem; }
.dt-day{ display:flex; flex-direction:column; align-items:center; gap:2px; padding:.5rem .25rem;
  border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--mut-bg); text-align:center; }
.dt-day .dt-dow{ font-size:.66rem; font-weight:700; text-transform:uppercase; letter-spacing:.03em; color:var(--muted); }
.dt-day .dt-dot{ width:26px; height:26px; border-radius:50%; display:grid; place-items:center; font-size:.85rem;
  background:#fff; border:1px solid var(--border); color:var(--muted); }
.dt-day .dt-dnum{ font-size:.7rem; color:var(--muted); font-variant-numeric:tabular-nums; }
.dt-done.dt-day{ background:var(--ok-bg); border-color:transparent; }
.dt-done .dt-dot{ background:var(--ok); border-color:var(--ok); color:#fff; }
.dt-miss.dt-day .dt-dot{ background:var(--bad-bg); border-color:transparent; color:var(--bad); }
.dt-today.dt-day{ border-color:var(--kz); box-shadow:0 0 0 2px rgba(79,70,229,.15); }
.dt-today .dt-dot{ background:var(--kz); border-color:var(--kz); color:#fff; }
.dt-weekend.dt-day{ background:transparent; border-style:dashed; opacity:.7; }
/* milestone badges */
.dt-miles{ display:flex; flex-wrap:wrap; gap:.4rem; }
.dt-mile{ display:inline-flex; align-items:center; gap:.3rem; padding:.25rem .6rem; border-radius:999px;
  font-size:.8rem; font-weight:700; border:1px dashed var(--border); color:var(--muted); background:var(--mut-bg); }
.dt-mile.on{ border-style:solid; border-color:transparent; color:#fff;
  background:linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent)); box-shadow:var(--shadow-sm); }
html[data-theme="dark"] .dt-day .dt-dot{ background:#0F172A; }
html[data-theme="dark"] .dt-day{ background:#243246; }
html[data-theme="dark"] .dt-weekend.dt-day{ background:transparent; }
html[data-theme="dark"] .dt-done.dt-day{ background:rgba(21,122,91,.18); }
html[data-theme="dark"] .dt-miss.dt-day .dt-dot{ background:rgba(196,54,47,.2); }
html[data-theme="dark"] .dt-mile{ background:#243246; }
/* super-admin design-preview banner (sample data, no client records) */
.dt-preview-banner{ border:1px solid var(--kz); background:linear-gradient(0deg,rgba(79,70,229,.06),rgba(79,70,229,.06)); color:var(--ink); border-radius:var(--radius-sm); }
.dt-preview-banner .bi{ color:var(--kz); }
html[data-theme="dark"] .dt-preview-banner{ background:rgba(79,70,229,.14); color:var(--e2, #E2E8F0); border-color:#4F46E5; }
/* full-width "add new log" footer under the table */
.dt-addrow{ border-top:1px solid var(--border); padding:.55rem; }
.dt-addbtn{ width:100%; border:1px dashed var(--border); color:var(--kz); font-weight:600;
  background:transparent; border-radius:var(--radius-sm); padding:.5rem; }
.dt-addbtn:hover{ background:var(--mut-bg); color:var(--kz); border-color:var(--kz); }
html[data-theme="dark"] .dt-addbtn:hover{ background:#243246; }
html[data-theme="dark"] .dt-addbtn{ color:#A5B4FC; }

/* compliance / margin ring */
.fin-ring{ --pct:0; width:92px; height:92px; border-radius:50%; display:grid; place-items:center; flex:0 0 auto;
  background:conic-gradient(var(--kz) calc(var(--pct)*1%), var(--mut-bg) 0); position:relative; }
.fin-ring::before{ content:""; position:absolute; inset:10px; border-radius:50%; background:var(--card, #fff); }
html[data-theme="dark"] .fin-ring::before{ background:#1E293B; }
.fin-ring span{ position:relative; font-weight:700; }

/* category card with a left metal rail */
.fin-cat{ position:relative; overflow:hidden; }
.fin-cat::before{ content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--rail, var(--kz)); }

/* P&L / statement table */
.fin-stmt{ width:100%; border-collapse:collapse; font-variant-numeric:tabular-nums; min-width:760px; }
.fin-stmt th, .fin-stmt td{ padding:9px 12px; text-align:left; font-size:.86rem; border-bottom:1px solid var(--border); white-space:nowrap; }
.fin-stmt th:first-child, .fin-stmt td:first-child{ text-align:left; }
.fin-stmt thead th{ font-size:.72rem; text-transform:uppercase; letter-spacing:.05em; color:var(--muted);
  font-weight:700; position:sticky; top:0; background:var(--card); border-bottom:2px solid var(--border); }
.fin-stmt .grp td{ font-weight:700; background:var(--mut-bg); }
.fin-stmt .sub td:first-child{ padding-left:28px; color:var(--muted); }
.fin-stmt .tot td{ font-weight:700; border-top:2px solid var(--border); border-bottom:2.5px double var(--border); }
.fin-stmt .pos{ color:var(--ok); } .fin-stmt .neg{ color:var(--bad); }

/* tabular numeric cell helper */
.tnum{ font-variant-numeric:tabular-nums; text-align:right; }

/* ---- Trial Balance: wide financial grid — sticky header + sticky totals ---- */
.tb-card{ overflow:hidden; }
.tb-scroll{ overflow-x:auto; }
.tb-tbl{ table-layout:fixed; width:max-content; min-width:100%; border-collapse:separate; border-spacing:0; font-variant-numeric:tabular-nums; }
.tb-tbl th, .tb-tbl td{ padding:10px 12px; border-bottom:1px solid #eceff6; vertical-align:middle; overflow:hidden; }
.tb-tbl thead th{ position:sticky; top:0; z-index:3; background:#f7f9fc; color:var(--fg);
  font-size:.74rem; text-transform:uppercase; letter-spacing:.05em; font-weight:700; border-bottom:2px solid #e3e8f2; white-space:nowrap; }
.tb-tbl tbody tr:hover td{ background:#f6f8fd; }
.tb-tbl .tb-act{ width:48px; text-align:center; }
.tb-tbl .tb-no{ width:96px; text-align:center; font-weight:700; }
.tb-tbl .tb-name{ width:244px; font-weight:500; white-space:normal; word-break:break-word; }
.tb-tbl .tb-m{ width:132px; text-align:left; }
/* 🔴 A MONEY FIGURE MUST NEVER BE CUT OFF — and an `<input>` clips, it cannot wrap
   (2026-08-28). 132px fits a whole-rupee grid, but the TRIAL BALANCE is the one grid that
   shows PAISE (`data-live-total="2"`, `|inrx`), so its strings are three characters longer:
   production's largest ledger balance is ₹66,12,25,693.06 — 15 characters needing 128px
   inside a 118px box, so 12 of 120 cells were silently truncating a crore figure on a
   financial statement. Nothing overflowed and nothing errored; the number just read wrong.
   Keyed off `data-live-total="2"` because that attribute IS "this grid shows paise" — the
   width and the reason then cannot drift apart. Sized from a measurement, not a guess:
   ₹1,00,00,00,000.00 with a minus sign is the widest realistic figure. */
.tb-tbl[data-live-total="2"] .tb-m{ width:184px; }
.tb-tbl .tb-del{ width:44px; text-align:center; }
.tb-tbl tfoot td{ background:#f1f4fb; font-weight:700; color:var(--ink);
  border-top:2px solid #e3e8f2; border-bottom:0; white-space:nowrap; }
/* 🔴 The INPUT carries the alignment in this grid, not the cell — flipping `.tb-m`
   alone left the heading on the left and the figure centred, which is heading and
   figures on different edges (owner-reported, August). */
.tb-tbl .tb-m .form-control{ width:100%; text-align:left; border-color:#e7ecf6; padding:.28rem .3rem; font-size:.82rem; }
.tb-tbl .tb-m .form-control:focus{ border-color:var(--kz); }
.tb-editbtn{ border:0; background:transparent; color:var(--muted); width:30px; height:30px; border-radius:8px;
  cursor:pointer; display:inline-grid; place-items:center; transition:background .15s, color .15s; }
.tb-editbtn:hover{ color:#fff; background:linear-gradient(120deg,var(--kz),var(--kz2)); }
/* holds a pencil's place in a row that has none, so names stay on one left edge */
.tb-editbtn-gap{ display:block; width:30px; height:30px; flex:0 0 30px; }
/* dark */
html[data-theme="dark"] .tb-tbl th, html[data-theme="dark"] .tb-tbl td{ border-bottom-color:#2a3852; }
html[data-theme="dark"] .tb-tbl thead th{ background:#1a2436; border-color:#2a3852; }
html[data-theme="dark"] .tb-tbl tbody tr:hover td{ background:#213046; }
html[data-theme="dark"] .tb-tbl tfoot td{ background:#0f1826; color:#E2E8F0; border-color:#2a3852; }
html[data-theme="dark"] .tb-tbl .tb-m .form-control{ border-color:#2f3d55; }

/* Inventory grid — reuses .tb-tbl fixed layout; category spans 3 UOM rows */
.inv-cat{ font-weight:600; font-size:.85rem; color:color-mix(in srgb, var(--fg) 82%, var(--fg-muted)); vertical-align:middle; background:#fbfcff; border-right:1px solid #eceff6; }
.inv-uom{ color:var(--muted); font-size:.8rem; white-space:nowrap; }
.tb-tbl tbody tr.inv-top td{ border-top:2px solid #e6eaf3; }
.tb-tbl tbody tr.inv-avg td{ color:var(--muted); font-size:.8rem; background:#fcfcfe; }
html[data-theme="dark"] .inv-cat{ background:#131c2b; border-right-color:#2a3852; }
html[data-theme="dark"] .tb-tbl tbody tr.inv-avg td{ background:#0f1826; }
html[data-theme="dark"] .tb-tbl tbody tr.inv-top td{ border-top-color:#2a3852; }
/* Working Capital flag row */
.wc-flag,.wc-flag td{ background:var(--bad-bg) !important; }  /* td-level so it wins over fin-table zebra */
html[data-theme="dark"] .wc-flag,html[data-theme="dark"] .wc-flag td{ background:#2c1512 !important; }

/* Inventory: freeze the Inventory-name + Unit-of-measurement columns on x-scroll.
   ⚠ These rules set NO background on purpose — the opacity a frozen column must have
   (kizuna-ui §9t) comes from `.inv-cat` above, `.tb-tbl thead th` / `tfoot td`, and
   `.inv-tbl tbody .inv-uom` below, all of which are opaque and dark-paired. Verified
   rendered, not read: alpha 1 in both themes at 1280px and 820px, on tables that
   really do overflow (607–1137px). Do NOT add a zebra to `.tb-tbl` without giving
   every frozen cell here a background that out-specifies it — that is precisely how
   `.ac-c1` ended up see-through. `testing/frozen_columns.py` guards this. */
.inv-tbl .inv-c1, .inv-tbl .inv-cat{ position:sticky; left:0; z-index:2; }
.inv-tbl .inv-c2, .inv-tbl .inv-uom{ position:sticky; left:162px; z-index:2; box-shadow:2px 0 0 -1px #e3e8f2; }
.inv-tbl tbody .inv-uom{ background:#fff; }
.inv-tbl tbody tr.inv-avg .inv-uom{ background:#fcfcfe; }
.inv-tbl thead .inv-c1, .inv-tbl thead .inv-c2{ z-index:4; }
.inv-tbl tfoot .inv-c1, .inv-tbl tfoot .inv-c2{ z-index:3; }
html[data-theme="dark"] .inv-tbl tbody .inv-uom{ background:#141c2b; }
html[data-theme="dark"] .inv-tbl tbody tr.inv-avg .inv-uom{ background:#0f1826; }

/* Expenses grid: freeze the Expense-head column on x-scroll. Own left/width offsets
   matched to its own <col> — deliberately NOT .inv-c1/.inv-c2, whose left:162px
   belongs to the Inventory/COGS layout and would park a frozen column over the Apr
   cell here. */
.exp-tbl .exp-c1, .exp-tbl .exp-head{ position:sticky; left:0; z-index:2; }
/* Expense-type column freezes beside the head. left = the head column's own width
   (230px) — NOT .inv-c2's 162px, which belongs to the Inventory/COGS layout. */
.exp-tbl .exp-c2, .exp-tbl .exp-type{ position:sticky; left:230px; z-index:2; box-shadow:2px 0 0 -1px #e3e8f2; }
.exp-tbl thead .exp-c1, .exp-tbl thead .exp-c2{ z-index:4; }
.exp-tbl tfoot .exp-c1, .exp-tbl tfoot .exp-c2{ z-index:3; }
.exp-tbl tbody .exp-type{ background:#fff; }
.exp-head{ font-weight:600; font-size:.85rem; color:color-mix(in srgb, var(--fg) 82%, var(--fg-muted)); vertical-align:middle; background:#fbfcff; border-right:1px solid #eceff6; }
.exp-type{ white-space:nowrap; }
/* read-only Expense-type tag — the type is changed only via the Add/Edit modal */
.exp-type-tag{ display:inline-flex; align-items:center; font-size:.8rem; color:var(--ink); }
html[data-theme="dark"] .exp-type-tag{ color:#E2E8F0; }
/* The project (site) an expense head is incurred for — construction (2026-10-02). */
.exp-site{ font-size:.72rem; font-weight:600; color:var(--kz); line-height:1.3; }
.exp-site.is-none{ color:var(--fg-muted); font-weight:500; }
.exp-sitebar{ display:flex; align-items:center; gap:9px; flex-wrap:wrap; padding:10px 13px; margin-bottom:12px;
  border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--surface-sunken,#fbfcfe); font-size:.84rem; }
.exp-sitebar > .bi{ color:var(--kz); }
.exp-sitebar select{ width:auto; min-width:200px; }
html[data-theme="dark"] .exp-sitebar{ background:#1E293B; border-color:#334155; }
.pc-proj{ font-size:.72rem; }
@media (max-width:575.98px){ .exp-sitebar select{ width:100%; } }
/* ---- Vendors (construction, 2026-10-02) -------------------------------------------------
   .vflow        the four steps a vendor's work passes through, on the profile
   .vnd-stepchip the same four, as chips on the vendor list
   .vnd-step     a numbered section of the Create-a-vendor form
   Every colour is a token, so the dark theme needs no rule of its own. */
/* auto-fit, not a fixed four: the same strip sits in a 420px drawer (two a row) and on the full page (four) */
.vflow{ list-style:none; margin:0 0 16px; padding:0; display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:8px; }
.vflow__s{ display:grid; grid-template-columns:auto 1fr; column-gap:8px; row-gap:1px; align-items:center;
  border:1px solid var(--border); border-radius:var(--radius-sm); padding:9px 10px; min-width:0; }
.vflow__n{ grid-row:1 / span 2; width:22px; height:22px; border-radius:50%; display:grid; place-items:center;
  font-size:.72rem; font-weight:700; background:var(--mut-bg); color:var(--fg-muted); }
.vflow__l{ font-size:.7rem; text-transform:uppercase; letter-spacing:.05em; font-weight:700; color:var(--fg-muted); }
.vflow__t{ grid-column:2; font-size:.8rem; line-height:1.3; overflow-wrap:anywhere; }
.vflow__s.is-done .vflow__n{ background:var(--ok-bg); color:var(--ok); }
.vflow__s:not(.is-done) .vflow__t{ color:var(--fg-muted); }
.vnd-steps{ display:flex; flex-wrap:wrap; gap:4px; }
.vnd-stepchip{ display:inline-flex; align-items:center; gap:3px; padding:2px 7px; border-radius:999px; font-size:.7rem;
  line-height:1.5; white-space:nowrap; background:var(--mut-bg); color:var(--fg-muted); }
.vnd-stepchip.is-done{ background:var(--ok-bg); color:var(--ok); font-weight:600; }
/* a step not yet done: the muted ink on the muted chip measured 4.0:1 in dark — lifted to AA */
html[data-theme="dark"] .vnd-stepchip:not(.is-done){ color:#CBD5E1; }
.vnd-step{ border:0; border-top:1px solid var(--border); margin:0; padding:14px 0 4px; min-width:0; }
.vnd-step:first-of-type{ border-top:0; padding-top:0; }
/* floated full-width so the legend sits INSIDE the section, under its rule — an un-floated
   legend is drawn on the border and the rule runs out of the end of the heading */
.vnd-step > legend{ float:left; width:100%; margin:0 0 9px; padding:0; font-size:.86rem; font-weight:700;
  display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.vnd-step > legend + *{ clear:both; }
.vnd-step__n{ width:22px; height:22px; border-radius:50%; display:inline-grid; place-items:center; flex:0 0 auto;
  font-size:.72rem; font-weight:700; background:var(--kz); color:#fff; }
.vq-add > summary{ cursor:pointer; font-size:.82rem; font-weight:600; color:var(--kz); width:max-content; max-width:100%; }
/* the brand indigo on the dark card is 2.3:1 — the link ink the app already uses there */
html[data-theme="dark"] .vq-add > summary{ color:#A5B4FC; }
/* ---- Budget by stage on Project & Channel (2026-10-02) ---- */
.stg-figs{ display:flex; gap:18px; flex-wrap:wrap; }
.stg-figs > div{ display:flex; flex-direction:column; min-width:0; }
.stg-figs .k{ font-size:.68rem; text-transform:uppercase; letter-spacing:.05em; font-weight:700; color:var(--fg-muted); }
.stg-figs .v{ font-size:1.02rem; font-weight:700; font-variant-numeric:tabular-nums; }
.stg-figs .is-over .v{ color:var(--bad); }
.stg-add{ display:flex; gap:10px; flex-wrap:wrap; align-items:flex-end; justify-content:space-between; }
.stg-add__form{ display:flex; gap:8px; flex-wrap:wrap; align-items:flex-end; flex:1 1 320px; }
.stg-add__form > div:first-of-type{ flex:1 1 200px; min-width:0; }
.stg-add__form > div:nth-of-type(2){ flex:0 1 170px; min-width:0; }
html[data-theme="dark"] .exp-head{ background:#131c2b; border-right-color:#2a3852; }
html[data-theme="dark"] .exp-tbl tbody .exp-type{ background:#141c2b; }
html[data-theme="dark"] .exp-tbl .exp-c2, html[data-theme="dark"] .exp-tbl .exp-type{ box-shadow:2px 0 0 -1px #2a3852; }

/* ── BEST · Account Checklist (2026-08-27) ──────────────────────────────────
   A wide sheet: four text columns then a box for every day of the month. The
   day cells are deliberately tiny — the client reads down the Particulars
   column and ticks across, so the boxes are targets, not content. */
.ac-months{ display:flex; gap:6px; flex-wrap:wrap; margin:0 0 14px; }
.ac-m{ position:relative; display:inline-flex; align-items:center; gap:5px; padding:6px 13px;
  border:1px solid var(--border); border-radius:20px; background:var(--surface-card,#fff);
  color:var(--fg-muted); font-size:.82rem; font-weight:600; text-decoration:none;
  transition:background var(--dur-fast,.14s), color var(--dur-fast,.14s); }
.ac-m:hover{ background:var(--mut-bg); color:var(--fg); }
.ac-m.is-on{ background:var(--kz); border-color:var(--kz); color:#fff; }
.ac-m.is-now:not(.is-on){ border-color:var(--kz); color:var(--kz); }
.ac-now-dot{ width:6px; height:6px; border-radius:50%; background:currentColor; }

.ac-tbl th, .ac-tbl td{ vertical-align:middle; }

/* ==== The checklist's heading row survives the scroll (2026-09-23) ===================
   \U0001F534 TWO HEADER ROWS, TWO OFFSETS. `.kz-stickytable` pins `thead th` at `top:0`,
   which is right for a one-row header and wrong here: the week bands and the day numbers
   would both pin at 0 and the days would draw over the week they belong to. Row one is given
   an EXPLICIT height and row two is offset by exactly that.
   \u26a0 A hard-coded offset is the same shape as this page's hard-coded `left` values, which
   have slid columns on top of each other twice. Change the height and change the offset in
   the SAME edit, and re-run testing/acct_checklist_browser.py, which measures the gap.
   \u26a0 The stacking order matters as much as the position: the four frozen heading cells sit
   above everything (4), the day headings above the rows (3), the frozen body cells above
   their neighbours (2). Get this wrong and the ticks scroll over their own column names. */
.ac-scroll{ --stick-h:70vh; }
/* 🔴 THE OFFSET IS MEASURED, NOT TYPED. My first cut hard-coded `top:32px` for the
   day row from a reading of the first row's height — and the week band is TALLER than that,
   because the current week carries a "now" badge. `height` on a `th` is a minimum, so the
   band grew and the day numbers pinned 6px INSIDE it. Hard-coded offsets have slid this
   page's columns on top of each other three times; the page now measures the band and
   publishes it as `--ac-h1`, so the two rows cannot drift apart again. */
.ac-tbl thead tr:nth-child(2) > th{ position:sticky; top:var(--ac-h1, 34px); z-index:3;
  background:var(--surface-sunken); }
.ac-tbl thead tr:first-child > th{ z-index:3; }
/* the four frozen heading cells are corner cells — they pin in BOTH directions and must
   stay above the day headings that slide under them */
/* ⚠ SPECIFICITY, not intent. `.ac-tbl thead .ac-c1` is (0,2,1) and loses to
   `.kz-stickytable .fin-table thead th` (0,2,2), so a plain `z-index:5` here measured as 3
   and the day headings sat level with the corner cells they must slide under. Naming the
   wrapper takes it to (0,3,2). */
.kz-stickytable .ac-tbl thead th.ac-c1, .kz-stickytable .ac-tbl thead th.ac-c2,
.kz-stickytable .ac-tbl thead th.ac-c3, .kz-stickytable .ac-tbl thead th.ac-c4{
  top:0; z-index:5; }

/* 🔴 A STICKY HEADING MUST BE OPAQUE, AND THE "now"/"today" TINTS ARE NOT.
   `.ac-wk.is-now`, `.ac-d.is-now` and `.ac-d.is-today` are rgba over TRANSPARENT — correct
   over body rows, where they tint the stripe, and wrong the moment the heading became sticky:
   the tick boxes scrolled straight THROUGH the highlighted week band and today's column, so
   the header showed rows of checkboxes over its own labels (owner screenshot).
   Same fix this page already used for the frozen column's stripe: state the tint OPAQUELY,
   composited over the heading's own surface, instead of over nothing.
   ⚠ `!important` is needed because `.ac-d.is-today` carries it; matching it is the only
   way to win, and dropping it there would change the body cells too. */
.kz-stickytable .ac-tbl thead th.ac-wk.is-now{
  background:color-mix(in srgb, var(--kz) 14%, var(--surface-sunken)) !important; }
.kz-stickytable .ac-tbl thead th.ac-d.is-now{
  background:color-mix(in srgb, var(--kz) 6%, var(--surface-sunken)) !important; }
.kz-stickytable .ac-tbl thead th.ac-d.is-today{
  background:color-mix(in srgb, var(--kz) 20%, var(--surface-sunken)) !important; }
html[data-theme="dark"] .kz-stickytable .ac-tbl thead th.ac-wk.is-now{
  background:color-mix(in srgb, #A5B4FC 18%, var(--surface-sunken)) !important; }
html[data-theme="dark"] .kz-stickytable .ac-tbl thead th.ac-d.is-now{
  background:color-mix(in srgb, #A5B4FC 8%, var(--surface-sunken)) !important; }
html[data-theme="dark"] .kz-stickytable .ac-tbl thead th.ac-d.is-today{
  background:color-mix(in srgb, #A5B4FC 26%, var(--surface-sunken)) !important; }
html[data-theme="dark"] .ac-tbl thead tr:nth-child(2) > th{ background:var(--surface-sunken); }

/* 🔴 FIXED LAYOUT IS WHAT MAKES THE FROZEN OFFSETS SAFE (2026-08-28).
   All four text columns are frozen now (owner ask: sticky "till Particulars"), and a
   frozen column's `left:` is a HARD-CODED sum of the widths before it — the trap that
   has bitten this project three times (kizuna-finance §10). With `table-layout:auto`
   those widths follow the content, so one long task name would slide Particulars on
   top of Last Date. `table-layout:fixed` + a <colgroup> makes them content-proof, and
   `width:max-content` stops the columns stretching when the table overflows — which is
   the only time a sticky offset is used at all. (When the table is NARROWER than its
   container `min-width:100%` lets it stretch, and that is safe precisely because there
   is then no horizontal scroll for anything to stick against.) Same recipe as `.tb-tbl`. */
.ac-tbl{ table-layout:fixed; width:max-content; min-width:100%; }
.ac-tbl col.ac-w1{ width:150px; } .ac-tbl col.ac-w2{ width:112px; }
.ac-tbl col.ac-w3{ width:96px; }  .ac-tbl col.ac-w4{ width:260px; }
.ac-tbl col.ac-wd{ width:30px; }  .ac-tbl col.ac-wv{ width:74px; }
.ac-tbl col.ac-wx{ width:40px; }
/* 🔴 A DECLARED WIDTH IS STILL A FLOOR, NOT A WIDTH, AND THAT MOVED A COLUMN.
   `width:max-content` keeps the auto minimum, so a column can never be narrower than
   its own MIN-CONTENT — and the single unbreakable word "FREQUENCY" in the micro-caps
   header measured 106.3px against the 104px declared. That column silently grew 2.3px
   and Last Date, pinned at a hard-coded 254px, parked 2px ON TOP of it. Two fixes, and
   both are wanted: the column is wide enough for its own header, AND `overflow-wrap:
   anywhere` drops the floor so a future font change (§9c1 — widths are font-specific)
   makes a header WRAP rather than shift every offset after it. Wrap is a cosmetic
   failure; shift is the column-on-column defect this page already shipped once. */
.ac-tbl .ac-c1, .ac-tbl .ac-c2, .ac-tbl .ac-c3, .ac-tbl .ac-c4{ overflow-wrap:anywhere; }
/* the four columns you read across FROM — frozen, in the sheet's own order.
   Each `left` is the sum of the widths above it: 0 · 150 · 262 · 358. Change a width
   in the colgroup and change its offsets here in the same edit — and re-run
   testing/acct_checklist_browser.py, which measures that they do not overlap. */
.ac-tbl .ac-c1, .ac-tbl .ac-c2, .ac-tbl .ac-c3, .ac-tbl .ac-c4{
  position:sticky; z-index:2; background:var(--surface-card,#fff); }
.ac-tbl .ac-c1{ left:0; }
.ac-tbl .ac-c2{ left:150px; }
.ac-tbl .ac-c3{ left:262px; }
/* the shadow marks where the frozen BLOCK ends — one edge, on the last column, not four */
.ac-tbl .ac-c4{ left:358px; box-shadow:2px 0 0 -1px var(--border); }
/* long names and task titles WRAP inside their fixed column instead of widening it */
.ac-tbl .ac-c1, .ac-tbl .ac-c4{ white-space:normal; word-break:break-word; }
.ac-tbl thead .ac-c1, .ac-tbl thead .ac-c2, .ac-tbl thead .ac-c3, .ac-tbl thead .ac-c4{
  z-index:4; background:var(--surface-sunken,#f7f9fc); }
/* 🔴 A FROZEN COLUMN MUST BE OPAQUE — AND THE ROW STRIPE OUT-SPECIFIES IT.
   `.fin-table tbody tr:nth-child(even) td` is (0,2,2) and beats `.ac-tbl .ac-c1` (0,2,0),
   so every other frozen cell inherited a 3%-ALPHA wash. Sticky and on top counts for
   nothing when you can see through it: scrolled sideways, the Particulars text ran
   straight under the name — "Charlie" printed over "Income Tax Return".
   Re-state the stripe as an OPAQUE composite (the same 3% tint, mixed over the card
   instead of over transparency) rather than dropping the stripe on this one column,
   which would make the frozen cells read as a different table. */
.ac-tbl tbody tr td.ac-c1, .ac-tbl tbody tr td.ac-c2,
.ac-tbl tbody tr td.ac-c3, .ac-tbl tbody tr td.ac-c4{ background:var(--surface-card,#fff); }
.ac-tbl tbody tr:nth-child(even) td.ac-c1, .ac-tbl tbody tr:nth-child(even) td.ac-c2,
.ac-tbl tbody tr:nth-child(even) td.ac-c3, .ac-tbl tbody tr:nth-child(even) td.ac-c4{
  background:color-mix(in srgb, var(--fg) 3%, var(--surface-card,#fff)); }
.ac-tbl .ac-d{ width:30px; min-width:30px; padding:4px 0; text-align:center;
  font-size:.68rem; font-variant-numeric:tabular-nums; }
.ac-tbl thead .ac-d{ color:var(--fg-muted); font-weight:600; }
.ac-tbl .ac-wk{ text-align:center; font-size:.66rem; letter-spacing:.06em;
  border-left:1px solid var(--border); }
/* 🔴 THE CURRENT WEEK, highlighted (owner ask). A whole tinted band rather than a
   border, so it is findable at a glance in 31 columns of identical boxes. */
.ac-tbl .ac-wk.is-now{ background:rgba(79,70,229,.14); color:var(--kz); }
.ac-tbl td.ac-d.is-now, .ac-tbl thead .ac-d.is-now{ background:rgba(79,70,229,.06); }
.ac-tbl .ac-d.is-today{ background:rgba(79,70,229,.20) !important; }
.ac-tbl thead .ac-d.is-today{ color:var(--kz); font-weight:800; }
.ac-nowtag{ font-family:var(--font-mono); font-size:.56rem; letter-spacing:.1em;
  text-transform:uppercase; background:var(--kz); color:#fff; border-radius:4px; padding:1px 5px; }
.ac-tick{ width:15px; height:15px; cursor:pointer; }
.ac-d.is-saving{ opacity:.45; }
.ac-d.is-failed{ background:var(--bad-bg) !important; outline:1.5px solid var(--bad); }

/* the person the tasks belong to — a band, so the grouping is unmistakable */
/* ⚠ The band spans every column, so with nothing sticky in it the person's name simply
   scrolled out of the viewport — the grouping label vanished exactly when the 31 day
   columns made it most useful.
   🔴 Sticky on the CELL does nothing here: a colspan cell already fills its containing
   block, so there is nowhere for it to slide to (measured left:-15px while pinned was the
   entire point). The WRAPPER inside it does have room, so that is what sticks. */
.ac-tbl tr.ac-grp td{ background:var(--surface-sunken,#f1f4fb) !important; padding:8px 14px; }
.ac-grpin{ position:sticky; left:14px; display:inline-flex; align-items:center; }
.ac-who{ display:inline-flex; align-items:center; gap:7px; font-weight:700;
  color:var(--fg); margin-right:9px; }
.ac-who .ki{ color:var(--kz); }

html[data-theme="dark"] .ac-m{ background:#1E293B; border-color:#334155; }
html[data-theme="dark"] .ac-m.is-on{ background:var(--kz); border-color:var(--kz); color:#fff; }
html[data-theme="dark"] .ac-m.is-now:not(.is-on){ border-color:#A5B4FC; color:#A5B4FC; }
html[data-theme="dark"] .ac-tbl .ac-c1, html[data-theme="dark"] .ac-tbl .ac-c2,
html[data-theme="dark"] .ac-tbl .ac-c3, html[data-theme="dark"] .ac-tbl .ac-c4{ background:#1E293B; }
html[data-theme="dark"] .ac-tbl tbody tr td.ac-c1, html[data-theme="dark"] .ac-tbl tbody tr td.ac-c2,
html[data-theme="dark"] .ac-tbl tbody tr td.ac-c3, html[data-theme="dark"] .ac-tbl tbody tr td.ac-c4{
  background:#1E293B; }
html[data-theme="dark"] .ac-tbl tbody tr:nth-child(even) td.ac-c1,
html[data-theme="dark"] .ac-tbl tbody tr:nth-child(even) td.ac-c2,
html[data-theme="dark"] .ac-tbl tbody tr:nth-child(even) td.ac-c3,
html[data-theme="dark"] .ac-tbl tbody tr:nth-child(even) td.ac-c4{
  background:color-mix(in srgb, var(--fg) 3%, #1E293B); }
html[data-theme="dark"] .ac-tbl thead .ac-c1, html[data-theme="dark"] .ac-tbl thead .ac-c2,
html[data-theme="dark"] .ac-tbl thead .ac-c3, html[data-theme="dark"] .ac-tbl thead .ac-c4{
  background:#243246; }
html[data-theme="dark"] .ac-tbl .ac-wk.is-now{ background:rgba(165,180,252,.18); color:#C7D2FE; }
html[data-theme="dark"] .ac-tbl td.ac-d.is-now, html[data-theme="dark"] .ac-tbl thead .ac-d.is-now{ background:rgba(165,180,252,.08); }
html[data-theme="dark"] .ac-tbl .ac-d.is-today{ background:rgba(165,180,252,.26) !important; }
html[data-theme="dark"] .ac-tbl thead .ac-d.is-today{ color:#C7D2FE; }
html[data-theme="dark"] .ac-nowtag{ background:#A5B4FC; color:#0F172A; }
html[data-theme="dark"] .ac-tbl tr.ac-grp td{ background:#243246 !important; }
/* 🔴 BELOW `lg` ONLY THE FIRST COLUMN STAYS FROZEN. The four together are 610px wide —
   on a 390px phone that block covers the entire viewport, so the day boxes could never
   be scrolled to at all. Freezing more than the screen can spare does not pin a column,
   it removes the table. Accountability alone (150px) is what this page has always done
   on a phone, and it still reads across from the name. */
@media (max-width:991.98px){
  .ac-tbl .ac-c2, .ac-tbl .ac-c3, .ac-tbl .ac-c4{ position:static; box-shadow:none; }
  .ac-tbl .ac-c1{ box-shadow:2px 0 0 -1px var(--border); }
}
/* on paper the sticky columns must un-stick, or they print over the day boxes — and the
   fixed max-content layout has to be released with them or the sheet runs off the page */
@media print{
  .ac-tbl{ table-layout:auto !important; width:100% !important; min-width:0 !important; }
  .ac-tbl .ac-c1, .ac-tbl .ac-c2, .ac-tbl .ac-c3, .ac-tbl .ac-c4{
    position:static; box-shadow:none; }
}

/* The way into Cost & Revenue Estimation, in the Project & Channel BODY (2026-08-27).
   Not a nav section — see FIN_SECTIONS_CONSTRUCTION §A. */
.pc-cre{ display:flex; align-items:center; justify-content:space-between; gap:16px;
  flex-wrap:wrap; padding:13px 15px; margin-bottom:16px; border-radius:var(--radius-sm);
  border:1px solid var(--border); background:var(--surface-sunken,#fbfcfe); }
.pc-cre-txt{ min-width:0; }
.pc-cre-txt b{ display:block; font-size:.95rem; }
.pc-cre-txt span{ display:block; font-size:.83rem; line-height:1.45; max-width:70ch; }
html[data-theme="dark"] .pc-cre{ background:#1E293B; border-color:#334155; }

/* Project Cost & Revenue Estimation (construction, 2026-08-27). The two category
   colours are the handoff's own, kept because they are validated colour-blind-safe:
   #c98500 Land · #3987e5 Construction. Re-validate before changing either.
   The third group, Operating Expenses (2026-08-28), takes the muted slate #64748b
   ON PURPOSE — it is not part of the statutory project cost, and a third saturated
   hue beside those two would read as a third kind of project cost. */
.ce-scope{ display:flex; align-items:flex-start; gap:9px; padding:11px 13px; margin-bottom:16px;
  border-radius:var(--radius-sm); background:var(--info-bg); color:var(--info);
  font-size:.85rem; line-height:1.5; border:1px solid rgba(21,101,166,.18); }
.ce-scope > .bi{ flex:0 0 auto; line-height:1.5; }
.ce-dot{ display:inline-block; width:9px; height:9px; border-radius:3px; margin-right:6px;
  vertical-align:1px; }
.ce-grp td{ background:var(--mut-bg); font-weight:600; }
.ce-head{ font-weight:600; color:var(--fg,#0F172A); }
/* Collapsible cost heads (2026-08-28). One <tbody> per group; `.ce-shut` hides its head
   rows and the group row stays with its subtotal — the sheet folds down to a three-line
   summary of where the money goes. The rows are hidden, never removed or disabled, so
   every figure still posts on Save. */
.ce-g.ce-shut tr.ce-h{ display:none; }
/* 🔴 A COLLAPSED GROUP MUST STILL PRINT. Folding a section is a reading convenience on
   screen; a printed estimate that quietly leaves out six cost heads is a document that
   understates the project — and this page has a Print button. Paper shows every head,
   whatever the screen is showing, and the fold's own furniture drops out. */
@media print{
  .ce-g.ce-shut tr.ce-h{ display:table-row; }
  .ce-caret, .ce-gn{ display:none; }
}
.ce-toggle{ display:inline-flex; align-items:center; gap:2px; padding:0; border:0;
  background:none; color:inherit; font:inherit; font-weight:600; text-align:left;
  cursor:pointer; }
.ce-toggle:hover{ color:var(--kz); }
.ce-caret{ font-size:.72rem; color:var(--muted); margin-right:5px;
  transition:transform var(--dur-fast,.14s) var(--ease, ease); }
.ce-toggle[aria-expanded="false"] .ce-caret{ transform:rotate(-90deg); }
@media (prefers-reduced-motion: reduce){ .ce-caret{ transition:none; } }
.ce-gn{ margin-left:8px; font-weight:400; font-size:.76rem; color:var(--muted); }
/* A group row is a BAND — it stays one line. The tag says the one thing a client must know
   before typing into this group; the rest is in the card's `.kz-note` disclosure (§9b1),
   because a three-line note here made that band 85px against the others' 38px. */
/* The RERA subtotal that closes the statutory heads, above Operating Expenses. It reuses
   `.fin-table tr.tot` (the emphasised-total row role) and only adds the rule that marks it
   as the END of a section rather than the end of the table. */
.ce-sub td{ border-top:2px solid var(--border); }
.ce-gtag{ margin-left:9px; font-weight:500; font-size:.7rem; color:var(--muted);
  background:var(--mut-bg); border-radius:20px; padding:2px 9px; white-space:nowrap; }
.ce-helper{ display:flex; align-items:center; gap:7px; flex-wrap:wrap; }
.ce-auto td{ background:rgba(79,70,229,.045); }
/* ⚠ Both of these sit on the group BAND, not on the page — a raised surface, so the muted
   ink that is fine elsewhere lands at 3.45:1 and 4.33:1 in dark (AA wants 4.5). A semantic
   ink is judged against the surface it is PAIRED with, never against the page (§9r). Found
   by testing/dark_mode.py after they shipped with the collapsible cost heads on 2026-08-28;
   the light theme was always fine. */
html[data-theme="dark"] .ce-gn,
html[data-theme="dark"] .ce-gtag{ color:color-mix(in srgb, var(--fg) 70%, var(--fg-muted)); }

.ce-tag{ margin-left:7px; font-family:var(--font-mono); font-size:.6rem; letter-spacing:.09em;
  text-transform:uppercase; color:var(--kz); background:rgba(79,70,229,.10);
  border-radius:5px; padding:2px 6px; vertical-align:1px; }
/* ⚠ `repeat(3, 1fr)` is NOT safe here. A bare `1fr` will not shrink below its content's
   min-content width, and these cells hold long monospaced ₹ figures — at 390px the three
   columns held a 435px floor and pushed the whole PAGE sideways. `minmax(0,1fr)` lets them
   shrink, and below 560px they stack instead of squeezing. */
.ce-revsum{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px;
  padding-top:12px; border-top:1px solid var(--border); }
.ce-revsum > div{ min-width:0; }
@media (max-width:560px){ .ce-revsum{ grid-template-columns:1fr; gap:7px; } }
.ce-revsum .k{ display:block; font-size:.76rem; color:var(--muted); }
.ce-revsum .v{ display:block; font-weight:700; font-family:var(--font-mono);
  font-variant-numeric:tabular-nums; }
/* cost-structure bars — hand-built, no chart library (as the handoff intends) */
.ce-bars{ display:flex; flex-direction:column; gap:7px; }
.ce-row{ display:grid; grid-template-columns:130px 1fr 128px; align-items:center; gap:10px; }
.ce-name{ font-size:.8rem; color:var(--muted); overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; }
.ce-track{ height:11px; border-radius:6px; background:var(--mut-bg); overflow:hidden; }
.ce-bar{ height:100%; border-radius:6px; }
.ce-val{ text-align:right; font-size:.82rem; font-weight:600; white-space:nowrap; }
.ce-pct{ color:var(--muted); font-weight:400; margin-left:6px; }
.ce-splitbar{ display:flex; height:13px; border-radius:7px; overflow:hidden; background:var(--mut-bg); }
/* The key WRAPS rather than spacing-between: with three groups a `justify-content:between`
   row strands the middle label, and a fourth would not fit at all. */
.ce-splitkey{ display:flex; flex-wrap:wrap; gap:4px 16px; }
/* ⚠ A LINK INSIDE A TINTED BLOCK IS JUDGED AGAINST THE TINT, NOT THE PAGE (kizuna-ui §9r).
   Bootstrap's dark link blue reads 4.48:1 on this amber wash — under AA by a hair, and only
   in dark mode. Brightened here rather than page-side, so every `.ce-warn` that ever carries
   a link is covered instead of the one that happened to be measured. */
html[data-theme="dark"] .ce-warn a{ color:#93C5FD; }
.ce-warn{ padding:9px 12px; border-radius:9px; background:var(--warn-bg); color:var(--warn);
  font-size:.83rem; border-left:3px solid var(--warn); }
html[data-theme="dark"] .ce-scope{ border-color:rgba(255,255,255,.10); }
html[data-theme="dark"] .ce-auto td{ background:rgba(165,180,252,.07); }
html[data-theme="dark"] .ce-tag{ background:rgba(165,180,252,.14); color:#A5B4FC; }
@media print{ .ce-scope{ border:1px solid #ccc; } }

/* Plan The Year — the client's own firm controls (2026-08-27). A firm added here is a
   PLANNING HEADING, not a switchable firm, and the notice says so rather than leaving the
   client hunting for a switcher entry that will never appear. */
.py-firmbar{ margin:0 0 16px; }
.py-note{ display:flex; align-items:flex-start; gap:9px; padding:11px 13px; margin-bottom:10px;
  border-radius:var(--radius-sm); background:var(--warn-bg); color:var(--warn); font-size:.86rem;
  line-height:1.5; border:1px solid rgba(154,106,22,.20); }
.py-note b{ color:var(--warn); }
.py-note > .bi{ font-size:.95rem; line-height:1.5; flex:0 0 auto; }
.py-chip{ display:inline-block; background:rgba(255,255,255,.72); border:1px solid rgba(154,106,22,.28);
  border-radius:20px; padding:1px 9px; margin:0 4px 2px 0; font-weight:600; font-size:.8rem; }
.py-addfirm{ display:flex; align-items:center; gap:9px; flex-wrap:wrap; padding:11px 13px;
  border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--surface-sunken,#fbfcfe); }
.py-addfirm input[name="firm_name"]{ width:220px; }
.py-addfirm input[name="brand_name"], .py-addfirm input[name="project_name"]{ width:180px; }
@media (max-width:575.98px){ .py-addfirm input.form-control{ width:100%; } }
html[data-theme="dark"] .py-note{ border-color:rgba(255,255,255,.10); }
html[data-theme="dark"] .py-chip{ background:rgba(255,255,255,.08); border-color:rgba(255,255,255,.16); }
html[data-theme="dark"] .py-addfirm{ background:#1E293B; border-color:#334155; }

/* Plan The Year: freeze the row-label column (REVENUE/PROFIT) while the 12 month
   columns scroll. Own left offset — .tb-name/.tb-grp offsets are Trial-Balance
   specific and would park this column over the Apr cell. */
.pl-tbl .pl-c1{ position:sticky; left:0; z-index:2; background:#fff; width:150px;
  box-shadow:2px 0 0 -1px #e3e8f2; text-align:left; }
.pl-tbl thead .pl-c1{ z-index:4; background:#f7f9fc; }
.pl-tbl tfoot .pl-c1{ z-index:3; background:#f1f4fb; }
.pl-tbl tbody tr:hover .pl-c1{ background:#f6f8fd; }
.pl-tbl tbody tr.table-light .pl-c1{ background:#f1f4fb; }
html[data-theme="dark"] .pl-tbl .pl-c1{ background:#141c2b; box-shadow:2px 0 0 -1px #2a3852; }
html[data-theme="dark"] .pl-tbl thead .pl-c1{ background:#1a2436; }
html[data-theme="dark"] .pl-tbl tbody tr:hover .pl-c1{ background:#213046; }
html[data-theme="dark"] .pl-tbl tbody tr.table-light .pl-c1{ background:#0f1826; }
/* Plan The Year: profit-margin % printed under each profit input (profit ÷ revenue
   for that month). Normal weight, muted; red when the month runs at a loss. */
.pl-pct{ margin-top:3px; font-size:.72rem; font-weight:400; color:var(--muted);
  text-align:right; font-variant-numeric:tabular-nums; line-height:1.2; min-height:.9rem; }
.pl-pct.neg{ color:var(--bad); }
/* #8a97a8 measured 4.49:1 on the raised surface this sits on — one hundredth under AA.
   A percentage on a plan is data, not decoration, so it takes the readable ink. */
html[data-theme="dark"] .pl-pct{ color:#9aa7b8; }
html[data-theme="dark"] .pl-pct.neg{ color:#f28b82; }

/* ===== Expense Analysis (fin_pnl) — ranked spend, budget-vs-actual, split, trend ===== */
.ea-rank{ display:flex; flex-direction:column; gap:9px; }
.ea-rrow{ display:grid; grid-template-columns:170px 1fr 128px; align-items:center; gap:12px; }
.ea-name{ font-size:.86rem; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ea-dot{ display:inline-block; width:8px; height:8px; border-radius:2px; margin-right:7px; vertical-align:middle; }
.ea-dot.op{ background:var(--kz); } .ea-dot.fin{ background:#7c8195; }
/* Expense types: operating / depreciation & amortisation / financing */
.ea-dot.ea-t-operating{ background:var(--kz); }
.ea-dot.ea-t-depreciation{ background:var(--accent); }
.ea-dot.ea-t-financing{ background:#7c8195; }
.ea-track{ height:22px; background:#eef2f9; border-radius:6px; overflow:hidden; }
.ea-fill{ height:100%; border-radius:6px; background:linear-gradient(90deg,var(--kz),var(--kz2)); min-width:2px; }
.ea-fill.fin{ background:linear-gradient(90deg,#7c8195,#9aa2b5); }
.ea-fill.ea-t-operating{ background:linear-gradient(90deg,var(--kz),var(--kz2)); }
.ea-fill.ea-t-depreciation{ background:linear-gradient(90deg,var(--accent),#67d3e8); }
.ea-fill.ea-t-financing{ background:linear-gradient(90deg,#7c8195,#9aa2b5); }
.ea-rval{ text-align:right; font-size:.85rem; font-weight:700; font-variant-numeric:tabular-nums; }
.ea-rpct{ color:var(--muted); font-weight:500; font-size:.76rem; }
@media (max-width:560px){ .ea-rrow{ grid-template-columns:110px 1fr 92px; } }
/* operating vs financing split */
.ea-split{ display:flex; height:30px; border-radius:8px; overflow:hidden; margin-top:16px; border:1px solid var(--border); }
.ea-seg{ display:flex; align-items:center; justify-content:center; font-size:.74rem; font-weight:700; color:#fff; white-space:nowrap; padding:0 10px; min-width:0; overflow:hidden; }
.ea-seg.op{ background:linear-gradient(120deg,var(--kz),var(--kz2)); }
.ea-seg.fi{ background:#6b7180; }
.ea-seg.ea-t-operating{ background:linear-gradient(120deg,var(--kz),var(--kz2)); }
.ea-seg.ea-t-depreciation{ background:var(--accent); }
.ea-seg.ea-t-financing{ background:#6b7180; }
.ea-legend{ display:flex; gap:16px; margin-top:9px; font-size:.76rem; color:var(--muted); flex-wrap:wrap; }
.ea-legend span{ display:inline-flex; align-items:center; gap:6px; }
.ea-legend i{ width:10px; height:10px; border-radius:3px; display:inline-block; }
.ea-legend-val{ gap:10px 20px; font-size:.78rem; }
.ea-legend-val b{ color:var(--ink); font-weight:700; }
.ea-legend-val .ea-lpct{ color:var(--muted); font-weight:700; }
html[data-theme="dark"] .ea-legend-val b{ color:var(--e2,#E2E8F0); }
.ea-legend i.op{ background:var(--kz); } .ea-legend i.fi{ background:#6b7180; }
.ea-legend i.ea-t-operating{ background:var(--kz); }
.ea-legend i.ea-t-depreciation{ background:var(--accent); }
.ea-legend i.ea-t-financing{ background:#6b7180; }
/* "Where the money goes" spend heatmap (head × month) */
.hm-wrap{ overflow-x:auto; margin:8px 0 4px; }
.hm{ border-collapse:separate; border-spacing:3px; width:100%; }
.hm th, .hm td{ font-variant-numeric:tabular-nums; }
.hm thead th{ font-size:.68rem; font-weight:700; text-transform:uppercase; letter-spacing:.02em;
  color:var(--muted); text-align:center; padding:2px 4px; }
.hm .hm-corner{ text-align:left; }
.hm .hm-head{ text-align:left; font-size:.8rem; font-weight:600; white-space:nowrap; padding-right:10px;
  position:sticky; left:0; background:var(--card,#fff); z-index:1; }
.hm .hm-head .ea-dot{ margin-right:6px; }
.hm-cell{ width:64px; min-width:56px; height:38px; text-align:center; border-radius:7px;
  background:#f1f4f9; color:var(--ink); font-size:.72rem; font-weight:600; vertical-align:middle; }
.hm-cell.zero{ background:repeating-linear-gradient(-45deg,#f5f7fb,#f5f7fb 4px,#eef2f8 4px,#eef2f8 8px); color:transparent; }
.hm-cell.hot{ color:#fff; }
.hm-cell span{ display:inline-block; }
.hm-cell.hm-colt{ background:transparent; font-weight:700; color:var(--muted); }
.hm .hm-tot-h{ text-align:right; }
.hm .hm-tot{ text-align:right; font-weight:700; font-size:.78rem; white-space:nowrap; padding-left:8px; }
.hm tfoot th, .hm tfoot .hm-tot{ padding-top:5px; }
html[data-theme="dark"] .hm .hm-head{ background:#1E293B; }
html[data-theme="dark"] .hm-cell{ background:#243246; color:var(--e2,#E2E8F0); }
html[data-theme="dark"] .hm-cell.zero{ background:repeating-linear-gradient(-45deg,#1b2635,#1b2635 4px,#212f42 4px,#212f42 8px); color:transparent; }
/* budget vs actual bar */
.ea-ba{ height:9px; border-radius:5px; background:#eef2f9; position:relative; overflow:hidden; }
.ea-bud{ position:absolute; inset:0 auto 0 0; height:100%; background:#c7cfdd; }
.ea-act{ position:absolute; inset:0 auto 0 0; height:100%; border-radius:5px; }
.ea-act.over{ background:var(--bad); } .ea-act.under{ background:var(--ok); } .ea-act.on{ background:var(--kz2); }
.table .ea-trow td{ font-weight:700; border-top:2px solid var(--border); background:#f1f4fb; }
.ea-callout{ display:flex; align-items:flex-start; gap:8px; margin:0 16px 16px; padding:11px 13px; border-radius:var(--radius-sm); background:var(--bad-bg); color:var(--bad); font-size:.84rem; font-weight:600; }
/* Cost Reduction Measures — the red-flag route out of Budget vs Actual, and the banner
   at the top of the page it leads to. Both are deliberately the WARNING tone, not the
   error tone: an overrun is something to act on, not something that has broken. */
.ea-crlink{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin:0 16px 16px;
  padding:11px 13px; border-radius:var(--radius-sm); background:var(--warn-bg);
  border:1px solid rgba(154,106,22,.22); }
.ea-crflag{ display:inline-flex; align-items:center; gap:6px; color:var(--bad);
  font-size:.84rem; font-weight:700; white-space:nowrap; }
.cr-flag{ display:flex; align-items:flex-start; gap:10px; padding:13px 15px;
  border-radius:var(--radius-sm); background:var(--warn-bg); color:var(--warn);
  border:1px solid rgba(154,106,22,.22); font-size:.88rem; }
.cr-flag > .bi{ color:var(--bad); font-size:1rem; line-height:1.4; }
.cr-flag b{ color:var(--bad); }
html[data-theme="dark"] .ea-crlink,
html[data-theme="dark"] .cr-flag{ border-color:rgba(255,255,255,.10); }
/* month-wise trend */
.ea-spark{ display:flex; align-items:flex-end; gap:5px; height:82px; }
.ea-b{ flex:1; background:linear-gradient(180deg,var(--kz2),var(--accent)); border-radius:3px 3px 0 0; min-height:3px; opacity:.85; }
.ea-b:last-child{ opacity:1; background:linear-gradient(180deg,var(--kz),var(--kz2)); }
.ea-months{ display:flex; gap:5px; margin-top:5px; }
.ea-months span{ flex:1; text-align:center; font-size:.6rem; color:var(--muted); font-variant-numeric:tabular-nums; }
/* dark mode */
html[data-theme="dark"] .ea-track, html[data-theme="dark"] .ea-ba{ background:#0f1826; }
html[data-theme="dark"] .ea-bud{ background:#3a475c; }
html[data-theme="dark"] .table .ea-trow td{ background:#0f1826; border-top-color:#2a3852; }
html[data-theme="dark"] .ea-split{ border-color:#2a3852; }
/* Budget-vs-Actual variance table — refined presentation (CA, via .ea-var-pro on the card) */
.ea-var-pro thead th{ font-size:.68rem; text-transform:uppercase; letter-spacing:.07em; color:var(--muted);
  background:#f7f9fc; border-bottom:1px solid var(--border); padding:11px 16px; }
.ea-var-pro tbody td{ padding:12px 16px; vertical-align:middle; }
.ea-var-pro tbody tr:not(.ea-trow):hover td{ background:rgba(79,70,229,.04); }
.ea-var-pro tbody tr:nth-child(even):not(.ea-trow) td{ background:rgba(15,23,42,.02); }
.ea-var-pro .ea-ba{ height:10px; border-radius:6px; }
.ea-var-pro .ea-act{ border-radius:6px; }
.ea-var-pro .ea-var{ font-weight:700; white-space:nowrap; }
.ea-var-pro .ea-var .bi{ font-size:.66rem; vertical-align:1px; }
.ea-var-pro .ea-trow td{ background:#eef1f9; font-size:.95rem; }
html[data-theme="dark"] .ea-var-pro thead th{ background:#243246; color:var(--th-fg); border-color:#334155; }
html[data-theme="dark"] .ea-var-pro tbody tr:not(.ea-trow):hover td{ background:rgba(165,180,252,.06); }
html[data-theme="dark"] .ea-var-pro tbody tr:nth-child(even):not(.ea-trow) td{ background:rgba(255,255,255,.02); }
html[data-theme="dark"] .ea-var-pro .ea-trow td{ background:#0f1826; }
/* Budget-vs-Actual: cap height on long tables (internal scroll) + sticky header + filter empty-state */
.ea-var-scroll{ max-height:clamp(320px, 52vh, 560px); overflow-y:auto; }
.ea-var-pro thead th{ position:sticky; top:0; z-index:2; }
.ea-var-noresult{ text-align:center; color:var(--muted); padding:26px 12px; font-size:.9rem; }
/* Budget-vs-Actual "Heads" dropdown — searchable name checklist */
.ea-headlist{ max-height:196px; overflow-y:auto; margin-top:8px; border:1px solid var(--hairline, var(--border)); border-radius:10px; padding:4px; }
.ea-headitem{ display:flex; align-items:center; gap:9px; padding:6px 8px; border-radius:8px; cursor:pointer; font-size:.85rem; color:var(--fg, var(--ink)); }
.ea-headitem:hover{ background:color-mix(in srgb, var(--kz) 8%, transparent); }
.ea-headitem input{ width:15px; height:15px; accent-color:var(--kz); flex:0 0 auto; }
.ea-headname{ flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ea-headempty{ padding:10px 8px; color:var(--muted); font-size:.82rem; }
.ea-headtools{ display:flex; gap:14px; margin-top:7px; }
.ea-headtools button{ background:none; border:0; padding:0; color:var(--kz); font-size:.78rem; font-weight:600; cursor:pointer; }
.ea-headtools button:hover{ text-decoration:underline; }
/* funnel filter button on the "Head" column header (left) */
.ea-hf{ display:inline-flex; align-items:center; }
.ea-hf-btn{ display:inline-flex; align-items:center; gap:3px; border:0; background:none; padding:2px 5px; margin-left:5px; color:var(--muted); cursor:pointer; border-radius:6px; }
.ea-hf-btn:hover{ color:var(--kz); background:color-mix(in srgb, var(--kz) 12%, transparent); }
.ea-hf-btn.tbf-active{ color:var(--kz); }
.ea-hf-btn .ki{ width:14px; height:14px; }
.ea-hf .tbf-panel{ text-transform:none; letter-spacing:normal; }  /* panel text is normal, not the caps th */
/* Budget-vs-Actual category-wise table (Expand toggles categories <-> individual expenses) */
.ea-vartable .ea-catrow > td{ font-weight:700; background:color-mix(in srgb, var(--kz) 6%, transparent) !important; }
.ea-catname{ font-weight:700; }
.ea-catcount{ margin-left:8px; font-weight:400; }
.ea-expandall .bi{ font-size:.85rem; }
html[data-theme="dark"] .inv-tbl .inv-c2, html[data-theme="dark"] .inv-tbl .inv-uom{ box-shadow:2px 0 0 -1px #2a3852; }

/* Balance Sheet — clickable accordion of Capital / Liabilities / Assets */
.bs-group{ border-bottom:1px solid var(--border); }
.bs-group:last-child{ border-bottom:0; }
.bs-sum{ display:flex; justify-content:space-between; align-items:center; gap:12px; padding:14px 18px;
  cursor:pointer; font-weight:700; list-style:none; }
.bs-sum::-webkit-details-marker{ display:none; }
.bs-sum .chev{ transition:transform .18s ease; margin-right:9px; color:var(--muted); font-size:.9rem; }
.bs-group[open] > .bs-sum .chev{ transform:rotate(90deg); }
.bs-sum .bs-total{ font-variant-numeric:tabular-nums; font-size:1.05rem; }
.bs-body{ padding:0 18px 14px; }
.bs-head-row{ display:flex; justify-content:space-between; gap:12px; padding:9px 0; border-top:1px solid var(--border); font-weight:600; }
.bs-subhead{ font-size:.7rem; text-transform:uppercase; letter-spacing:.07em; color:var(--muted); margin-top:14px; margin-bottom:2px; font-weight:700; }
.bs-led{ display:flex; justify-content:space-between; gap:12px; padding:3px 0 3px 20px; font-size:.85rem; color:var(--muted); }
.bs-led .n{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.bs-amt{ font-variant-numeric:tabular-nums; white-space:nowrap; }
.bs-empty{ padding:3px 0 3px 20px; font-size:.82rem; color:var(--faint,#94a3b8); font-style:italic; }
html[data-theme="dark"] .bs-group{ border-bottom-color:#2a3852; }
html[data-theme="dark"] .bs-head-row{ border-top-color:#2a3852; }

/* Finance ratio help icon (tooltip trigger on stat cards) */
.fin-help{ color:var(--muted); font-size:.76rem; cursor:help; margin-left:3px; opacity:.65; vertical-align:baseline; }
.fin-help:hover{ opacity:1; color:var(--kz); }

/* Balance Sheet — visible descriptions under each heading */
.bs-sum-l{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.bs-sum-desc{ font-size:.76rem; font-weight:400; color:var(--muted); padding-left:24px; max-width:64ch; }
.bs-head-l{ display:flex; flex-direction:column; gap:1px; min-width:0; }
.bs-head-desc{ font-size:.75rem; font-weight:400; color:var(--muted); max-width:64ch; }

/* Finance grids: consistent tabular numerals + size so columns/rows line up evenly */
.tb-tbl input.form-control, .tb-tbl select.form-select,
.tb-tbl td, .tb-tbl th{ font-variant-numeric:tabular-nums; font-feature-settings:"tnum" 1, "lnum" 1; }
.tb-tbl .tb-m, .tb-tbl .tb-m .form-control, .tb-tbl .inv-uom{ font-size:.82rem; }
.tb-tbl .tb-amt{ font-family:inherit; letter-spacing:0; }
/* An empty amount cell reads as a DASH (ux.js sets the placeholder — see there for why
   it is a placeholder and never a value). It states "no figure recorded", so it is a
   value to be read, not a hint: stronger than the browser's default placeholder grey,
   and full opacity. `--fg-faint` is theme-aware, so dark mode follows. */
input.tb-amt::placeholder, input.kz-inr::placeholder{ color:var(--fg-faint); opacity:1; }
/* Trial-Balance grouping select column */
.tb-tbl .tb-grp{ width:210px; }
.tb-tbl .tb-grp .form-select{ font-size:.8rem; }

/* Trial Balance: freeze Group + Ledger Name columns on horizontal scroll.
   Column order is now Group (leftmost) then Ledger Account Name (owner 2026-07-25);
   the A/c No. column was removed. tb-grp width is 210px, so tb-name starts at 210. */
.tb-tbl .tb-act, .tb-tbl .tb-no, .tb-tbl .tb-name, .tb-tbl .tb-grp{ position:sticky; z-index:2; background:#fff; }
.tb-tbl .tb-grp{ left:0; }
.tb-tbl .tb-name{ left:210px; box-shadow:2px 0 0 -1px #e3e8f2; }
/* numbers right-aligned + decimals visible (owner ask): TB month cells + totals */
#tbTable .tb-m, #tbTable .tb-m .form-control{ text-align:left; }
.tb-tbl thead .tb-act, .tb-tbl thead .tb-no, .tb-tbl thead .tb-name, .tb-tbl thead .tb-grp{ z-index:4; background:#f7f9fc; }
.tb-tbl tfoot .tb-act, .tb-tbl tfoot .tb-no, .tb-tbl tfoot .tb-name, .tb-tbl tfoot .tb-grp{ z-index:3; background:#f1f4fb; }
.tb-tbl tbody tr:hover .tb-act, .tb-tbl tbody tr:hover .tb-no, .tb-tbl tbody tr:hover .tb-name, .tb-tbl tbody tr:hover .tb-grp{ background:#f6f8fd; }
html[data-theme="dark"] .tb-tbl .tb-act, html[data-theme="dark"] .tb-tbl .tb-no, html[data-theme="dark"] .tb-tbl .tb-name, html[data-theme="dark"] .tb-tbl .tb-grp{ background:#141c2b; }
html[data-theme="dark"] .tb-tbl thead .tb-act, html[data-theme="dark"] .tb-tbl thead .tb-no, html[data-theme="dark"] .tb-tbl thead .tb-name, html[data-theme="dark"] .tb-tbl thead .tb-grp{ background:#1a2436; }
html[data-theme="dark"] .tb-tbl tfoot .tb-act, html[data-theme="dark"] .tb-tbl tfoot .tb-no, html[data-theme="dark"] .tb-tbl tfoot .tb-name, html[data-theme="dark"] .tb-tbl tfoot .tb-grp{ background:#0f1826; }
html[data-theme="dark"] .tb-tbl tbody tr:hover .tb-act, html[data-theme="dark"] .tb-tbl tbody tr:hover .tb-no, html[data-theme="dark"] .tb-tbl tbody tr:hover .tb-name, html[data-theme="dark"] .tb-tbl tbody tr:hover .tb-grp{ background:#213046; }
html[data-theme="dark"] .tb-tbl .tb-name{ box-shadow:2px 0 0 -1px #2a3852; }

/* Trial Balance bulk-select column (2026-08-17). It is a .ge-col — it EXISTS only in
   edit mode — so it freezes at left:0 and pushes the other two frozen columns right by
   its own width, and ONLY while editing. Getting this wrong is the hard-coded-`left:`
   trap this file has already been bitten by three times: leave tb-grp at left:0 with a
   tick column ahead of it and the Group cells park on top of the checkboxes the moment
   Edit is pressed. Keep these three numbers in step: 38 = tb-bulk, 38+210 = tb-name. */
.tb-tbl .tb-bulk{ width:38px; text-align:center; padding-left:6px; padding-right:6px;
  position:sticky; left:0; z-index:2; background:#fff; }
.tb-tbl thead .tb-bulk{ z-index:4; background:#f7f9fc; }
.tb-tbl tfoot .tb-bulk{ z-index:3; background:#f1f4fb; }
.tb-tbl tbody tr:hover .tb-bulk{ background:#f6f8fd; }
.tb-tbl.grid-editing .tb-grp{ left:38px; }
.tb-tbl.grid-editing .tb-name{ left:248px; }
/* The month-wise grids (Sales / COGS / Inventory / Expenses / Budget) carry the same tick
   column since 2026-09-09. Their frozen label columns move right by the tick's 38px in
   edit mode and ONLY then — same trap, same cure as the Trial Balance above. */
.inv-tbl.grid-editing .inv-c1, .inv-tbl.grid-editing .inv-cat{ left:38px; }
.inv-tbl.grid-editing .inv-c2, .inv-tbl.grid-editing .inv-uom{ left:200px; }
.exp-tbl.grid-editing .exp-c1, .exp-tbl.grid-editing .exp-head{ left:38px; }
.exp-tbl.grid-editing .exp-c2, .exp-tbl.grid-editing .exp-type{ left:268px; }
.inv-tbl tbody .tb-bulk, .exp-tbl tbody .tb-bulk{ vertical-align:middle; }
html[data-theme="dark"] .tb-tbl .tb-bulk{ background:#141c2b; }
html[data-theme="dark"] .tb-tbl thead .tb-bulk{ background:#1a2436; }
html[data-theme="dark"] .tb-tbl tfoot .tb-bulk{ background:#0f1826; }
html[data-theme="dark"] .tb-tbl tbody tr:hover .tb-bulk{ background:#213046; }

/* ===== Savings Scheme ("Bishi") — jeweller monthly savings schemes =============
   Placed under Financial Year at a Glance as ONE page with four `.seg-tab` panels
   (owner ask 2026-08-19; the build brief had proposed three separate pages).
   ⚠ The `.seg-tab` primitive needs a per-instance :checked rule — it is written as
   an explicit selector list, not a generic one, so a new tab set that forgets this
   renders every panel hidden and looks completely broken. */
#ss-members:checked   ~ .seg-tabbar label[for="ss-members"],
#ss-dues:checked      ~ .seg-tabbar label[for="ss-dues"],
#ss-analytics:checked ~ .seg-tabbar label[for="ss-analytics"],
#ss-plans:checked     ~ .seg-tabbar label[for="ss-plans"] {
  background:linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent)); color:#fff; border-color:transparent; }
#ss-members:focus-visible   ~ .seg-tabbar label[for="ss-members"],
#ss-dues:focus-visible      ~ .seg-tabbar label[for="ss-dues"],
#ss-analytics:focus-visible ~ .seg-tabbar label[for="ss-analytics"],
#ss-plans:focus-visible     ~ .seg-tabbar label[for="ss-plans"] { outline:2px solid var(--kz); outline-offset:2px; }
#ss-members:checked   ~ #sspanel-members,
#ss-dues:checked      ~ #sspanel-dues,
#ss-analytics:checked ~ #sspanel-analytics,
#ss-plans:checked     ~ #sspanel-plans { display:block; }

/* a lens heading — the three analytics groups the owner named */
.ss-lens{ font-size:.7rem; font-weight:800; letter-spacing:.07em; text-transform:uppercase;
  color:var(--fg-muted); margin:1.25rem 0 .5rem; }
.ss-lens:first-child{ margin-top:0; }

.ss-tbl td, .ss-tbl th{ vertical-align:middle; font-size:var(--fs-table,.82rem); }
.ss-tbl .num, .ss-tbl th.num{ text-align:left; font-variant-numeric:tabular-nums; }

/* how far through the scheme this member is */
.ss-prog{ height:6px; border-radius:999px; background:var(--surface-sunken,rgba(15,23,42,.08));
  overflow:hidden; min-width:74px; }
.ss-prog > span{ display:block; height:100%; border-radius:999px;
  background:linear-gradient(90deg,var(--kz),var(--kz2)); }
.ss-wt{ font-size:.7rem; color:var(--fg-muted); font-variant-numeric:tabular-nums; }
.ss-over{ font-size:.7rem; font-weight:700; color:var(--bad); }
.ss-row-over > td{ background:color-mix(in srgb, var(--bad) 6%, transparent); }
.ss-next{ font-size:.8rem; color:var(--fg-muted); }

/* the paid / not-paid tick — the paper passbook's own mark */
.ss-tick{ text-align:center; font-size:1rem; line-height:1; color:var(--fg-faint); }
.ss-tick.is-paid{ color:var(--ok,#12855f); font-weight:800; }

.ss-cust{ border:1px solid var(--border); border-radius:10px; padding:.6rem .75rem; height:100%; }
.ss-chip{ display:inline-block; margin:.2rem .25rem 0 0; padding:.15rem .5rem; border-radius:999px;
  border:1px solid var(--border); font-size:.7rem; text-decoration:none; color:var(--fg-muted); }
.ss-chip:hover{ border-color:var(--kz); color:var(--kz); }

.ss-pipe{ font-family:var(--font-mono); font-size:1.35rem; font-weight:800; color:var(--fg);
  font-variant-numeric:tabular-nums; line-height:1.2; margin:.15rem 0 .2rem; white-space:nowrap; }

/* scheme cards in the builder */
.ss-inactive{ opacity:.62; }
.ss-facts{ display:grid; grid-template-columns:1fr 1fr; gap:.35rem .9rem; }
.ss-facts > div{ display:flex; flex-direction:column; }
.ss-facts span{ font-size:.66rem; text-transform:uppercase; letter-spacing:.05em; color:var(--fg-muted); }
.ss-facts b{ font-size:.82rem; }
@media (max-width:575.98px){ .ss-facts{ grid-template-columns:1fr; } }

/* collections trend — a plain CSS bar row, no chart library for twelve numbers */
.ss-trend{ display:flex; align-items:flex-end; gap:.5rem; height:130px; padding-top:.25rem; }
.ss-trend .ss-bar{ flex:1 1 0; display:flex; flex-direction:column; justify-content:flex-end;
  align-items:center; height:100%; min-width:0; }
.ss-trend .ss-bar > span{ display:block; width:100%; max-width:38px; border-radius:6px 6px 0 0;
  background:linear-gradient(180deg,var(--kz),var(--kz2)); min-height:2px; }
.ss-trend .ss-bar > em{ font-style:normal; font-size:.62rem; color:var(--fg-muted); margin-top:.3rem;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; }

/* A stat card that is a CONTROL — the Collections & Dues cards filter the lists below.
   A real <button> so it is keyboard-reachable and announces its state; the card inside
   keeps its own look, the button only adds the pressed affordance. */
.ss-cardbtn{ display:block; width:100%; padding:0; border:0; background:none; text-align:inherit;
  border-radius:var(--radius); }
.ss-cardbtn .stat-card{ height:100%; transition:box-shadow .15s ease, transform .15s ease; }
.ss-cardbtn:hover .stat-card{ box-shadow:var(--shadow); transform:translateY(-1px); }
.ss-cardbtn:focus-visible{ outline:3px solid var(--accent); outline-offset:2px; }
.ss-cardbtn[aria-pressed="true"] .stat-card{ border-color:var(--kz);
  box-shadow:0 0 0 2px color-mix(in srgb, var(--kz) 55%, transparent); }

.ss-search{ width:230px; max-width:52vw; }

/* Joining vs maturing — two counts on ONE axis, so the comparison is honest.
   Same construction as .plan-* (§9i): a definite track height, and `flex:1 1 0;
   min-width:0` so twelve months never burst the card. */
.ss-flow{ display:flex; align-items:flex-end; gap:6px; height:150px; padding-top:6px; }
.ss-fcol{ flex:1 1 0; min-width:0; display:flex; flex-direction:column;
  align-items:center; justify-content:flex-end; height:100%; }
.ss-ftrack{ flex:1 1 auto; min-height:0; width:100%; display:flex; align-items:flex-end;
  justify-content:center; gap:2px; }
.ss-fbar{ display:block; width:100%; max-width:12px; min-height:2px; border-radius:4px 4px 0 0; }
.ss-fbar.j{ background:linear-gradient(180deg,var(--kz),var(--kz2)); }
.ss-fbar.m{ background:linear-gradient(180deg,var(--accent),#67e8f9); }
.ss-fcol > em{ font-style:normal; font-size:.62rem; color:var(--fg-muted); margin-top:.3rem;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; }
.ss-legend{ display:flex; gap:1rem; margin-top:.5rem; font-size:.7rem; color:var(--fg-muted); }
.ss-legend i{ display:inline-block; width:10px; height:10px; border-radius:3px; margin-right:.3rem; }
.ss-legend i.j{ background:var(--kz); } .ss-legend i.m{ background:var(--accent); }

html[data-theme="dark"] .ss-prog{ background:rgba(255,255,255,.10); }
html[data-theme="dark"] .ss-cust, html[data-theme="dark"] .ss-chip{ border-color:#334155; }
html[data-theme="dark"] .ss-tick.is-paid{ color:#34D399; }

/* ===== Turnover Calculator — Inventory page only ================================
   Owner brief 2026-08-18; moved out of a floating bottom-right FAB and into the page
   toolbar on 2026-08-19 (owner ask). The panel now hangs from its own button inside a
   `position:relative` wrapper, so it scrolls WITH the toolbar instead of being pinned to
   the viewport — a floating card that stays put while the table moves under it reads as
   detached from the numbers it is about.
   ⚠ The wrapper must stay OUTSIDE the table's `.tb-scroll`, or `overflow-x:auto` clips
   the panel to the scroller instead of letting it overhang.
   Dark mode is authored here, not bolted on: every colour is a token. */
.tc-wrap{ position:relative; }
.tc-panel{ position:absolute; top:calc(100% + 8px); left:0; z-index:1041;
  /* Sized from a MEASUREMENT, not a guess (owner ask 2026-08-19): with the explainer
     OPEN the content is 607px tall at 344px wide and 569px at 380px+, so widening to 400
     buys ~38px of height back and anything past ~380 buys nothing — the text already
     fits. The cap then has to clear 569px on a 768px-tall laptop, hence 84vh rather than
     70. Result: the card never scrolls, explainer open or shut.
     ⚠ Re-measure before changing either number; see testing/turnover_widget_browser.py,
     which asserts scrollHeight <= clientHeight in BOTH states at two viewport heights. */
  width:400px; max-width:calc(100vw - 32px); max-height:84vh; overflow:auto;
  background:var(--surface-card,#fff); border:2px solid var(--kz);
  border-radius:var(--radius); box-shadow:0 18px 44px -14px rgba(15,23,42,.34); }
.tc-head{ display:flex; align-items:center; justify-content:space-between; gap:8px;
  padding:11px 14px; border-bottom:2px solid var(--border); font-weight:800; font-size:.95rem;
  letter-spacing:-.01em; color:var(--fg); position:sticky; top:0; background:inherit; z-index:1; }
.tc-head .bi{ color:var(--kz); }
.tc-x{ border:0; background:transparent; font-size:1.3rem; line-height:1; color:var(--fg-muted);
  cursor:pointer; padding:0 2px; }
.tc-x:hover{ color:var(--fg); }
.tc-body{ padding:12px 14px 6px; }
/* the tool explains itself — see the note in the template on why it is this short */
/* The explainer folds away — see the template comment. Shut, it is one short row, and
   that is what keeps Estimated Turnover above the fold when the panel opens.
   Built on the system's own `.kz-note` disclosure (defined near the top of this file):
   the chevron, the rotate-on-open, the dark-mode summary colour and the reduced-motion
   guard all come from there. Only the panel-specific sizing lives here. */
.tc-disc{ font-size:.76rem; margin:0 0 11px; }
.tc-disc > summary{ font-weight:800; font-size:.76rem; }
.tc-note{ font-size:.78rem; font-weight:600; line-height:1.6; color:var(--fg); margin:7px 0 0;
  padding:10px 12px; background:rgba(79,70,229,.07);
  border-left:4px solid var(--kz); border-radius:0 8px 8px 0; }
.tc-note b{ font-weight:800; }
.tc-lbl{ display:block; font-size:.8rem; font-weight:800; color:var(--fg); margin-bottom:4px; }
.tc-field{ position:relative; display:flex; align-items:center; }
.tc-field .form-control{ padding-left:26px; font-variant-numeric:tabular-nums;
  font-weight:700; font-size:.95rem; border-width:2px; }
.tc-cur,.tc-unit{ position:absolute; font-size:.88rem; font-weight:800; color:var(--fg); pointer-events:none; }
.tc-cur{ left:9px; }
.tc-unit{ right:10px; }
.tc-field:has(.tc-unit) .form-control{ padding-left:.6rem; padding-right:24px; }
/* .tc-what = what the figure IS (fixed); .tc-hint = where THIS value came from (changes) */
.tc-what{ font-size:.72rem; font-weight:500; line-height:1.5; color:var(--fg-muted); margin:5px 0 2px; }
.tc-what b{ font-weight:800; color:var(--fg); }
.tc-hint{ font-size:.72rem; font-weight:700; color:var(--kz); margin:0 0 12px; }
.tc-out{ border-top:2px dashed var(--border); margin-top:8px; padding-top:11px; }
.tc-out .l{ font-size:.72rem; font-weight:800; letter-spacing:.06em; text-transform:uppercase; color:var(--fg-muted); }
.tc-out .v{ font-family:var(--font-mono); font-size:1.5rem; font-weight:800; color:var(--fg);
  font-variant-numeric:tabular-nums; line-height:1.25; margin:3px 0 5px; }
.tc-out .d{ font-size:.72rem; font-weight:500; color:var(--fg-muted); line-height:1.5; }
.tc-out .d b{ font-weight:800; color:var(--fg); }
/* Sticky, like the header: on a phone the sheet is taller than its max-height, and a
   "Reset to actuals" you have to scroll to find is a "Reset to actuals" nobody finds. */
.tc-foot{ display:flex; align-items:center; justify-content:space-between; gap:8px;
  padding:9px 14px 12px; position:sticky; bottom:0; background:inherit;
  border-top:1px solid var(--border); }
.tc-reset{ border:0; background:transparent; padding:0; font-size:.78rem; font-weight:800;
  color:var(--kz); cursor:pointer; text-decoration:underline; }
.tc-note-sm{ font-size:.7rem; font-weight:600; color:var(--fg-faint); }
#tcOpen:focus-visible{ outline:3px solid var(--accent); outline-offset:2px; }
html[data-theme="dark"] .tc-panel{ background:#1E293B; border-color:var(--kz2,#818CF8);
  box-shadow:0 18px 44px -14px rgba(0,0,0,.7); }
html[data-theme="dark"] .tc-head{ border-bottom-color:#334155; }
html[data-theme="dark"] .tc-out,
html[data-theme="dark"] .tc-foot{ border-top-color:#334155; }
html[data-theme="dark"] .tc-note{ background:rgba(129,140,248,.18); }
html[data-theme="dark"] .tc-hint{ color:var(--kz2,#818CF8); }
@media (max-width:575.98px){
  /* a phone gets a bottom sheet — a 340px card hanging off a toolbar button would run
     straight off the right edge */
  .tc-panel{ position:fixed; top:auto; right:0; left:0; bottom:0; width:auto; max-width:none;
    border-radius:var(--radius) var(--radius) 0 0; max-height:78vh; }
}

/* Balance Sheet ▸ Inventories — the WEIGHT beside the value (owner ask 2026-08-18).
   Subordinate to the money: a jeweller wants both, but the rupee figure is the one the
   sheet totals. Tabular figures so the units line up down the list. */
.bs-wt{ display:block; font-size:.72rem; font-weight:600; color:var(--fg-muted);
  font-variant-numeric:tabular-nums; margin-top:1px; }
.bs-qty{ display:inline-block; min-width:92px; text-align:right; margin-right:12px;
  font-size:.76rem; color:var(--fg-muted); font-variant-numeric:tabular-nums; }

/* ── Trial Balance: single advanced filter panel ─────────────────────────── */
.tbf-btn .tbf-badge{ display:inline-flex; align-items:center; justify-content:center;
  min-width:18px; height:18px; padding:0 5px; margin-left:4px; border-radius:9px;
  background:var(--kz); color:#fff; font-size:.68rem; font-weight:700; line-height:1; }
.tbf-btn.tbf-active{ border-color:var(--kz); color:var(--kz); box-shadow:inset 0 0 0 1px var(--kz); }
.tbf-panel{ width:340px; max-width:92vw; padding:0; border:1px solid var(--border);
  border-radius:16px; box-shadow:var(--shadow-lg); overflow:hidden; }
.tbf-head{ display:flex; align-items:center; justify-content:space-between; padding:12px 14px;
  border-bottom:1px solid var(--border); background:linear-gradient(120deg,rgba(79,70,229,.07),rgba(6,182,212,.07)); }
.tbf-title{ font-weight:700; font-size:.92rem; display:flex; align-items:center; gap:7px; }
.tbf-title i{ color:var(--kz); }
.tbf-reset{ border:0; background:none; color:var(--muted); font-size:.78rem; font-weight:600; cursor:pointer; padding:0; }
.tbf-reset:hover{ color:var(--bad); }
.tbf-body{ padding:13px 14px; display:flex; flex-direction:column; gap:15px; max-height:62vh; overflow:auto; }
.tbf-label{ font-size:.7rem; font-weight:700; text-transform:uppercase; letter-spacing:.04em;
  color:var(--muted); margin-bottom:8px; }
.tbf-search{ position:relative; }
.tbf-search > i{ position:absolute; left:11px; top:50%; transform:translateY(-50%); color:var(--muted); font-size:.85rem; }
.tbf-q{ width:100%; padding:.42rem .6rem .42rem 31px; border:1px solid var(--border);
  border-radius:10px; font-size:.85rem; background:#fff; color:var(--ink); }
.tbf-q:focus{ outline:0; border-color:var(--kz); box-shadow:0 0 0 3px rgba(79,70,229,.13); }
.tbf-chips{ display:flex; flex-wrap:wrap; gap:6px; }
.tbf-chip{ display:inline-flex; align-items:center; gap:6px; padding:5px 10px; border:1px solid var(--border);
  border-radius:999px; background:#fff; color:var(--ink); font-size:.79rem; font-weight:600; cursor:pointer;
  transition:border-color .12s, background .12s, color .12s; }
.tbf-chip b{ font-weight:700; color:var(--muted); font-size:.72rem; }
@media (hover:hover){ .tbf-chip:hover{ border-color:var(--kz); } }
.tbf-chip.active{ background:linear-gradient(120deg,var(--kz),var(--kz2)); border-color:transparent; color:#fff; }
.tbf-chip.active b{ color:rgba(255,255,255,.85); }
.tbf-dot{ width:9px; height:9px; border-radius:50%; flex:0 0 9px; }
.tbf-cap{ background:#4F46E5; } .tbf-lia{ background:#F59E0B; } .tbf-ast{ background:#10B981; }
.tbf-pnl{ background:#2563EB; } .tbf-net{ background:#8B5CF6; }
/* The four expense heads are their own chips (2026-08-17). Warm family so they read as
   one block of the filter while staying tellable apart. */
.tbf-pur{ background:#EA580C; } .tbf-dir{ background:#EF4444; }
.tbf-ind{ background:#F472B6; } .tbf-tax{ background:#B45309; }
.tbf-mut{ background:#94A3B8; }
.tbf-chip.active .tbf-dot{ box-shadow:0 0 0 2px rgba(255,255,255,.55); }
.tbf-seg{ display:flex; border:1px solid var(--border); border-radius:10px; overflow:hidden; }
.tbf-segbtn{ flex:1; padding:7px 8px; border:0; border-right:1px solid var(--border); background:#fff;
  color:var(--muted); font-size:.8rem; font-weight:600; cursor:pointer; }
.tbf-segbtn:last-child{ border-right:0; }
.tbf-segbtn.active{ background:linear-gradient(120deg,var(--kz),var(--kz2)); color:#fff; }
.tbf-switch{ display:flex; align-items:center; gap:10px; cursor:pointer; font-size:.83rem; font-weight:500; margin:0; }
.tbf-switch input{ position:absolute; opacity:0; width:0; height:0; }
.tbf-slider{ position:relative; width:36px; height:20px; border-radius:20px; background:#cbd5e1; flex:0 0 36px; transition:background .15s; }
.tbf-slider::after{ content:''; position:absolute; top:2px; left:2px; width:16px; height:16px; border-radius:50%; background:#fff; transition:transform .15s; box-shadow:0 1px 2px rgba(0,0,0,.2); }
.tbf-switch input:checked + .tbf-slider{ background:var(--kz); }
.tbf-switch input:checked + .tbf-slider::after{ transform:translateX(16px); }
.tbf-switch input:focus-visible + .tbf-slider{ box-shadow:0 0 0 3px rgba(79,70,229,.28); }
.tbf-foot{ display:flex; align-items:center; justify-content:space-between; padding:10px 14px;
  border-top:1px solid var(--border); background:#f8fafc; }
.tbf-result{ font-size:.8rem; color:var(--muted); }
.tbf-result b{ color:var(--ink); }
html[data-theme="dark"] .tbf-panel{ background:#1E293B; border-color:#334155; }
html[data-theme="dark"] .tbf-head{ border-bottom-color:#334155; background:linear-gradient(120deg,rgba(99,102,241,.14),rgba(6,182,212,.10)); }
html[data-theme="dark"] .tbf-q,
html[data-theme="dark"] .tbf-chip,
html[data-theme="dark"] .tbf-segbtn{ background:#0F172A; color:#E2E8F0; border-color:#334155; }
html[data-theme="dark"] .tbf-segbtn{ border-right-color:#334155; }
html[data-theme="dark"] .tbf-foot{ background:#172033; border-top-color:#334155; }
html[data-theme="dark"] .tbf-result b{ color:#E2E8F0; }
html[data-theme="dark"] .tbf-slider{ background:#475569; }

/* ===== Sidebar toggle — the "hide / show menu" control ==========================
   Owner ask 2026-08-20. Replaces a plain `btn btn-light` whose icon was swapped in JS.

   🔴 The glyph is drawn in CSS, not taken from the sprite. The old control did
   `ic.className = 'bi sbc-ic bi-layout-sidebar'` on an element the template renders as an
   **<svg> from the sprite** — so the first click stripped its `.ki` class and applied
   Bootstrap-Icon classes that mean nothing on an <svg>. The icon simply vanished on use.
   A CSS glyph cannot break that way and needs no symbol to exist.

   The shape says what it does: a rounded panel with a filled rail down its left edge.
   Expanded, the rail is solid — the menu is there. Collapsed, it empties and the chevron
   points the way back. One object, two states, no swap. */
.sbt{ --sbt-fg:var(--fg-muted);
  display:inline-flex; align-items:center; gap:.5rem; height:32px; padding:0 .62rem 0 .5rem;
  border:1px solid var(--border); border-radius:999px; background:var(--surface-card,#fff);
  color:var(--sbt-fg); font:inherit; font-size:.78rem; font-weight:650; cursor:pointer;
  transition:border-color var(--dur,.2s) var(--ease,ease), color var(--dur,.2s) var(--ease,ease),
             background var(--dur,.2s) var(--ease,ease), box-shadow var(--dur,.2s) var(--ease,ease); }
.sbt:hover{ --sbt-fg:var(--kz); border-color:var(--kz); box-shadow:var(--shadow-sm); }
.sbt:active{ transform:translateY(.5px); }
.sbt:focus-visible{ outline:3px solid var(--accent); outline-offset:2px; }

/* the panel glyph */
.sbt-g{ position:relative; width:19px; height:15px; flex:0 0 auto;
  border:1.6px solid currentColor; border-radius:4.5px; opacity:.95; }
/* the rail — solid when the menu is open, gone when it is hidden */
.sbt-g::before{ content:""; position:absolute; top:1.4px; bottom:1.4px; left:1.4px;
  width:5px; border-radius:2px 1px 1px 2px; background:currentColor;
  transition:width var(--dur,.2s) var(--ease,ease), opacity var(--dur,.2s) var(--ease,ease); }
html.sb-collapsed .sbt-g::before{ width:0; opacity:0; }
/* a chevron that only exists while hidden, pointing back to the menu */
.sbt-g::after{ content:""; position:absolute; top:50%; left:5.5px; width:4.5px; height:4.5px;
  border-left:1.6px solid currentColor; border-bottom:1.6px solid currentColor;
  transform:translateY(-50%) rotate(45deg) scale(.4); transform-origin:center;
  opacity:0; transition:opacity var(--dur,.2s) var(--ease,ease), transform var(--dur,.2s) var(--ease,ease); }
html.sb-collapsed .sbt-g::after{ opacity:1; transform:translateY(-50%) rotate(225deg) scale(1); }

.sbt-lbl{ line-height:1; white-space:nowrap; }
/* the keyboard hint — the same ⌘K treatment the search chip uses, so the topbar reads
   as one family rather than two ideas about what a shortcut looks like */
.sbt-kbd{ font-family:var(--font-mono); font-size:.62rem; font-weight:700; line-height:1;
  padding:.2rem .3rem; border-radius:5px; border:1px solid var(--border);
  background:var(--surface-sunken,#f1f5f9); color:var(--fg-faint); }
.sbt:hover .sbt-kbd{ border-color:color-mix(in srgb, var(--kz) 35%, var(--border)); }

html[data-theme="dark"] .sbt{ background:#1E293B; border-color:#334155; }
html[data-theme="dark"] .sbt:hover{ border-color:#818CF8; --sbt-fg:#A5B4FC; }
html[data-theme="dark"] .sbt-kbd{ background:#0F172A; border-color:#334155; color:#8a97a8; }

/* ⚠ Every transition here is opt-in to motion. The glyph animates on a state change the
   user asked for, which is exactly the kind of movement reduced-motion users switch off. */
@media (prefers-reduced-motion:reduce){
  .sbt, .sbt-g::before, .sbt-g::after{ transition:none; }
}

/* Desktop: user-collapsible sidebar for a wider workspace (state persisted). On
   mobile the sidebar is already off-canvas, so this only applies from lg up. */
.content{ transition:margin-left .25s ease; }
@media (min-width: 992px){
  html.sb-collapsed .sidebar{ transform:translateX(-100%); }
  html.sb-collapsed .content{ margin-left:0; }
}

/* Balance Sheet — master-detail: dropdown sidebar (left) + body panels (right) */
.bs-master{ display:grid; grid-template-columns:340px minmax(0,1fr); gap:16px; align-items:start; }
@media (max-width: 900px){ .bs-master{ grid-template-columns:1fr; } }
.bs-side{ padding:0; overflow:hidden; }
.bs-side-head{ padding:14px 16px 11px; }
.bs-side-head .t{ font-size:.72rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--muted); }
.bs-side-head .h{ font-size:.75rem; color:#94a3b8; margin-top:2px; }
.bs-navgrp{ display:flex; align-items:center; justify-content:space-between; gap:8px; width:100%;
  padding:11px 14px; border:0; border-top:1px solid var(--border); background:transparent; cursor:pointer; font:inherit; text-align:left; }
.bs-navgrp .l{ display:flex; align-items:center; gap:9px; font-weight:700; font-size:.9rem; color:var(--ink); }
.bs-navgrp .chev{ color:var(--muted); font-size:.7rem; transition:transform .18s ease; }
.bs-navgrp .amt{ font-variant-numeric:tabular-nums; font-size:.85rem; color:var(--muted); font-weight:600; }
.bs-navgrp.active{ background:#EEF2FF; }
.bs-navgrp.active .l, .bs-navgrp.active .amt{ color:var(--kz); }
.bs-navgrp.active .chev{ transform:rotate(90deg); }
.bs-navsub{ display:none; align-items:center; justify-content:space-between; gap:8px; padding:8px 14px 8px 34px;
  font-size:.82rem; color:var(--muted); cursor:pointer; }
.bs-navsub:hover{ background:var(--mut-bg); }
.bs-navsub.show{ display:flex; }
.bs-navsub .amt{ font-variant-numeric:tabular-nums; }
.bs-navsub.on{ color:var(--kz); font-weight:600; }
.bs-panel{ display:none; }
.bs-panel.active{ display:block; }
.bs-panel-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:16px; padding-bottom:14px; margin-bottom:14px; border-bottom:1px solid var(--border); }
.bs-panel-head h4{ font-size:1.15rem; }
.bs-panel-head .desc{ font-size:.8rem; color:var(--muted); margin-top:4px; max-width:66ch; }
.bs-panel-head .tot{ text-align:right; white-space:nowrap; }
.bs-panel-head .tot .k{ font-size:.62rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:#94a3b8; }
.bs-panel-head .tot .v{ font-size:1.2rem; font-weight:700; font-variant-numeric:tabular-nums; }
.bs-subblock{ border:1px solid #eef1f8; border-radius:11px; padding:11px 14px 12px; margin-bottom:10px; background:#fcfcfe; }
.bs-subblock .sh{ display:flex; align-items:center; justify-content:space-between; gap:8px; }
.bs-subblock .sh .nm{ display:flex; align-items:center; gap:8px; font-weight:600; }
.bs-subblock .sh .amt{ font-weight:700; font-variant-numeric:tabular-nums; white-space:nowrap; }
.bs-subblock .sd{ font-size:.75rem; color:#94a3b8; margin-top:3px; }
.bs-subled{ display:flex; align-items:center; justify-content:space-between; gap:8px; padding:4px 0; font-size:.82rem; color:var(--muted); font-variant-numeric:tabular-nums; }
.bs-subled .n{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.bs-subempty{ font-size:.8rem; color:#94a3b8; font-style:italic; padding:4px 0; }
html[data-theme="dark"] .bs-navgrp{ border-top-color:#233149; }
html[data-theme="dark"] .bs-navgrp.active{ background:#1e2740; }
html[data-theme="dark"] .bs-navgrp.active .l, html[data-theme="dark"] .bs-navgrp.active .amt, html[data-theme="dark"] .bs-navsub.on{ color:#A5B4FC; }
html[data-theme="dark"] .bs-subblock{ background:#0f1826; border-color:#233149; }

/* Sidebar: nested sub-nav (e.g. Balance Sheet analysis → Capital / Liability / Assets) */
.sidebar .nav-sub2{ margin:1px 0 5px 12px; padding-left:8px; border-left:2px solid rgba(255,255,255,.10); }
.sidebar .nav-sub2 a.navlink{ margin:1px 4px; font-size:.85rem; padding:7px 12px; }
.sidebar .nav-sub2 a.navlink .bi-dot{ font-size:1.15rem; margin:0 -2px 0 -4px; }

/* ---- Column filters (table.colfilter + th.cf) --------------------------- */
.cf-btn{border:0;background:transparent;color:var(--muted);cursor:pointer;padding:0 3px;margin-left:4px;font-size:.78rem;line-height:1;opacity:.5;vertical-align:middle}
.cf-btn:hover,.cf-btn.cf-on{opacity:1;color:var(--kz)}
.cf-pop{display:none;position:fixed;z-index:1085;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-lg);padding:8px;width:224px}
.cf-pop.cf-open{display:flex;gap:6px;align-items:center}
.cf-pop input{flex:1 1 auto}
html[data-theme="dark"] .cf-btn{color:var(--muted)}
html[data-theme="dark"] .cf-btn:hover,html[data-theme="dark"] .cf-btn.cf-on{color:#A5B4FC}
html[data-theme="dark"] .cf-pop{background:#1E293B;border-color:#334155}

/* "Open <portal>" link inside a sidebar dropdown — distinct from the module list */
.nav-sub .navlink-open{ color:var(--kz); font-weight:600; }
.nav-sub .navlink-open .bi{ color:var(--kz); }
html[data-theme="dark"] .nav-sub .navlink-open, html[data-theme="dark"] .nav-sub .navlink-open .bi{ color:#A5B4FC; }

/* ==========================================================================
   PRINT — shared foundation (project-wide)
   base.html already tags chrome with .no-print but nothing defined it, so it
   only worked on pages carrying their own @media print block (MIS). These
   rules give EVERY page a sane print/Save-as-PDF baseline; a page adds only
   what's specific to its own layout. Primitives:
     .no-print    hide on paper        .print-only  show only on paper
     .kz-phead    formal masthead      .kz-pfoot    report footer
   ========================================================================== */
.print-only{ display:none; }
@media print{
  @page{ size:A4 landscape; margin:11mm 9mm 13mm; }
  *{ -webkit-print-color-adjust:exact !important; print-color-adjust:exact !important; }
  /* chrome off */
  .sidebar,.topbar,.no-print,.modal,.modal-backdrop,.toast-container,.offcanvas,
  #globalConfirm,#navprogress,.skip-link{ display:none !important; }
  .content,#main,.page-wrap,main{ margin:0 !important; padding:0 !important; max-width:none !important; width:auto !important; }
  /* always print on white, even when the viewer is in dark mode */
  body,html[data-theme="dark"] body,html[data-theme="dark"]{ background:#fff !important; color:#0f172a !important; }
  body{ font-size:11px; }

  /* The dark->light repaint lives at the END of this file (see PRINT REPAINT),
     because rules declared later with equal specificity would otherwise undo it. */
  .print-only{ display:block !important; }
  /* cards flatten into bordered report blocks that don't split across pages */
  .card{ box-shadow:none !important; border:1px solid #cbd2df !important; border-radius:6px !important;
         padding:9px 11px !important; margin-bottom:9px !important; break-inside:avoid; page-break-inside:avoid; }
  .card.p-0{ padding:0 !important; }
  .card>h6.page-title,.card .page-title{ font-size:12px !important; margin-bottom:5px !important;
         break-after:avoid; page-break-after:avoid; }
  /* ⚠ EVERY month-wide grid must be released for print, not just Plan The Year's.
     `.tb-tbl .tb-m` hard-codes width:132px and out-ranks a <colgroup>, so a 15-column
     grid demands ~1,856px and simply runs off a 1,061px page — Expense Analysis was
     losing Nov to Mar off the right-hand edge of the paper. This was fixed once for
     `.pl-tbl` and the fix never generalised; here it does. */
  .tb-tbl{ table-layout:auto !important; width:100% !important; min-width:0 !important; }
  .tb-tbl col{ width:auto !important; }
  .tb-tbl .tb-m, .tb-tbl .tb-no, .tb-tbl .tb-name, .tb-tbl .tb-grp,
  .tb-tbl .pl-c1, .tb-tbl .exp-c1, .tb-tbl .inv-c1, .tb-tbl .inv-c2,
  .tb-tbl .inv-cat, .tb-tbl .inv-uom{ width:auto !important; min-width:0 !important; }
  .tb-tbl th, .tb-tbl td{ font-size:9px !important; padding:3px 3px !important; }
  .tb-tbl thead th{ white-space:nowrap; }
  /* ⚠ An <input> CLIPS its value; once the columns above are released to fit the page
     they get narrow, and "66,666.6667" printed as "66,666.". So on paper every grid
     input is replaced by its value as TEXT, which can wrap. The mirror spans are
     created and kept in step by ux.js, so this needs no markup in any template. */
  .tb-tbl input.form-control{ display:none !important; }
  .tb-tbl .pl-v{ display:block !important; font-size:9px; text-align:right;
        color:#0f172a; white-space:normal; overflow-wrap:anywhere; }

  /* scrollers must show everything on paper, and sticky columns must not float */
  .tb-scroll,.table-responsive{ overflow:visible !important; }
  [class*="-c1"],[class*="-c2"],.tb-no,.tb-name,.tb-grp,.inv-cat,.inv-uom,.exp-head,.pl-c1{
         position:static !important; box-shadow:none !important; }
  table thead{ display:table-header-group; }
  table tr{ break-inside:avoid; page-break-inside:avoid; }
  /* masthead / footer */
  .kz-phead{ border-bottom:2px solid var(--kz); padding-bottom:8px; margin-bottom:11px; }
  .kz-phead .co{ font-family:var(--font-display); font-size:19px; font-weight:700; letter-spacing:-.01em; color:#0f172a; }
  .kz-phead .ttl{ font-size:11px; font-weight:700; color:var(--kz); text-transform:uppercase; letter-spacing:.07em; margin-top:2px; }
  .kz-phead .meta{ font-size:10px; color:#475569; margin-top:4px; }
  .kz-phead .amt{ float:right; text-align:right; font-size:9.5px; color:#64748b; font-weight:600;
         text-transform:uppercase; letter-spacing:.04em; }
  .kz-pfoot{ margin-top:9px; padding-top:5px; border-top:1px solid #cbd2df; font-size:8.5px; color:#64748b;
         display:flex; justify-content:space-between; }
}

/* Plan The Year — print. The grid is made of <input>s; on paper they must read as
   plain figures, not form boxes (borders/shadows/spinners stripped, value kept). */
@media print{
  /* The grid carries inline <col style="width:120px"> (150 + 12x120 + 150 = 1740px),
     which on A4 landscape pushes Mar + Total off the paper. A stylesheet !important
     beats an inline style, so release the fixed columns and let the table fit. */
  .pl-tbl col{ width:auto !important; }
  .pl-tbl{ table-layout:auto !important; width:100% !important; min-width:0 !important; font-size:9.5px; }
  /* tight padding buys width for the month columns; the figures are what matter */
  /* nowrap on the HEADINGS only. A month cell must be free to wrap, or an extreme
     figure widens the table past the page edge and is lost at the margin. */
  .pl-tbl th,.pl-tbl td{ padding:3px 3px !important; }
  .pl-tbl thead th{ white-space:nowrap; }
  /* ⚠ .tb-tbl .tb-m hard-codes width:132px and out-ranks the colgroup — release it
     here too, or every month column demands 132px and the grid cannot fit the page. */
  .pl-tbl .tb-m{ width:auto !important; }
  .pl-tbl .pl-c1{ width:auto !important; }
  .pl-tbl thead th{ background:#eef1f7 !important; color:#334155 !important; font-size:8.5px !important; }
  .pl-tbl tfoot td,.pl-tbl tr.table-light td{ background:#e7e8fb !important; }
  .pl-tbl .pl-c1{ background:#eef1f7 !important; font-size:9px !important; }
  /* ⚠ inputs are REPLACED by their value as text, not merely restyled. An <input>
     CLIPS its content — at A4 portrait the month columns squeeze to ~34px and
     "52,00,000" (48px) simply prints cut off, which is what a client reported as
     figures "hiding". Text can wrap; an input can only hide. .pl-v is kept in step
     with its input by ux.js. */
  .pl-tbl input.form-control{ display:none !important; }
  .pl-tbl .pl-v{ display:block !important; font-size:9.5px; text-align:right; color:#0f172a;
         white-space:normal; overflow-wrap:anywhere; }
  .pl-pct{ font-size:8px !important; margin-top:1px !important; }
  /* the chart is supporting material — keep it whole, never split it. Height is
     set on the WRAPPER, not the canvas: scaling a rendered canvas with max-height
     squashes the bitmap instead of re-laying-out the plot. */
  .py-chart{ height:200px !important; break-inside:avoid; page-break-inside:avoid; }
}

/* Plan The Year — the Revenue vs Profit chart.
   ⚠ The canvas MUST take its height from this wrapper. It used to carry only a
   `height="90"` attribute, and once Chart.js had given the x-axis labels and the
   bottom legend their share there were NINE pixels left for the plot: both series
   drew as one flat line, on screen and in the printed PDF, with no readable value
   anywhere. Pair this with `maintainAspectRatio:false` — one without the other does
   nothing. */
/* ── Advisory note (`.recon-bar`) — "read this before you trust the numbers below".
   THREE TONES, because these notes are not equal:
     (base)        info      — how to read the page  (blue)
     .is-warn      warning   — the data is incomplete, the figures may mislead  (amber)
     .is-critical  critical  — the figures are wrong until you act  (red)
   ⚠ Use `_note.html`'s macro, not this markup by hand: it was hand-rolled twice and
   for a while `.recon-bar` had NO CSS AT ALL, so the one element telling a client
   their figures were incomplete rendered as ordinary body copy.
   ⚠ Every note must end with the ACTION that clears it — a link to the page to edit.
   A warning with no way out is just an apology. ── */
.recon-bar{ display:flex; gap:10px; align-items:flex-start;
  background:var(--info-bg); color:var(--info);
  border:1px solid color-mix(in srgb, var(--info) 26%, transparent);
  border-radius:var(--radius-sm); padding:10px 13px; margin:0 0 14px;
  font-size:.88rem; line-height:1.5; }
.recon-bar > i{ font-size:1.05rem; line-height:1.35; flex:0 0 auto; }
.recon-bar b{ font-weight:700; }
.recon-bar a{ color:inherit; text-decoration:underline; text-underline-offset:2px; }
.recon-bar.is-warn{ background:var(--warn-bg); color:var(--warn);
  border-color:color-mix(in srgb, var(--warn) 26%, transparent); }
.recon-bar.is-critical{ background:var(--bad-bg); color:var(--bad);
  border-color:color-mix(in srgb, var(--bad) 30%, transparent); }
html[data-theme="dark"] .recon-bar{ background:#152738; color:#8fc4f2; border-color:#28496b; }
html[data-theme="dark"] .recon-bar.is-warn{ background:#3a2f12; color:#f0c869; border-color:#5c4a1c; }
html[data-theme="dark"] .recon-bar.is-critical{ background:#3a1c1a; color:#f3a09a; border-color:#6b2f2a; }
@media print{
  /* mono-safe: tone carries no meaning on paper, so it reads as one bordered band */
  .recon-bar, .recon-bar.is-warn, .recon-bar.is-critical{
    background:#fff !important; color:#0f172a !important;
    border:1px solid #94a3b8 !important; font-size:9px; padding:6px 8px; }
}

.pl-v{ display:none; }        /* print-only mirror of a Plan-the-Year input */
.py-chart{ position:relative; height:260px; }
.py-chart .py-chart-off{ display:none; }
.py-chart.is-off canvas{ display:none; }
.py-chart.is-off .py-chart-off{ display:block; }

/* Shareholding analysis — a 100% ownership bar + control facts. Deliberately a
   different shape from the category donut above it on the same page. */
.sh-head{ display:flex; justify-content:space-between; align-items:flex-end; gap:12px; }
.sh-head .k{ font-size:.72rem; text-transform:uppercase; letter-spacing:.05em; color:var(--muted); font-weight:700; }
.sh-head .v{ font-size:1.15rem; font-weight:700; letter-spacing:-.01em; overflow-wrap:anywhere; }
.sh-bar{ margin-top:10px; height:34px; }
.sh-seg{ font-size:.78rem; }
.sh-facts{ display:grid; grid-template-columns:repeat(auto-fit,minmax(116px,1fr)); gap:9px; margin-top:14px; }
.sh-fact{ border:1px solid var(--border); border-radius:var(--radius-sm); padding:9px 11px; min-width:0; }
.sh-fact .k{ font-size:.68rem; text-transform:uppercase; letter-spacing:.05em; color:var(--muted); font-weight:700; }
.sh-fact .v{ font-weight:700; font-size:.94rem; margin-top:2px; overflow-wrap:anywhere; }
.sh-fact .d{ font-size:.74rem; color:var(--muted); margin-top:2px; }
.sh-bg-1{ background:var(--sh-1); } .sh-bg-2{ background:var(--sh-2); } .sh-bg-3{ background:var(--sh-3); }
.sh-bg-4{ background:var(--sh-4); } .sh-bg-5{ background:var(--sh-5); } .sh-bg-6{ background:var(--sh-6); }
.sh-bg-7{ background:var(--sh-7); } .sh-bg-8{ background:var(--sh-8); }
html[data-theme="dark"] .sh-fact{ border-color:#334155; }
html[data-theme="dark"] .sh-bar{ border-color:#334155; }

/* Finance Overview — business-health strip, books health, and a roomier
   shareholding card (2026-07-20 dashboard audit). */
.fh-health{ display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:10px; }
.fh-hgrp{ border:1px solid var(--border); border-radius:var(--radius-sm); padding:11px 13px;
  text-decoration:none; color:inherit; display:block; transition:border-color .15s, box-shadow .15s, transform .15s; }
.fh-hgrp .t{ font-size:.72rem; text-transform:uppercase; letter-spacing:.05em; color:var(--muted);
  font-weight:700; margin-bottom:7px; display:flex; align-items:center; gap:6px; }
.fh-hgrp .t .bi{ color:var(--kz); }
.fh-hgrp .r{ display:flex; justify-content:space-between; gap:10px; font-size:.84rem; padding:3px 0; }
.fh-hgrp .r span{ color:var(--muted); }
.fh-hgrp .r b{ font-variant-numeric:tabular-nums; white-space:nowrap; }
@media (hover:hover){ .fh-hgrp:hover{ border-color:var(--kz); box-shadow:var(--shadow-sm); transform:translateY(-1px); } }
.fh-note{ margin-top:11px; font-size:.8rem; color:var(--muted); background:var(--mut-bg);
  border-radius:var(--radius-sm); padding:8px 11px; }
.fh-books{ display:flex; flex-direction:column; gap:7px; }
.fh-brow{ display:flex; align-items:center; justify-content:space-between; gap:10px; }
.fh-pill{ display:inline-flex; align-items:center; gap:7px; font-size:.82rem; font-weight:600; }
.fh-pill.ok{ color:var(--ok); } .fh-pill.warn{ color:var(--warn); } .fh-pill.bad{ color:var(--bad); }
.fh-budget{ margin-top:13px; padding-top:12px; border-top:1px solid var(--border); }
.sh-card .sh-bar{ height:40px; }
.sh-card .sh-head .v{ font-size:1.35rem; }
html[data-theme="dark"] .fh-hgrp{ border-color:#334155; }
html[data-theme="dark"] .fh-note{ background:#1a2436; }
html[data-theme="dark"] .fh-budget{ border-top-color:#334155; }

/* ===== Ratio charts (.rchart) — the analysis cards, as 12-month trends ==========
   Replaces the single annual number on every finance analysis card. Deliberately
   inline SVG, not Chart.js, for two structural reasons:
     - base.html's theme toggle is a closure that dispatches NO event, so a <canvas>
       could never follow a light/dark switch without new plumbing. SVG inherits the
       custom properties below and re-themes for free, with zero JavaScript.
     - the print foundation forces a white page but does NOT reset the dark-mode
       custom properties, so a dark-configured canvas rasterises white-on-white on A4.
   There is also no axis-tick layer by design: every number near a chart is rendered
   through |inr in Jinja, so Chart.js's western-grouping trap cannot recur.
   Geometry (and gap handling) comes from _fin_chart() server-side. */
.rchart{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.rc-l{ font-size:.72rem; font-weight:600; letter-spacing:.02em; text-transform:uppercase;
  color:var(--muted); display:flex; align-items:center; gap:5px; }
.rc-l .bi{ font-size:.78rem; }
.rc-svg{ display:block; width:100%; height:auto; overflow:visible; margin-top:4px; }
.rc-grid{ stroke:var(--border); stroke-width:1; }
.rc-zero{ stroke:var(--muted); stroke-width:1; stroke-dasharray:3 3; }
.rc-area{ fill:var(--kz); fill-opacity:.07; stroke:none; }
.rc-path{ fill:none; stroke:var(--kz); stroke-width:2; stroke-linejoin:round;
  stroke-linecap:round; vector-effect:non-scaling-stroke; }
.rc-dot{ fill:var(--card,#fff); stroke:var(--kz); stroke-width:1.5;
  vector-effect:non-scaling-stroke; cursor:help; }
.rc-dot.is-last{ fill:var(--kz); }
.rc-hit{ fill:transparent; cursor:help; }         /* wider hover target than the dot */
.rc-x{ display:flex; gap:1px; margin-top:3px; }
.rc-x span{ flex:1; text-align:center; font-size:.58rem; color:var(--muted);
  font-variant-numeric:tabular-nums; }
.rc-x span.on{ color:var(--kz); font-weight:700; }
/* the value, printed under the last point marker */
.rc-foot{ margin-top:5px; font-size:.78rem; color:var(--muted);
  font-variant-numeric:tabular-nums; }
.rc-foot b{ color:var(--ink); font-size:1.02rem; font-weight:700; }
.rc-foot .rc-mo{ font-size:.7rem; text-transform:uppercase; letter-spacing:.03em; }
.rc-note{ font-size:.68rem; color:var(--muted); margin-top:2px; }
.rc-empty{ font-size:.78rem; color:var(--muted); padding:14px 0 10px; text-align:center; }
/* accent variant, so a page can distinguish a second series */
.rchart.alt .rc-path{ stroke:var(--accent); }
.rchart.alt .rc-area{ fill:var(--accent); }
.rchart.alt .rc-dot{ stroke:var(--accent); }
.rchart.alt .rc-dot.is-last{ fill:var(--accent); }
.rchart.good .rc-path,.rchart.good .rc-dot{ stroke:var(--ok); }
.rchart.good .rc-area{ fill:var(--ok); } .rchart.good .rc-dot.is-last{ fill:var(--ok); }
.rchart.bad .rc-path,.rchart.bad .rc-dot{ stroke:var(--bad); }
.rchart.bad .rc-area{ fill:var(--bad); } .rchart.bad .rc-dot.is-last{ fill:var(--bad); }
html[data-theme="dark"] .rc-grid{ stroke:#2a3852; }
html[data-theme="dark"] .rc-zero{ stroke:#4a5a72; }
html[data-theme="dark"] .rc-path{ stroke:#A5B4FC; }
html[data-theme="dark"] .rc-area{ fill:#A5B4FC; fill-opacity:.10; }
html[data-theme="dark"] .rc-dot{ fill:#1E293B; stroke:#A5B4FC; }
html[data-theme="dark"] .rc-dot.is-last{ fill:#A5B4FC; }
html[data-theme="dark"] .rc-x span.on{ color:#A5B4FC; }
html[data-theme="dark"] .rc-foot b{ color:#E2E8F0; }
html[data-theme="dark"] .rchart.alt .rc-path,html[data-theme="dark"] .rchart.alt .rc-dot{ stroke:#4FB6E8; }
html[data-theme="dark"] .rchart.alt .rc-area,html[data-theme="dark"] .rchart.alt .rc-dot.is-last{ fill:#4FB6E8; }
/* Two-series card (.rc2) — Net sales (indigo) + Net profit (green) on one axis */
.rc2 .rc-path.rc-a,.rc2 .rc-dot.rc-a{ stroke:var(--kz); }
.rc2 .rc-dot.rc-a.is-last{ fill:var(--kz); }
.rc2 .rc-path.rc-b,.rc2 .rc-dot.rc-b{ stroke:var(--ok); }
.rc2 .rc-dot.rc-b.is-last{ fill:var(--ok); }
.rc2-legend{ display:flex; flex-wrap:wrap; gap:4px 16px; margin:2px 0 1px; }
.rc2-lg{ display:inline-flex; align-items:center; gap:6px; font-size:.72rem; color:var(--muted);
  letter-spacing:.01em; }
.rc2-lg > i{ width:16px; height:3px; border-radius:2px; display:inline-block; flex:0 0 auto; }
.rc2-lg.rc-a > i{ background:var(--kz); } .rc2-lg.rc-b > i{ background:var(--ok); }
.rc2-lg b{ color:var(--ink); font-weight:700; font-family:var(--font-mono); font-variant-numeric:tabular-nums; }
.rc2-lg b.is-neg{ color:var(--bad); }
html[data-theme="dark"] .rc2 .rc-path.rc-a,html[data-theme="dark"] .rc2 .rc-dot.rc-a{ stroke:#A5B4FC; }
html[data-theme="dark"] .rc2 .rc-dot.rc-a.is-last{ fill:#A5B4FC; }
html[data-theme="dark"] .rc2 .rc-path.rc-b,html[data-theme="dark"] .rc2 .rc-dot.rc-b{ stroke:#34D399; }
html[data-theme="dark"] .rc2 .rc-dot.rc-b.is-last{ fill:#34D399; }
html[data-theme="dark"] .rc2-lg.rc-a > i{ background:#A5B4FC; } html[data-theme="dark"] .rc2-lg.rc-b > i{ background:#34D399; }
html[data-theme="dark"] .rc2-lg b{ color:#E2E8F0; }
html[data-theme="dark"] .rc2-lg b.is-neg{ color:#F87171; }
@media (max-width:420px){ .rc-x span{ font-size:.52rem; } .rc-l{ font-size:.66rem; } }
/* on paper: pin the colours light — the print foundation whitens the page but does
   not reset html[data-theme="dark"]'s custom properties. */
@media print{
  .rc-path{ stroke:#4F46E5 !important; } .rc-dot{ fill:#fff !important; stroke:#4F46E5 !important; }
  .rc-dot.is-last{ fill:#4F46E5 !important; }
  .rc-area{ fill:#4F46E5 !important; fill-opacity:.08 !important; }
  /* keep the second series green on paper too, or the two lines merge */
  .rc2 .rc-path.rc-b,.rc2 .rc-dot.rc-b{ stroke:#157a5b !important; }
  .rc2 .rc-dot.rc-b.is-last{ fill:#157a5b !important; }
  .rc2-lg.rc-a > i{ background:#4F46E5 !important; } .rc2-lg.rc-b > i{ background:#157a5b !important; }
  .rc2-lg,.rc2-lg b{ color:#475569 !important; } .rc2-lg b{ color:#0f172a !important; } .rc2-lg b.is-neg{ color:#c4362f !important; }
  .rc-grid{ stroke:#cbd2df !important; } .rc-zero{ stroke:#94a3b8 !important; }
  .rc-l,.rc-x span,.rc-foot,.rc-note{ color:#475569 !important; }
  .rc-foot b{ color:#0f172a !important; }
  .rchart{ break-inside:avoid; page-break-inside:avoid; }
}

/* ===== Flippable ratio cards ==================================================
   Front = the chart. Back = what the ratio means. Flipped by a real <input
   type=checkbox> so this needs NO JavaScript and stays keyboard-operable — the
   whole finance chart layer ships without JS and this keeps that true.

   Geometry note: the FRONT sits in normal flow and therefore defines the card's
   height; only the BACK is absolutely positioned over it. A common flip
   implementation absolutely positions both faces, which forces a fixed pixel
   height — unworkable here because these cards measure anywhere from 165px at
   375px wide to 253px on a laptop. Letting the front set the height means the
   flip is responsive for free, and Bootstrap's h-100 still equalises the row. */
.rcard{ position:relative; }
.rc-flip{ position:absolute; opacity:0; width:1px; height:1px; margin:0;
  pointer-events:none; }                       /* focusable, not visible */
.rcard-inner{ position:relative; transition:transform .55s cubic-bezier(.2,.7,.3,1);
  transform-style:preserve-3d; }
.rcard-front,.rcard-back{ backface-visibility:hidden; -webkit-backface-visibility:hidden; }
.rcard-back{ position:absolute; inset:0; transform:rotateY(180deg);
  display:flex; flex-direction:column; gap:6px; overflow:auto; }
.rc-flip:checked ~ .rcard-inner{ transform:rotateY(180deg); }
.rc-back-t{ font-size:.72rem; font-weight:600; letter-spacing:.02em;
  text-transform:uppercase; color:var(--muted); display:flex; align-items:center; gap:5px; }
.rc-back-d{ font-size:.82rem; color:var(--ink); line-height:1.45; }
/* the flip control sits OUTSIDE .rcard-inner, so it never rotates away and one
   button serves both faces */
.rc-flipbtn{ position:absolute; top:0; right:0; z-index:2; cursor:pointer;
  width:26px; height:26px; display:inline-flex; align-items:center; justify-content:center;
  border-radius:50%; color:var(--muted); font-size:.92rem; margin:0;
  transition:background .15s, color .15s; }
.rc-flipbtn:hover{ background:var(--mut-bg); color:var(--kz); }
.rc-flip:focus-visible ~ .rc-flipbtn{ outline:2px solid var(--kz); outline-offset:2px; }
.rc-flip:checked ~ .rc-flipbtn{ color:var(--kz); }
.rc-flip:checked ~ .rc-flipbtn .bi::before{ content:"\f129"; }   /* ⓘ → ✕-ish (arrow-left) */
html[data-theme="dark"] .rc-back-d{ color:#E2E8F0; }
html[data-theme="dark"] .rc-flipbtn:hover{ background:#243246; color:#A5B4FC; }
html[data-theme="dark"] .rc-flip:checked ~ .rc-flipbtn{ color:#A5B4FC; }
/* A 3D rotation is decoration. With reduced motion the faces swap instantly —
   the information is identical, only the transition goes. */
@media (prefers-reduced-motion: reduce){
  .rcard-inner{ transition:none; }
}
/* On paper the flip is undone and BOTH faces print, stacked: a printed MIS report
   must carry the explanation, and a rotated card would print its front alone. */
@media print{
  .rcard-inner{ transform:none !important; }
  .rcard-front,.rcard-back{ backface-visibility:visible !important; }
  .rcard-back{ position:static !important; transform:none !important;
    margin-top:8px; padding-top:7px; border-top:1px solid #cbd2df; overflow:visible !important; }
  .rc-back-t{ display:none; }                  /* the front already names the ratio */
  .rc-back-d{ color:#475569 !important; font-size:8.5px !important; }
  .rc-flipbtn{ display:none !important; }
}

/* ===== Month values printed under the chart (.rc-vals) ========================
   Twelve figures, one per point, so a value never needs hovering for. Shares the
   flex geometry of .rc-x above it so letters and numbers line up in one grid.
   Shortened to ₹40.2L / ₹1.36Cr by _inr_short(): '-40,20,000' needs 46.5px and
   even a two-per-row card offers only 46.2px per month. The <title> on each point
   keeps the exact rupees, and every table on the page still shows full figures. */
.rc-vals{ display:flex; gap:1px; margin-top:2px; }
.rc-vals span{ flex:1 1 0; min-width:0; text-align:center; font-size:.6rem;
  color:var(--muted); font-variant-numeric:tabular-nums; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; }
.rc-vals span.on{ color:var(--ink); font-weight:700; }
html[data-theme="dark"] .rc-vals span.on{ color:#E2E8F0; }
/* Below 576px a full-width card gives ~25.8px per month against ~27.9px needed, so
   the labels would collide. Show every second month; the rest stay on hover and the
   month letters above remain complete, so the axis is not misread. */
@media (max-width:575.98px){
  .rc-vals span:nth-of-type(even){ display:none; }
  .rc-vals span{ font-size:.58rem; }
}
/* 1200-1399px is the tightest the layout ever gets: two cards per row have just
   started, but the viewport has not caught up, leaving ~31px per month against ~33px
   needed for a label like '-108%'. Measured, not guessed — a hair smaller here rather
   than shrinking every label at every width to satisfy one narrow band. */
@media (min-width:1200px) and (max-width:1399.98px){
  .rc-vals span{ font-size:.55rem; letter-spacing:-.01em; }
}
@media print{
  .rc-vals span{ color:#475569 !important; font-size:7.5px !important; }
  .rc-vals span.on{ color:#0f172a !important; }
  .rc-vals span:nth-of-type(even){ display:block !important; }   /* paper is wide */
}

/* ===== Net Profit statement (.np-stmt) — the waterfall on .fin-stmt ===========
   Adds only what the shared primitive doesn't cover: the magnitude-bar column and
   the inline hint. Everything else (sticky heads, .grp/.sub/.tot, .pos/.neg) comes
   from .fin-stmt, which was already designed for exactly this and unused until now. */
.np-stmt{ min-width:680px; }
.np-stmt .np-barcell{ width:22%; min-width:120px; padding-top:12px; padding-bottom:12px; }
.np-stmt .np-barcell .ea-track{ margin:0; }
.np-stmt .np-barhead{ text-align:center; }
.np-stmt .ea-fill.np-loss{ background:linear-gradient(90deg,var(--bad),#e06b62); }
.np-hint{ font-size:.72rem; font-weight:400; color:var(--muted); margin-left:6px;
  text-transform:none; letter-spacing:0; }
@media (max-width:767.98px){
  /* the bar is the first thing to go when space is short — it is a nicety, the
     numbers beside it are not */
  .np-stmt .np-barcell, .np-stmt .np-barhead{ display:none; }
  .np-stmt{ min-width:520px; }
}
@media print{
  .np-stmt .np-barcell .ea-fill{ print-color-adjust:exact; -webkit-print-color-adjust:exact; }
}

/* ===== Category cell in the Sales grid ========================================
   dot · text · pencil, as a 3-column grid. The name and the unit sit in ONE text
   column so their left edges align — they used to be siblings of the dot, so the
   name began after it while the unit began at it, giving a ragged edge. The dot is
   nudged to the optical centre of the name's first line rather than its box top. */
.cat-cell{ display:grid; grid-template-columns:auto auto 1fr; align-items:start;
  column-gap:8px; }
/* In edit mode the leftmost slot (a now-pencil-less spacer) is dropped so the
   name/unit/delete controls sit flush-left in the Sales category column. */
.grid-editing .cat-cell .tb-editbtn-gap{ display:none; }
.grid-editing .cat-cell{ text-align:left; }
.cat-cell .fin-dot{ margin-top:.45em; }
.cat-txt{ min-width:0; }
.cat-name{ font-weight:700; line-height:1.3; overflow-wrap:anywhere; }
.cat-uom{ font-size:.74rem; font-weight:500; color:var(--muted); line-height:1.3;
  margin-top:1px; }
.cat-edit{ margin-top:-1px; }
/* the same dot+label pairing inline in a normal table row (Annual Analysis) */
.cat-inline{ display:inline-flex; align-items:center; gap:7px; }
html[data-theme="dark"] .cat-uom{ color:#8a97a8; }

/* ===== Month-grid density (Sales / Purchases / Inventory) =====================
   These grids carry 12 month columns plus two frozen label columns, so every pixel
   spent on padding is a month the client cannot see. Measured before this change:
   the two frozen columns ate 364px of a 1128px viewport (32%) to show "Gold" and
   "Qty (gms)", rows stood 54px tall, and the Category cell wasted 120px of vertical
   space around 37px of content. */
.inv-tbl th, .inv-tbl td{ padding:7px 10px; }
.inv-tbl .inv-uom{ font-size:.78rem; }
/* let the long "Unit Of Measurements" heading wrap instead of forcing the column
   wide enough to hold it on one line */
.inv-tbl thead .inv-c2{ white-space:normal; line-height:1.2; }
/* The dominant "empty white space" in these grids was never the column maths — it
   was ~144 rounded white pill inputs floating with gaps between them, which reads as
   a form rather than a spreadsheet. Filling the cell and flattening the chrome makes
   the grid read as a table: the borders become the grid lines, and an empty cell
   looks like an empty cell rather than a blank card. */
/* ⚠ `.tb-tbl .tb-m` hard-codes width:132px and `.tb-tbl .tb-m .form-control` sets
   text-align:center — BOTH out-rank a colgroup and a plain `input` selector, which is
   why the <col> widths appeared to be ignored. Match their specificity and come later
   in the file. Money reads right-aligned; 118px still holds "12,00,00,000". */
.inv-tbl .tb-m{ padding:0; width:118px; }
.inv-tbl .tb-m .form-control{ width:100%; max-width:none; height:38px; padding:4px 9px;
  border-radius:0; border:0; border-right:1px solid #eef1f7; background:transparent;
  box-shadow:none; text-align:right; font-size:.82rem; }
/* Interaction styling (hover tint + indigo focus ring) applies ONLY to editable cells —
   a locked amount is `readonly` until the grid's Edit button is pressed, so it must show
   NO hover/focus effect (a readonly input is still focusable; without :not([readonly]) the
   focus ring makes a locked cell look editable). */
.inv-tbl .tb-m .form-control:not([readonly]):hover{ background:#f7f9fd; }
.inv-tbl .tb-m .form-control:not([readonly]):focus{ background:#fff; box-shadow:inset 0 0 0 2px var(--kz);
  border-radius:4px; position:relative; z-index:1; }
.inv-tbl tbody tr:hover .tb-m .form-control:not([readonly]){ background:#f9fbff; }
.inv-tbl tbody tr:hover .tb-m .form-control:not([readonly]):focus{ background:#fff; }
html[data-theme="dark"] .inv-tbl .tb-m .form-control{ border-right-color:#243146; }
html[data-theme="dark"] .inv-tbl .tb-m .form-control:not([readonly]):hover,
html[data-theme="dark"] .inv-tbl tbody tr:hover .tb-m .form-control:not([readonly]){ background:#1a2436; }
html[data-theme="dark"] .inv-tbl .tb-m .form-control:not([readonly]):focus{ background:#0f1826; }

/* ⚠ FROZEN-COLUMN OFFSETS ARE PER-LAYOUT — see the finance skill. `.inv-c2` is
   pinned to the Inventory/Purchases first-column width; a grid with a different
   first column MUST declare its own, or the frozen column slides over its
   neighbour once the grid is scrolled. Both now match a 152px first column. */
.inv-tbl .inv-c2, .inv-tbl .inv-uom{ left:152px; }
.sal-tbl .inv-c2, .sal-tbl .inv-uom{ left:152px; }

/* ===== Fully centred analysis tables =========================================
   Sales "Annual Analysis" and Gross Profit "Category-wise" read as comparison
   grids rather than ledgers, so every column is centred — heading AND cell. The
   .num right-alignment and Bootstrap's .text-center both carry weight, so this
   has to beat them; hence the !important, scoped to the opt-in class only. */
.tbl-centre th, .tbl-centre td{ text-align:center !important; }
.tbl-centre tfoot td{ text-align:center !important; }

/* Net Profit statement: every column centred EXCEPT the magnitude bar, which is a
   fixed-width track and must stay flush so the bars share one baseline. */
.np-stmt th, .np-stmt td{ text-align:center !important; }
.np-stmt th.np-barhead, .np-stmt td.np-barcell{ text-align:left !important; }

/* ===== Cost of Goods Sold statement (.cogs-tbl) ==============================
   A read-only month grid, so its cells hold text rather than inputs — the shared
   .inv-tbl input rules don't apply and the numbers need their own right-alignment.
   Its frozen-column offset matches the 152px first column, per the per-layout rule. */
.cogs-tbl .inv-c2, .cogs-tbl .inv-uom{ left:152px; }
.cogs-tbl .cogs-v{ text-align:left; font-variant-numeric:tabular-nums; }
.cogs-tbl .tb-m{ text-align:left; }
.cogs-tbl tbody tr.cogs-row td{ background:#f7f9fd; border-top:1px solid #e3e8f2; }
.cogs-tbl tbody tr.cogs-row .inv-uom{ background:#f7f9fd; }
/* a month with no closing stock recorded — the input that makes the formula misread */
.cogs-tbl .cogs-gap{ background:#fffbf0; }
.cogs-tbl thead th.cogs-gap{ background:#fdf3e0; color:var(--warn); }
html[data-theme="dark"] .cogs-tbl tbody tr.cogs-row td,
html[data-theme="dark"] .cogs-tbl tbody tr.cogs-row .inv-uom{ background:#16202f; border-top-color:#2a3852; }
html[data-theme="dark"] .cogs-tbl .cogs-gap{ background:#221c10; }
html[data-theme="dark"] .cogs-tbl thead th.cogs-gap{ background:#2e2410; color:#e0b862; }

/* ===== Business Growth Plan · Home Work · Score Card ===================== */
/* Week button grid */
.gw-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(58px,1fr)); gap:8px; }
.gw-week{ position:relative; display:flex; flex-direction:column; align-items:center; justify-content:center;
  min-height:52px; border:1.5px solid var(--border); border-radius:var(--radius-sm); text-decoration:none;
  color:var(--ink); background:#fff; font-weight:700; transition:transform .08s, box-shadow .12s, border-color .12s; }
.gw-week .n{ font-size:1.05rem; line-height:1; }
.gw-week:hover{ transform:translateY(-1px); box-shadow:var(--shadow); border-color:var(--kz); color:var(--kz); }
.gw-week.current{ border-color:var(--kz); background:linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent)); color:#fff; box-shadow:var(--shadow); }
.gw-week.current:hover{ color:#fff; }
.gw-week.sel{ outline:3px solid rgba(79,70,229,.28); outline-offset:1px; }
.gw-week.complete{ border-color:var(--ok); }
.gw-week.locked{ background:var(--mut-bg); color:var(--muted); border-style:dashed; cursor:not-allowed; font-weight:600; }
.gw-week.locked .bi{ font-size:.72rem; margin-top:2px; opacity:.7; }
.gw-dot{ width:7px; height:7px; border-radius:50%; background:var(--muted); margin-top:4px; }
.gw-dot.ok{ background:var(--ok); }
.gw-week.current .gw-dot{ background:#fff; }
.gw-key{ display:inline-block; width:11px; height:11px; border-radius:3px; vertical-align:-1px; border:1.5px solid var(--border); background:#fff; }
.gw-key.cur{ background:var(--kz); border-color:var(--kz); }
.gw-key.lock{ background:var(--mut-bg); border-style:dashed; }
/* Weekly message + read-only task list */
.gw-msg{ white-space:pre-wrap; background:var(--mut-bg); border:1px solid var(--border); border-radius:var(--radius-sm);
  padding:12px 14px; line-height:1.6; margin-bottom:12px; }
.gw-label{ font-size:.78rem; font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:.04em; margin:4px 0 6px; }
.gw-tasks{ list-style:none; padding:0; margin:0; }
.gw-tasks li{ display:flex; gap:9px; padding:7px 0; border-bottom:1px solid var(--border); }
.gw-tasks li:last-child{ border-bottom:0; }
.gw-tasks li .bi{ color:var(--muted); margin-top:2px; }
.gw-tasks li.done .bi{ color:var(--ok); }
.gw-tasks li .t{ font-weight:600; }
.gw-tasks li.done .t{ text-decoration:line-through; color:var(--muted); }
.gw-tasks li .d{ font-size:.85rem; color:var(--muted); }
/* Home Work rows */
/* Home Work task row — horizontal: task on the left, the work textarea in the middle,
   Save button on the right. Stacks on mobile. (owner ask 2026-08-10) */
.hw-row{ display:flex; align-items:center; gap:14px; border:1px solid var(--border);
  border-radius:var(--radius-sm); padding:10px 12px; margin-bottom:9px; }
.hw-row.done{ border-color:var(--ok); background:var(--ok-bg); }
.hw-task{ flex:0 0 34%; min-width:0; }
.hw-check{ display:flex; align-items:flex-start; gap:8px; font-weight:600; cursor:pointer; margin:0; }
.hw-check input{ width:17px; height:17px; margin-top:2px; flex:0 0 auto; }
.hw-detail{ font-size:.85rem; color:var(--muted); margin:3px 0 0 25px; }
.hw-ta{ flex:1 1 auto; min-width:0; align-self:stretch; }
.hw-save{ flex:0 0 auto; align-self:center; white-space:nowrap; }
/* week heading = collapse toggle; caret rotates when the week is open */
.hw-wtoggle{ cursor:pointer; }
.hw-caret{ font-size:.8rem; transition:transform .18s ease; display:inline-block; }
.hw-wtoggle[aria-expanded="true"] .hw-caret{ transform:rotate(90deg); }
@media (max-width:640px){
  .hw-row{ flex-direction:column; align-items:stretch; gap:8px; }
  .hw-task{ flex:none; }
  .hw-save{ align-self:flex-end; }
}
/* Admin authoring */
.gw-adm-list{ display:flex; flex-direction:column; max-height:70vh; overflow:auto; }
.gw-adm-week{ display:flex; align-items:center; gap:8px; padding:8px 10px; border-radius:var(--radius-sm);
  text-decoration:none; color:var(--fg); font-weight:600; }
/* ⚠ was color:var(--ink) — a FIXED light-mode ink (§2a), so every unselected week read
   near-black on the dark card (owner-reported 2026-09-11). --fg flips with the theme. */
.gw-adm-week:hover{ background:var(--mut-bg); }
.gw-adm-week.sel{ background:linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent)); color:#fff; }
.gw-adm-week.sel .badge{ background:rgba(255,255,255,.25)!important; }
.gw-adm-task{ display:flex; gap:6px; align-items:center; margin-bottom:6px; }
.gw-adm-edit{ display:flex; gap:6px; align-items:center; flex:1; }
.gw-adm-task .form-control{ flex:1; }
html[data-theme="dark"] .gw-week{ background:#1E293B; color:#E2E8F0; border-color:#334155; }
html[data-theme="dark"] .gw-week:hover{ color:#A5B4FC; border-color:#A5B4FC; }
html[data-theme="dark"] .gw-week.locked{ background:#141d2b; color:#8a97a8; }
html[data-theme="dark"] .gw-key{ background:#1E293B; border-color:#334155; }
html[data-theme="dark"] .gw-msg{ background:#141d2b; border-color:#334155; }

/* ===== Coverflow carousel (Business Growth Plan week cards) =============
   Cards carry the whole week (message + tasks) and use the SYSTEM theme:
   a surface card (like every other card) with a brand-gradient header. */
.cf-wrap{ display:flex; align-items:center; gap:6px; margin:6px 0 2px; }
.cf-stage{ flex:1; position:relative; height:590px; perspective:1900px; overflow:hidden; outline:none; touch-action:pan-y; }
.cf-track{ position:absolute; inset:0; transform-style:preserve-3d; }
/* Card = 650×550 (owner ask 2026-08-10). Centred via negative margins = −½ dimensions,
   so any width/height override MUST update the margin to match, or it drifts off-centre. */
.cf-card{ position:absolute; left:50%; top:50%; width:650px; height:550px; margin:-275px 0 0 -325px;
  border:2px solid var(--border); border-radius:var(--radius-lg); overflow:hidden; cursor:pointer;
  background:#fff; color:var(--ink); display:flex; flex-direction:column; will-change:transform,opacity;
  box-shadow:0 22px 52px -18px rgba(15,23,42,.48);
  transition:transform .5s cubic-bezier(.2,.75,.2,1), opacity .5s; backface-visibility:hidden; }
.cf-card.cf-active{ cursor:default; border-color:var(--kz); box-shadow:0 38px 80px -22px rgba(15,23,42,.58); }
/* header — brand gradient, identical across cards (the system's signature) */
.cf-head{ position:relative; flex:0 0 auto; padding:22px 26px 20px; color:#fff;
  background:linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent)); }
.cf-card.locked .cf-head{ background:linear-gradient(120deg,#64748b,#475569); }
.cf-card.complete .cf-head{ background:linear-gradient(120deg,#0f9d63,#157a5b 60%,#0d9488); }
.cf-eyebrow{ display:block; font-size:.7rem; font-weight:700; letter-spacing:.14em; text-transform:uppercase; opacity:.85; }
.cf-title{ display:block; margin-top:5px; font-family:var(--font-display); font-size:1.6rem;
  font-weight:700; line-height:1.15; letter-spacing:-.02em; text-wrap:balance; padding-right:52px; }
.cf-ring{ position:absolute; top:17px; right:18px; width:38px; height:38px; border-radius:50%;
  background:conic-gradient(#fff calc(var(--p,0)*1%), rgba(255,255,255,.28) 0);
  -webkit-mask:radial-gradient(closest-side, transparent 63%, #000 65%); mask:radial-gradient(closest-side, transparent 63%, #000 65%); }
/* body — surface, holds message + tasks */
.cf-body{ flex:1 1 auto; overflow-y:auto; padding:16px 19px 18px; display:flex; flex-direction:column; }
.cf-msg{ white-space:pre-wrap; font-size:.95rem; line-height:1.62; margin:0 0 15px; color:var(--ink);
  padding-left:13px; border-left:3px solid var(--kz); }
.cf-tlabel{ font-size:.68rem; font-weight:700; color:var(--muted); text-transform:uppercase; letter-spacing:.1em; margin:0 0 4px; }
.cf-tasks{ list-style:none; padding:0; margin:0 0 14px; }
.cf-tasks li{ position:relative; padding:8px 0 8px 22px; border-bottom:1px solid var(--border); }
.cf-tasks li:last-child{ border-bottom:0; }
.cf-tasks li::before{ content:"\203A"; position:absolute; left:3px; top:6px; font-weight:700;
  font-size:1.05rem; line-height:1; color:var(--kz); }        /* › pointer */
.cf-tasks li.done::before{ content:"\2713"; color:var(--ok); font-size:.92rem; top:8px; left:1px; }  /* ✓ */
.cf-tasks li .t{ display:block; font-weight:600; font-size:.94rem; line-height:1.4; color:var(--ink); }
.cf-tasks li.done .t{ text-decoration:line-through; text-decoration-thickness:1px; color:var(--muted); }
.cf-tasks li .d{ display:block; font-size:.82rem; color:var(--muted); margin-top:2px; line-height:1.45; }
/* Week material on a growth card — a download is an ACTION, so each row is a full-width
   hit target with the size on the right, rather than a bare inline link in prose. */
.cf-files{ list-style:none; padding:0; margin:0 0 14px; }
.cf-files li{ margin:0 0 6px; }
.cf-files li:last-child{ margin-bottom:0; }
.cf-files a{ display:flex; align-items:center; gap:8px; padding:8px 10px; border:1px solid var(--border);
  border-radius:10px; background:#fff; color:var(--ink); text-decoration:none; font-size:.88rem;
  transition:border-color .15s, background .15s, transform .08s; }
.cf-files a:hover{ border-color:var(--kz); background:rgba(79,70,229,.05); transform:translateY(-1px); }
.cf-files a:focus-visible{ outline:2px solid var(--kz); outline-offset:2px; }
.cf-files a > .bi{ color:var(--kz); flex:0 0 auto; }
.cf-files .fn{ flex:1 1 auto; font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.cf-files .fs{ flex:0 0 auto; font-size:.76rem; color:var(--muted); font-variant-numeric:tabular-nums; }
html[data-theme="dark"] .cf-files a,
[data-bs-theme="dark"] .cf-files a{ background:#1E293B; border-color:#334155; color:#E2E8F0; }
html[data-theme="dark"] .cf-files a:hover,
[data-bs-theme="dark"] .cf-files a:hover{ background:#243246; border-color:#A5B4FC; }
html[data-theme="dark"] .cf-files a > .bi,
[data-bs-theme="dark"] .cf-files a > .bi{ color:#A5B4FC; }

.cf-cta{ margin-top:auto; align-self:flex-start; }
.cf-cta-row{ margin-top:auto; display:flex; gap:8px; flex-wrap:wrap; }
.cf-cta-row .cf-cta{ margin-top:0; }
/* Week jump strip (Business Growth Plan / Work Log) — Week 1…38 pills */
.gw-weekstrip{ display:flex; gap:6px; overflow-x:auto; padding:6px 2px 10px; margin-bottom:4px; scrollbar-width:thin; scroll-behavior:smooth; }
.gw-wpill{ flex:0 0 auto; min-width:38px; height:38px; padding:0 8px; border:1px solid var(--border); border-radius:11px;
  background:#fff; color:var(--ink); font-weight:700; font-size:.86rem; cursor:pointer; display:inline-flex; align-items:center;
  justify-content:center; gap:3px; text-decoration:none; transition:transform .08s, border-color .15s, background .15s, color .15s; }
.gw-wpill .wn{ line-height:1; }
.gw-wpill .bi{ font-size:.72rem; opacity:.85; }
.gw-wpill:hover{ border-color:var(--kz2); color:var(--kz); transform:translateY(-1px); text-decoration:none; }
.gw-wpill.on{ background:linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent)); border-color:transparent; color:#fff; box-shadow:var(--shadow-sm); }
.gw-wpill.current:not(.on){ border-color:var(--kz); color:var(--kz); }
.gw-wpill.done:not(.on){ border-color:var(--ok); color:var(--ok); }
.gw-wpill.locked{ background:var(--mut-bg); color:var(--muted); border-style:dashed; cursor:not-allowed; }
.gw-wpill.locked:hover{ transform:none; border-color:var(--border); color:var(--muted); }
.wl-text{ white-space:pre-wrap; font-size:.9rem; color:var(--ink); line-height:1.55; }
html[data-theme="dark"] .gw-wpill{ background:#1E293B; color:#E2E8F0; border-color:#334155; }
html[data-theme="dark"] .gw-wpill.locked{ background:#141d2b; color:#8a97a8; }
html[data-theme="dark"] .gw-wpill.current:not(.on){ border-color:#A5B4FC; color:#A5B4FC; }
html[data-theme="dark"] .wl-text{ color:#E2E8F0; }
.cf-note{ display:flex; gap:12px; align-items:flex-start; margin:auto 0; }
.cf-note>.bi{ font-size:1.5rem; color:var(--kz); margin-top:1px; }
.cf-note b{ display:block; color:var(--ink); }
.cf-note span{ display:block; font-size:.86rem; color:var(--muted); margin-top:2px; }
.cf-nav{ flex:0 0 auto; width:42px; height:42px; border-radius:50%; border:1px solid var(--border);
  background:#fff; color:var(--kz); display:flex; align-items:center; justify-content:center; font-size:1.15rem;
  cursor:pointer; box-shadow:var(--shadow); z-index:60; transition:background .15s,color .15s,transform .08s; }
.cf-nav:hover{ background:var(--kz); color:#fff; }
.cf-nav:active{ transform:scale(.94); }
.cf-hint{ text-align:center; font-size:.8rem; margin:2px 0 12px; }
/* Responsive card sizes — keep the 650×550 target on large screens, scale down so the
   card always fits (margin stays −½ dimensions at every tier). (owner: all screen sizes) */
@media (max-width:991.98px){
  .cf-stage{ height:560px; perspective:1500px; }
  .cf-card{ width:500px; height:520px; margin:-260px 0 0 -250px; }
  .cf-title{ font-size:1.4rem; }
}
@media (max-width:575.98px){
  .cf-stage{ height:500px; perspective:1200px; }
  .cf-card{ width:310px; height:480px; margin:-240px 0 0 -155px; }
  .cf-head{ padding:16px 18px 14px; }
  .cf-title{ font-size:1.12rem; padding-right:44px; }
  .cf-nav{ width:36px; height:36px; }
}
@media (prefers-reduced-motion: reduce){ .cf-card{ transition:none; } }
html[data-theme="dark"] .cf-card{ background:#1E293B; color:#E2E8F0; border-color:#334155; }
html[data-theme="dark"] .cf-card.cf-active{ border-color:#A5B4FC; }
html[data-theme="dark"] .cf-tasks li{ border-bottom-color:rgba(148,163,184,.22); }
html[data-theme="dark"] .cf-msg,
html[data-theme="dark"] .cf-tasks li .t,
html[data-theme="dark"] .cf-note b,
html[data-theme="dark"] .cf-tlabel,
html[data-theme="dark"] .cf-tasks li::before{ color:#E2E8F0; }
html[data-theme="dark"] .cf-tasks li.done .t{ color:#94A3B8; }
html[data-theme="dark"] .cf-tasks li .d{ color:#94A3B8; }
html[data-theme="dark"] .cf-note span{ color:#CBD5E1; }
html[data-theme="dark"] .cf-nav{ background:#1E293B; color:#A5B4FC; border-color:#334155; }
html[data-theme="dark"] .cf-nav:hover{ background:var(--kz); color:#fff; }

/* The free-trial notice above the carousel. A trial opens every PUBLISHED week, so the page
   has to say so — under a lede promising one step a week, cards that open look like a fault.
   Toned as information, not a promotion: same brand tint as .cf-note, no gradient, no badge. */
.gp-trial{ display:flex; gap:10px; align-items:flex-start; margin:-2px 0 14px;
  padding:11px 14px; border:1px solid color-mix(in srgb, var(--kz) 34%, transparent);
  border-radius:12px; background:color-mix(in srgb, var(--kz) 8%, transparent);
  color:var(--muted); font-size:.9rem; line-height:1.5; }
.gp-trial>.bi{ color:var(--kz); font-size:1.02rem; margin-top:1px; flex:none; }
.gp-trial b{ color:var(--ink); }
html[data-theme="dark"] .gp-trial{ color:#CBD5E1;
  border-color:rgba(165,180,252,.34); background:rgba(99,102,241,.14); }
html[data-theme="dark"] .gp-trial b{ color:#E2E8F0; }
html[data-theme="dark"] .gp-trial>.bi{ color:#A5B4FC; }

/* =====================================================================================
   MIS REPORT (construction) — the CLIENT'S OWN TEMPLATE, reproduced exactly.
   Palette, type scale and page geometry lifted from `MIS_Report_Template.pdf`:
     ground #0A0E1A · text #EEF3FF · muted #93A0C0 / #B7C0DA / #8A96B6
     accents #22D3EE (cyan, section eyebrows) · #F4B740 (amber, every figure)
     A4 portrait on a 794 x 1123 px grid; H1 40px, lede 19.6px, eyebrow 15.3px.

   ⚠ DO NOT restyle this to the app's own look. It is a DARK document by design, and it
   stays dark on paper — `print-color-adjust:exact` is already set app-wide, and the
   global "print on white" rule is overridden below for `.misr` only. The owner supplied
   this format and asked for no deviation.

   Two print modes:
     · colour — as supplied
     · mono   — `.misr-mono`, for an office laser. Inverts to ink-on-white, because a
                full-bleed #0A0E1A page empties a toner cartridge and reads as a smudge.
   ================================================================================== */
.misr{ --g:#0A0E1A; --g2:#121212; --tx:#EEF3FF; --m1:#B7C0DA; --m2:#93A0C0; --m3:#8A96B6;
  --cy:#22D3EE; --am:#F4B740; --am-d:#3A2C05; --rule:rgba(183,192,218,.20);
  --sans:var(--bs-body-font-family); --disp:var(--font-display); --mono:var(--font-mono);
  color:var(--tx); }

/* one A4 sheet */
.misr-page{ background:var(--g); color:var(--tx); width:100%; max-width:794px; margin:0 auto 20px;
  min-height:1123px; padding:64px 53px 72px; position:relative; border-radius:2px;
  box-shadow:0 18px 44px -22px rgba(0,0,0,.75); overflow:hidden; }
.misr-pageno{ position:absolute; right:53px; bottom:34px; font-size:15px; font-weight:700;
  letter-spacing:.14em; color:var(--m3); }

/* section head */
.misr-eyebrow{ font-size:15.3px; font-weight:700; letter-spacing:.18em; text-transform:uppercase;
  color:var(--cy); margin-bottom:14px; }
.misr-h1{ font-family:var(--disp); font-weight:700; font-size:40px; line-height:1.08;
  letter-spacing:-.02em; margin:0 0 14px; color:var(--tx); text-transform:uppercase; }
.misr-lede{ font-size:19.6px; line-height:1.5; color:var(--m1); margin:0 0 30px; max-width:60ch; }

/* ---- cover ---- */
.misr-cover{ padding:0; display:flex; flex-direction:column; }
.misr-cband{ padding:78px 53px 62px; flex:0 0 auto;
  background:
    radial-gradient(120% 130% at 86% 0%, rgba(34,211,238,.22) 0%, transparent 58%),
    radial-gradient(100% 120% at 6% 100%, rgba(244,183,64,.14) 0%, transparent 60%),
    var(--g); }
.misr-brand{ font-size:15.3px; font-weight:700; letter-spacing:.22em; text-transform:uppercase;
  color:var(--cy); margin-bottom:64px; }
.misr-kick{ font-size:15.3px; font-weight:700; letter-spacing:.2em; text-transform:uppercase;
  color:var(--m2); margin-bottom:10px; }
.misr-title{ font-family:var(--disp); font-weight:700; font-size:56px; line-height:1.03;
  letter-spacing:-.03em; margin:0 0 22px; color:var(--tx); text-transform:uppercase; }
.misr-cdesc{ font-size:19.6px; line-height:1.55; color:var(--m1); max-width:52ch; }
.misr-toc{ list-style:none; margin:0; padding:0 53px; flex:1 1 auto; }
.misr-toc li{ display:flex; align-items:baseline; gap:20px; padding:18px 0;
  border-top:1px solid var(--rule); font-size:19.6px; color:var(--tx); }
.misr-toc li:last-child{ border-bottom:1px solid var(--rule); }
.misr-toc .n{ font-family:var(--mono); font-size:18.8px; font-weight:700; color:var(--cy); }
.misr-toc .p{ margin-left:auto; font-size:15.3px; color:var(--m3); }

/* ---- executive summary ---- */
.misr-big{ font-family:var(--mono); font-size:52px; font-weight:700; line-height:1.05;
  letter-spacing:-.02em; color:var(--am); }
.misr-big-l{ font-size:17.5px; line-height:1.45; color:var(--m2); margin-top:8px; max-width:44ch; }
.misr-narr{ font-size:19.6px; line-height:1.62; color:var(--m1); margin:26px 0 14px; max-width:64ch; }
.misr-narr b, .misr-narr .v{ color:var(--am); font-weight:700; }
.misr-driver{ font-size:17.6px; line-height:1.5; color:var(--tx); border-left:3px solid var(--cy);
  padding-left:16px; margin:0 0 28px; }
.misr-h2{ font-family:var(--disp); font-weight:700; font-size:20.9px; margin:0 0 14px; color:var(--tx); }

/* ---- tables ---- */
.misr-tbl{ width:100%; border-collapse:collapse; }
.misr-tbl thead th{ font-size:15.4px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  color:var(--m2); text-align:left; padding:0 0 12px; border-bottom:1px solid var(--rule);
  white-space:nowrap; }
.misr-tbl td{ font-size:19.6px; color:var(--m1); padding:15px 0; border-bottom:1px solid var(--rule);
  vertical-align:top; }
/* A 6-column table on a 794px page has ~120px per money column. At the body size the
   figures collided and the last column fell off the sheet, so the wider tables step down
   a size and give the columns breathing room rather than overflowing. */
.misr-tbl.misr-wide{ table-layout:fixed; }
.misr-tbl.misr-wide th, .misr-tbl.misr-wide td{ font-size:15.5px; padding:13px 8px 13px 0; }
.misr-tbl.misr-wide th:first-child, .misr-tbl.misr-wide td:first-child{ width:26%; }
.misr-tbl.misr-wide th.n, .misr-tbl.misr-wide td.n{ padding-right:0; }
.misr-tbl.misr-wide thead th{ font-size:12.6px; letter-spacing:.06em; }
.misr-tbl.misr-wide td:last-child, .misr-tbl.misr-wide th:last-child{ width:13%; text-align:left; }
.misr-tbl .n, .misr-tbl th.n{ text-align:left; font-family:var(--mono);
  font-variant-numeric:tabular-nums; white-space:nowrap; }
.misr-tbl th.n{ font-family:var(--sans); }
.misr-tbl td.n{ color:var(--am); }                 /* every figure is amber, as supplied */
.misr-tbl tbody tr:last-child td{ border-bottom:none; }
.misr-rev td, .misr-subtot td{ color:var(--tx); font-weight:700; }
.misr-rev td.n, .misr-subtot td.n{ color:var(--am); }
.misr-tot td{ color:var(--tx); font-weight:700; border-top:1px solid var(--m3);
  border-bottom:1px solid var(--m3) !important; }
.misr-tot td.n{ color:var(--am); }
.misr-na{ color:var(--m3); }                       /* em-dash: "not recorded", never nil */
.misr-tr{ font-family:var(--sans); }
.misr-tr.up{ color:#4ADE80; } .misr-tr.down{ color:#F87171; } .misr-tr.flat{ color:var(--m3); }
.misr-pill{ display:inline-block; font-size:13.2px; font-weight:700; letter-spacing:.1em;
  text-transform:uppercase; padding:5px 12px; border-radius:4px; background:var(--am-d);
  color:var(--am); }
.misr-pill.ok{ background:rgba(74,222,128,.13); color:#4ADE80; }
.misr-pill.bad{ background:rgba(248,113,113,.13); color:#F87171; }
.misr-sub2{ display:block; font-size:13.2px; color:var(--m3); margin-top:3px; }
.misr-empty{ color:var(--m3); text-align:center; padding:34px 0 !important; }

/* ---- KPI cards: 2 across, 3 down = the template's six ---- */
.misr-grid2{ display:grid; grid-template-columns:repeat(2,1fr); gap:26px 30px; }
.misr-card{ display:flex; flex-direction:column; }
.misr-card-h{ font-family:var(--disp); font-size:20px; font-weight:700; color:var(--tx);
  margin-bottom:4px; }
.misr-card-m{ font-size:13.2px; font-weight:700; letter-spacing:.12em; text-transform:uppercase;
  color:var(--cy); margin-bottom:12px; }
.misr-chart{ position:relative; height:132px; }
.misr-chart canvas{ display:block; }
.misr-card-n{ font-size:15.5px; line-height:1.45; color:var(--m2); margin-top:11px; }
.misr-card-n b{ color:var(--m1); font-weight:700; }
.misr-nodata{ height:132px; display:flex; align-items:center; justify-content:center; text-align:center;
  border:1px dashed var(--rule); border-radius:6px; font-size:15px; color:var(--m3); padding:0 18px; }
.misr-legend{ display:flex; flex-wrap:wrap; gap:5px 14px; margin-top:10px; font-size:13.2px;
  color:var(--m2); }
.misr-legend i{ width:10px; height:10px; border-radius:2px; display:inline-block; margin-right:6px; }

/* ---- qualitative ---- */
.misr-qcard{ border-top:1px solid var(--rule); padding-top:14px; }
.misr-qh{ font-size:13.2px; font-weight:700; letter-spacing:.12em; text-transform:uppercase;
  color:var(--cy); margin-bottom:8px; }
.misr-qcard p{ margin:0 0 10px; font-size:17.5px; line-height:1.5; color:var(--m1); }
.misr-qcard p:last-child{ margin-bottom:0; }
.misr-colophon{ font-size:15.5px; line-height:1.55; color:var(--m2); }
.misr-colophon b{ color:var(--m1); }

@media (max-width:840px){
  .misr-page{ min-height:0; padding:34px 22px 46px; }
  .misr-cband{ padding:40px 22px 34px; } .misr-toc{ padding:0 22px; }
  .misr-title{ font-size:38px; } .misr-h1{ font-size:29px; }
  .misr-big{ font-size:38px; } .misr-grid2{ grid-template-columns:1fr; }
  .misr-lede,.misr-narr,.misr-tbl td{ font-size:16px; }
  .misr-pageno{ right:22px; bottom:16px; }
}

/* ---- BLACK & WHITE ----
   Not a greyscale filter: a full-bleed #0A0E1A page empties a toner cartridge and reads
   as a smudge, so mono inverts to ink-on-white and drops the accents to ink. */
.misr-mono{ --g:#FFFFFF; --g2:#FFFFFF; --tx:#0A0E1A; --m1:#2A3140; --m2:#4A5262; --m3:#6B7280;
  --cy:#0A0E1A; --am:#0A0E1A; --am-d:#EDEDED; --rule:#C9CDD6; }
.misr-mono .misr-page{ box-shadow:none; border:1px solid #C9CDD6; }
.misr-mono .misr-cband{ background:#fff; border-bottom:2px solid #0A0E1A; }
.misr-mono .misr-tr.up, .misr-mono .misr-tr.down{ color:#0A0E1A; }
.misr-mono .misr-pill, .misr-mono .misr-pill.ok, .misr-mono .misr-pill.bad{
  background:#EDEDED; color:#0A0E1A; border:1px solid #C9CDD6; }

@media print{
  /* portrait, THIS document only — app.css sets landscape app-wide and a page must never
     re-declare @page; a named page is the one scopable form. */
  @page kzreport{ size:A4 portrait; margin:0; }
  .misr-page{ page:kzreport; }
  /* the app's global "print on white" + card-flattening rules must not touch the report */
  .misr, .misr-page{ background:var(--g) !important; color:var(--tx) !important; }
  .misr .card{ border:none !important; padding:0 !important; background:transparent !important; }
  .misr-page{ width:210mm; max-width:none; min-height:296mm; margin:0; border-radius:0;
    box-shadow:none; padding:17mm 14mm 19mm; break-after:page; page-break-after:always;
    break-inside:avoid; page-break-inside:avoid; }
  .misr-page:last-of-type{ break-after:auto; page-break-after:auto; }
  .misr-pageno{ right:14mm; bottom:9mm; }
  .misr-cband{ padding:22mm 14mm 16mm; }
  .misr-toc{ padding:0 14mm; }
  /* A Chart.js canvas keeps the pixel width it was drawn at — read on a wide monitor and
     printed it would hang off the paper. This does not depend on a resize event firing. */
  .misr-chart{ height:32mm; }
  .misr-chart canvas{ width:100% !important; height:100% !important; max-width:100% !important; }
  .misr-tbl thead{ display:table-header-group; }
  .misr-tbl tr{ break-inside:avoid; page-break-inside:avoid; }
  .misr-card{ break-inside:avoid; page-break-inside:avoid; }
}

/* ── Access picker (Admin Panel) ──────────────────────────────────────────────
   One control for what a user may reach. The menu is a checkbox list built per
   business, so its height varies — cap it and let it scroll rather than letting a
   client with many services push the Apply button off-screen. */
.kz-access__menu{ min-width:250px; max-height:min(60vh,340px); overflow-y:auto; }
.kz-access .dropdown-toggle{ min-width:150px; }
/* A dropdown inside .table-responsive is clipped by the scroller, so the menu has to
   escape it — Bootstrap already floats it, this just keeps it above the sticky heads. */
.kz-access .dropdown-menu{ z-index:1056; }
@media (max-width:640px){
  .kz-access .dropdown-toggle{ min-width:0; width:100%; }
  .kz-access__menu{ max-height:60vh; }
}

/* ── Document Master ──────────────────────────────────────────────────────────
   The module holds paperwork, and paperwork is READ far more often than it is
   edited. Everything below exists to make a saved document read as a record —
   not as a row of form controls, which is what it used to look like. */

/* Live search (the `data-filter-list` primitive). Hidden by class, not inline
   style, so a row's own display mode (table-row / flex / grid) survives. */
.kz-fl-hide{ display:none !important; }
.doc-search{ position:relative; max-width:420px; width:100%; }
.doc-search .form-control{ padding-left:2.1rem; }
.doc-search__ic{ position:absolute; left:.7rem; top:50%; transform:translateY(-50%);
  color:var(--fg-faint); pointer-events:none; font-size:.95rem; }
.doc-search__x{ position:absolute; right:.4rem; top:50%; transform:translateY(-50%);
  border:0; background:none; color:var(--fg-faint); line-height:1; padding:.2rem .35rem;
  border-radius:6px; }
.doc-search__x:hover{ color:var(--fg); background:var(--surface-sunken); }

/* A document as a RECORD. The name leads, everything else is quiet metadata. */
.doc-name{ font-weight:600; color:var(--fg); }
.doc-meta{ font-size:.78rem; color:var(--fg-muted); }
.doc-num{ font-family:var(--font-mono); font-size:.82rem; font-variant-numeric:tabular-nums; }
/* An empty value is stated, not left blank — a blank cell reads as a broken page.
   --fg-muted, not --fg-faint: "Not recorded" is information the reader has to be able to
   read (--fg-faint measured 3.06:1, under the AA floor). */
.doc-nil{ color:var(--fg-muted); }

/* Expiry state reuses the app's own status pill (.kz-pill .is-good/.is-warn/.is-bad/
   .is-mut) rather than a private colour set. The first version here painted
   --ok/--warn/--bad on --*-bg; those are the older badge tokens and are NOT paired for
   dark, so every pill rendered dark-on-dark (contrast 1.0–2.3) while looking perfect in
   light. .kz-pill is built on --good/--warn/--bad, which ARE dark-paired.
   Colour never carries the state alone — each pill also says what it means. */

/* Attachment chip — a document without its file is the thing worth noticing. */
.doc-file{ display:inline-flex; align-items:center; gap:.3rem; max-width:190px;
  font-size:.78rem; text-decoration:none; color:var(--kz);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.doc-file:hover{ text-decoration:underline; }

/* Per-row edit drawer: the row stays a record, editing opens beneath it. */
.doc-edit-row > td{ padding:0 !important; border-top:0 !important; }
.doc-edit-box{ background:var(--surface-sunken); padding:var(--sp-4);
  border-top:1px solid var(--hairline); }

/* Completeness meter (how much of a standard set a person actually has). */
.doc-prog{ height:6px; border-radius:999px; background:var(--surface-sunken); overflow:hidden; min-width:90px; }
.doc-prog > i{ display:block; height:100%; border-radius:999px; background:var(--kz-grad); }
.doc-prog.is-full > i{ background:var(--ok); }

/* Dashboard chart. Height on the WRAPPER + maintainAspectRatio:false — a height
   attribute on the canvas leaves Chart.js a few pixels of plot area (§9a). */
.doc-chart{ position:relative; height:230px; }
.doc-chart--tall{ height:280px; }
/* Custom HTML legend: the canvas legend is not reliably clickable under the
   page's 80% zoom (§5), so charts here render their own. */
.doc-leg{ display:flex; flex-wrap:wrap; gap:.35rem .75rem; margin-top:.5rem; }
.doc-leg button{ display:inline-flex; align-items:center; gap:.35rem; border:0; background:none;
  padding:.1rem .2rem; font-size:.76rem; color:var(--fg-muted); border-radius:6px; }
.doc-leg button:hover{ color:var(--fg); }
.doc-leg i{ width:10px; height:10px; border-radius:3px; flex:0 0 auto; }
.doc-leg .is-off{ opacity:.4; text-decoration:line-through; }

/* Section tiles on the portal home. */
.doc-tile{ display:flex; align-items:center; gap:var(--sp-3); text-decoration:none;
  color:inherit; padding:var(--sp-4); border-radius:var(--radius);
  background:var(--surface-card); border:1px solid var(--hairline); box-shadow:var(--elev-1);
  transition:transform var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease); }
@media (hover:hover){
  .doc-tile:hover{ transform:translateY(-2px); box-shadow:var(--elev-2); color:inherit; }
}
.doc-tile__n{ margin-left:auto; font-family:var(--font-mono); font-weight:700;
  font-size:1.15rem; color:var(--fg); font-variant-numeric:tabular-nums; }

/* Entrance: the dashboard settles in rather than snapping. Gated on the
   reduced-motion preference, per §7. */
@media (prefers-reduced-motion: no-preference){
  .doc-rise{ animation:docRise .38s var(--ease) both; }
  .doc-rise:nth-child(2){ animation-delay:.05s; }
  .doc-rise:nth-child(3){ animation-delay:.1s; }
  .doc-rise:nth-child(4){ animation-delay:.15s; }
  @keyframes docRise{ from{ opacity:0; transform:translateY(8px); } to{ opacity:1; transform:none; } }
}

html[data-theme="dark"] .doc-edit-box{ background:#182234; border-top-color:var(--hairline); }
html[data-theme="dark"] .doc-search__x:hover{ background:#243246; }
/* The brand indigo is a LIGHT-mode ink: #4F46E5 on the dark card surface measures ~2.4:1,
   under the AA floor. Dark mode uses the brand-on-dark tint (kizuna-ui §2). */
html[data-theme="dark"] .doc-file{ color:#A5B4FC; }

@media (max-width:640px){
  .doc-search{ max-width:none; }
  .doc-chart{ height:210px; }
}
/* ── AA contrast corrections (measured, both themes) ──────────────────────────
   Found while building Document Master, which leans on both of these heavily.
   Pre-existing and app-wide, so fixed here rather than papered over per page.
   Light mode only — the dark palette already clears AA and is left untouched.

   The measurement that finds these has to COMPOSITE a translucent background over
   its ancestor first. Reading rgba(231,178,74,.15) as opaque #E7B24A compares the
   amber text with itself and reports 1.00 — a correctly paired pill flagged as
   unreadable, and the real failures below lost in the noise. */
.kz-pill.is-good{ color:#0a7d56; }                 /* was #0E9F6E on --good-bg = 3.03:1 */
.kz-pill.is-warn{ color:#8a5e12; }                 /* was #9a6a16 on --warn-bg = 4.21:1 */
html[data-theme="dark"] .kz-pill.is-good{ color:var(--good); }
html[data-theme="dark"] .kz-pill.is-warn{ color:var(--warn); }
/* KPI tile label — the small caps line above the figure. */
.kz-kpi__l{ color:#5A6576; }                       /* was 3.06:1 */
html[data-theme="dark"] .kz-kpi__l{ color:#9caabe; }

/* ── Revenue register: seven frozen identity columns ──────────────────────────
   The construction Sales register is ~26 columns wide, so scrolling right to the
   money loses track of WHICH unit the row is. Sr. / Project / Building-Tower /
   Floor / Unit / Unit Type / Customer stay pinned (owner ask 2026-08-13).

   🔴 Each offset is the SUM of the widths before it, and those widths are declared
   inline on the <th>s in fin_re_revenue.html. Change one and you must change the
   other, or a frozen column parks on top of its neighbour (kizuna-finance §10 —
   this trap has bitten four times now).

   The bulk-select tick column is `.ge-col`: hidden in view mode, 34px wide in edit
   mode. So every offset is measured from `--revfz0`, which the edit toggle shifts —
   otherwise the whole frozen block sits 34px out of place the moment you press Edit. */
.rev-fz{ --revfz0:0px; }
.rev-fz.grid-editing{ --revfz0:34px; }
.rev-fz th.bulk-cell, .rev-fz td.bulk-cell{ position:sticky; left:0; z-index:3; background:var(--surface-card); }
.rev-fz thead th.bulk-cell{ z-index:5; background:var(--surface-sunken); }
.rev-fz .rev-fz1, .rev-fz .rev-fz2, .rev-fz .rev-fz3, .rev-fz .rev-fz4,
.rev-fz .rev-fz5, .rev-fz .rev-fz6, .rev-fz .rev-fz7{
  position:sticky; z-index:2; background:var(--surface-card);
}
.rev-fz thead .rev-fz1, .rev-fz thead .rev-fz2, .rev-fz thead .rev-fz3, .rev-fz thead .rev-fz4,
.rev-fz thead .rev-fz5, .rev-fz thead .rev-fz6, .rev-fz thead .rev-fz7{
  z-index:4; background:var(--surface-sunken);
}
/* 🔴 Each width is PINNED (width+min+max). A bare `width:` on a table cell is only a
   suggestion: measured on the real page, "Sr." had shrunk from 46px to 29 and
   "Building/Tower" had grown from 110 to 122 to fit its unbreakable header — which
   put rev-fz3 nine pixels on top of rev-fz4. Pinning all three properties, and giving
   the two long headers a space so they can wrap, makes the rendered width equal the
   declared one, which is the only thing that makes the offsets below correct. */
.rev-fz .rev-fz1{ width:46px;  min-width:46px;  max-width:46px;  left:var(--revfz0); }
.rev-fz .rev-fz2{ width:140px; min-width:140px; max-width:140px; left:calc(var(--revfz0) + 46px); }
.rev-fz .rev-fz3{ width:110px; min-width:110px; max-width:110px; left:calc(var(--revfz0) + 186px); }
.rev-fz .rev-fz4{ width:80px;  min-width:80px;  max-width:80px;  left:calc(var(--revfz0) + 296px); }
.rev-fz .rev-fz5{ width:100px; min-width:100px; max-width:100px; left:calc(var(--revfz0) + 376px); }
.rev-fz .rev-fz6{ width:100px; min-width:100px; max-width:100px; left:calc(var(--revfz0) + 476px); }
.rev-fz thead .rev-fz1, .rev-fz thead .rev-fz2, .rev-fz thead .rev-fz3, .rev-fz thead .rev-fz4,
.rev-fz thead .rev-fz5, .rev-fz thead .rev-fz6, .rev-fz thead .rev-fz7{
  white-space:normal; word-break:normal; overflow-wrap:break-word;
}
/* The last frozen column carries the edge, so the split from the scrolling money is visible.
   Widened 150 → 180px (2026-08-28) because it holds a person's NAME: nothing after it is
   pinned, so this is the one frozen width here that can change without moving an offset. */
.rev-fz .rev-fz7{ width:180px; min-width:180px; max-width:180px;
  left:calc(var(--revfz0) + 576px); box-shadow:6px 0 8px -6px rgba(15,23,42,.28); }
/* 🔴 A NAME WRAPS — IT MUST NEVER BE CUT OFF. This column is capped, so the value has to
   be allowed onto a second line. The wrapping itself now comes from `.kz-txtcell` (the
   shared `ui.txtcell` macro), which every register's text cell uses — one definition,
   so a column cannot quietly go back to clipping. Same rule as the firm switcher (§9l):
   in a list you identify a record from, an ellipsis is worse than two lines. */
html[data-theme="dark"] .rev-fz .rev-fz7{ box-shadow:6px 0 8px -6px rgba(0,0,0,.6); }

/* On a phone/tablet 756px of frozen columns would leave nothing to scroll into, so
   the freeze is released and the whole register scrolls normally. */
@media (max-width:991.98px){
  .rev-fz th.bulk-cell, .rev-fz td.bulk-cell,
  .rev-fz .rev-fz1, .rev-fz .rev-fz2, .rev-fz .rev-fz3, .rev-fz .rev-fz4,
  .rev-fz .rev-fz5, .rev-fz .rev-fz6, .rev-fz .rev-fz7{
    position:static; box-shadow:none;
  }
}
/* ── stat-card breakdown ──────────────────────────────────────────────────────
   A KPI card's combined total, with the same figure split beneath it (revenue by
   wing on the construction Sales register). Definition list, so a screen reader
   reads "A Wing — ₹2,65,00,000" as a pair rather than two loose numbers. */
.stat-break{ margin:.55rem 0 0; padding:.5rem 0 0; border-top:1px solid var(--hairline);
  display:grid; gap:.22rem; max-height:8.6rem; overflow-y:auto; }
.stat-break > div{ display:flex; align-items:baseline; justify-content:space-between; gap:.5rem; }
.stat-break dt{ font-size:.72rem; font-weight:500; color:var(--fg-muted);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* Money never wraps mid-number (kizuna-finance §10) — the label ellipses instead. */
.stat-break dd{ margin:0; font-family:var(--font-mono); font-size:.76rem; font-weight:600;
  color:var(--fg); white-space:nowrap; font-variant-numeric:tabular-nums; }
/* A card carrying a breakdown is taller; let the row equalise so the four line up. */
.stat-card.has-break{ align-items:flex-start; height:100%; }
@media (max-width:575.98px){
  .stat-break dt{ font-size:.68rem; }
  .stat-break dd{ font-size:.72rem; }
}

/* ── Credential row + copy button — `.kz-credrow` / `.kz-copy` ──────────────────────────────────
   A credential the client has to read off the screen and paste into another product.
   The row is [input][copy], and the global password-eye toggle (base.html) wraps the
   input in its own `.position-relative` at DOM-ready — so BOTH the raw control and that
   wrapper have to be the growing item, or the eye's arrival collapses the field.
   ⚠ `min-width:0` is load-bearing: a flex item defaults to `min-width:auto` and refuses
   to shrink below its content, which is how a row of fields bursts its card (§9i). */
/* 🔴 RENAMED from `.kz-field` (2026-09-02). That name is ALSO auth.css's login form
   field, and the login pages load both stylesheets — so this `display:flex` won there and
   laid every login field out as [label][input], with each input's width set by its own
   label's length. Two components, one class name: §9i. */
.kz-credrow{ display:flex; align-items:center; gap:.35rem; }
.kz-credrow > .form-control,
.kz-credrow > .position-relative{ flex:1 1 auto; min-width:0; }
.kz-credrow > .position-relative > .form-control{ width:100%; }

.kz-copy{ flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center;
  width:31px; height:31px; padding:0; border:1px solid var(--border); border-radius:8px;
  background:var(--surface-card); color:var(--fg-muted); font-size:.82rem; line-height:1;
  transition:color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease); }
.kz-copy:hover{ color:var(--kz); border-color:var(--kz); }
/* Feedback states. Bordered, not just coloured ink, so the result survives a mono print
   and a colour-blind reader (§9b print checklist). */
.kz-copy.is-ok{ color:var(--good); border-color:var(--good); }
.kz-copy.is-bad{ color:var(--bad); border-color:var(--bad); }
html[data-theme="dark"] .kz-copy{ background:var(--surface-raised); border-color:var(--hairline); }
html[data-theme="dark"] .kz-copy:hover{ color:#A5B4FC; border-color:#A5B4FC; }

/* A credential is a value to be read character by character, so it is set in the
   numerate mono face — l/1 and O/0 are otherwise a guess when someone retypes it. */
.kz-cred{ font-family:var(--font-mono); font-size:.86rem; letter-spacing:.01em; }
/* On a phone the box is ~364px and a real work email runs to 38 characters. An <input>
   CLIPS and cannot wrap (kizuna-ui §5), so the face SHRINKS — the same answer as a crore
   figure on a narrow KPI card (§8), and for the same reason: silently cutting the value
   is the one outcome that must not happen. */
@media (max-width:575.98px){
  .kz-cred{ font-size:.72rem; letter-spacing:0; }
  .kz-copy{ width:28px; height:28px; }
}

/* A whole login pasted at once changes three boxes the client is not looking at — the
   flash is what tells them it happened. Gated on reduced-motion like every other
   animation in the system. */
.kz-pasted .form-control{ border-color:var(--good); }
@media (prefers-reduced-motion: no-preference){
  .kz-pasted .form-control{ transition:border-color var(--dur) var(--ease); }
}

/* ── Admin Panel: the per-user management panel (`.ur-*`) ────────────────────────────
   Each user is a compact row that expands into a full panel. The controls used to be
   squeezed into table cells, which is why there was nowhere to put per-section access. */
.ur-detailrow > td{ border-top:0; }
.ur-detail{ background:var(--surface-sunken); border-top:1px solid var(--hairline); }
.ur-block{ background:var(--surface-card); border:1px solid var(--hairline);
  border-radius:var(--radius-sm); padding:14px 16px; }
.ur-blocktitle{ font-size:var(--fs-label); font-weight:700; letter-spacing:var(--tracking-caps);
  text-transform:uppercase; color:var(--th-fg); margin-bottom:9px; }
.ur-secgroup{ border:1px solid var(--hairline); border-radius:10px; padding:9px 11px; }
/* A service the user was not granted: its sections are shown but inert, so the client admin
   can SEE what they would get rather than the block silently disappearing. */
.ur-off{ opacity:.55; }
.ur-rolehelp{ color:var(--fg-muted); }
.ur-stats .card h4{ font-size:1.25rem; }
@media (max-width:991.98px){ .ur-stats > .col{ flex:0 0 50%; } }

/* ── Per-user access page (`.ua-*`) ──────────────────────────────────────────────────
   One card per service, its sections inside it. A service that is OFF dims rather than
   disappears: a client admin should SEE what a service contains before deciding. */
.ua-card{ transition:opacity var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.ua-card.is-off{ opacity:.62; }
.ua-card.is-off .ua-head{ opacity:1; }          /* the decision itself stays fully legible */
.ua-head .form-check-label{ font-size:.95rem; }
.ua-pill{ font-size:.7rem; font-weight:700; letter-spacing:.02em; padding:1px 8px;
  border-radius:999px; background:var(--mut-bg); color:var(--fg-muted); }
.ua-pill.is-bad{ background:var(--bad-bg); color:var(--bad); }
.ua-secs{ max-height:none; }
.ua-sec-wrap.ua-dim{ opacity:.4; }
.ua-who .avatar{ width:42px; height:42px; font-size:.95rem; }
.ua-bar{ position:sticky; top:0; z-index:3; background:var(--surface-ground);
  padding:8px 0; border-bottom:1px solid var(--hairline); }
@media (max-width:575.98px){ .ua-bar{ position:static; } }

/* The access summary in a user's Manage drawer: which services they hold, at a glance. */
.ur-svclist{ display:flex; flex-wrap:wrap; gap:5px; }
.ur-chip{ font-size:.7rem; font-weight:600; padding:2px 9px; border-radius:999px;
  background:var(--good-bg); color:var(--good); white-space:nowrap; }
.ur-chip.is-off{ background:var(--mut-bg); color:var(--fg-faint);
  text-decoration:line-through; }

/* ---- Process flowchart (BEST · Process Sheet, Phase 2) ---------------------
   The chart is GENERATED from the step table, so nothing here is authored per
   process. ⚠ Every colour is a token: the same SVG is rendered on a light page,
   a dark page and on paper, and a hard-coded ink is invisible in one of them.
   (The .svg DOWNLOAD carries its own inline <style> instead — a file has no app
   stylesheet behind it. On the page these rules win, so the two cannot drift.) */
/* ==== Process readiness (2026-09-23) ==================================================
   \U0001F534 THE BAR COUNTS SETTLED QUESTIONS, NOT QUALITY. The tool cannot know whether a
   process is *right*; it can know whether every step has an owner and a time. The label
   beside it always says "N of M", so the bar can never be read as a score for the work.
   `is-bad` is a STRUCTURAL fault (two starts, no finish) and is a different colour from a
   merely unfinished bar, because mixing the two is what makes a panel like this ignorable. */
.pr-ready{ display:flex; align-items:center; gap:8px; font-size:.84rem; }
.pr-bar{ flex:1 1 auto; min-width:56px; height:6px; border-radius:99px;
  background:color-mix(in srgb, var(--fg) 12%, transparent); overflow:hidden; }
.pr-bar__f{ display:block; height:100%; border-radius:99px; background:var(--kz);
  transition:width .25s ease; }
.pr-bar__f.is-bad{ background:var(--bad); }
.pr-issues{ list-style:none; margin:0; padding:0; font-size:.88rem; }
.pr-issues li{ padding:5px 0 5px 20px; position:relative; border-top:1px solid var(--border); }
.pr-issues li:first-child{ border-top:0; }
.pr-issues li::before{ content:""; position:absolute; left:6px; top:12px; width:6px; height:6px;
  border-radius:99px; background:var(--fg-muted); }
.pr-issues li.is-bad{ color:var(--bad); font-weight:600; padding-left:0; }
.pr-issues li.is-bad::before{ display:none; }
.pr-issues li.is-bad .ki{ width:15px; height:15px; vertical-align:-2px; margin-right:4px; }
.pr-done{ border-left:3px solid var(--ok); }
html[data-theme="dark"] .pr-bar{ background:rgba(255,255,255,.14); }

/* ==== `kz-zoom` — zoom + pan for a vector drawing (2026-09-24) =======================
   The controls FLOAT on the drawing rather than sitting in a bar above it: the chart card
   already carries Compare / Download / Print in its header, and a second toolbar would be a
   second row of chrome on a page whose whole subject is the picture. A map control sits on
   the map. */
.kz-zoom{ position:relative; }
.kz-zoom__bar{
  position:absolute; top:8px; right:10px; z-index:3;
  display:flex; align-items:center; gap:2px; padding:3px;
  border:1px solid var(--hairline); border-radius:999px;
  /* \u26a0 OPAQUE, never a blurred glass panel \u2014 `backdrop-filter` breaks under the app's
     80% desktop zoom and bleeds the scrolled drawing through as a hard band (\u00a75). */
  background:var(--surface-card); box-shadow:var(--shadow-sm); }
html[data-theme="dark"] .kz-zoom__bar{ background:var(--surface-raised);
  border-color:var(--hairline-2); }
.kz-zoom__b{
  min-width:26px; height:26px; padding:0 6px; border:0; border-radius:999px;
  background:transparent; color:var(--fg-muted); font-size:.9rem; font-weight:700;
  line-height:1; cursor:pointer; display:grid; place-content:center; }
.kz-zoom__b--w{ font-size:.72rem; font-weight:600; padding:0 9px; }
@media (hover:hover){ .kz-zoom__b:hover:not(:disabled){
  background:color-mix(in srgb, var(--kz) 13%, transparent); color:var(--kz); } }
/* \u26a0 A limit that is REACHED says so by going quiet, rather than by doing nothing when
   pressed \u2014 a control that silently no-ops is the fault this section already paid for. */
.kz-zoom__b:disabled{ opacity:.35; cursor:default; }
.kz-zoom__pct{ font-family:var(--font-mono); font-size:.72rem; font-weight:700;
  color:var(--fg); min-width:44px; text-align:center; }

.kz-zoom__stage{
  overflow:auto;
  /* \u26a0 A CAP, NOT A HEIGHT. A drawing shorter than this renders at its own size and the
     box shrinks to it, so a three-step process is not shown in a half-empty window; only a
     zoomed-in drawing gets a frame \u2014 which is also what gives panning something to pan.
     Without it, 400% is a card several thousand pixels tall with the zoom controls scrolled
     far off the top, and zooming in makes the chart HARDER to read. */
  max-height:min(78vh, 880px); }
.kz-zoom__stage:focus-visible{ outline:2px solid var(--kz); outline-offset:-2px; }
/* The grab cursor appears ONLY when there is somewhere to drag to. An affordance that is
   wrong is worse than none (\u00a75, the Account Checklist's stuck-scroll report). */
.kz-zoom__stage.is-pannable{ cursor:grab; }
.kz-zoom__stage.is-grabbing{ cursor:grabbing; user-select:none; }
.kz-zoom__stage svg{ display:block; }
.kz-zoom__hint{ margin:.35rem 0 0; font-size:.74rem; color:var(--fg-faint); text-align:center; }

/* Full screen is a READING mode: the drawing gets the whole surface and the page's own
   background comes with it, or the chart floats on the browser's default white in dark mode. */
.kz-zoom:fullscreen{ background:var(--surface-ground); padding:10px; }
.kz-zoom:fullscreen .kz-zoom__stage{ height:calc(100vh - 54px); max-height:none; }
.kz-zoom:fullscreen .kz-zoom__hint{ position:absolute; bottom:8px; left:0; right:0; }

@media (max-width:575.98px){
  /* On a phone the pill is the only way in and out, so its targets grow rather than shrink
     (\u00a79ab: a coarse pointer gets a bigger target, not a tighter one). */
  .kz-zoom__b{ min-width:32px; height:32px; }
  .kz-zoom__bar{ top:6px; right:6px; }
  .kz-zoom__b--w{ padding:0 8px; }
}

@media print{
  /* \U0001F534 ZOOM IS A SCREEN STATE. The scale is an inline width on the <svg>, so without
     this a client who had zoomed to 300% would print a chart three times the width of the
     paper and lose two thirds of it. On paper it always fits. */
  .kz-zoom__stage{ overflow:visible !important; height:auto !important;
    max-height:none !important; }
  .kz-zoom__stage svg{ width:100% !important; height:auto !important; }
}

/* ==== `.kz-verpick` — which version am I looking at (2026-09-24) ====================
   One control, two callers (the process page and the monitoring sheet), because two
   hand-rolled copies of one thing drift — they already had: different chip sizes and the
   explanation in a different place on each.
   🔴 EVERY CHIP IS THE SAME SHAPE WHATEVER ITS STATE. The selected one used to be
   `btn-kz` and the rest `btn-light border`, so the border made a 2px height difference
   between neighbours — small, and exactly what reads as "not well aligned". The border is
   always there; only its COLOUR changes. */
/* ==== The measure of a line of prose (2026-09-24) ==================================
   \U0001F534 EVERY EXPLAINER GETS A MEASURE, not only the ones inside a `.kz-note`. Measured
   on a 1920 screen before this rule: the sentence under a page title ran **209 characters**
   and the one under a card heading **239\u2013251**. 60\u201385 is the readable band; past about
   100 the eye loses the start of the next line, and a page of full-width prose reads as
   unfinished however good the words are. This one rule is most of what separates the
   section from a finished product (owner ask, 2026-09-24).
   \u26a0 36rem is the number the system ALREADY uses for a brief's body \u2014 one constant, not
   a second opinion. ~72 characters at 1rem, ~83 at the .86rem card sub-heading.
   \u26a0 A cap only constrains: a narrow column, a phone, or a page squeezed by the WhatsApp
   dock is untouched, because it was already narrower than this. */
.page-title + p,
.page-title + p.text-muted,
/* \U0001F534 a breadcrumb now sits between the title and its sentence on several pages —
   without this the cap stops matching and the 209-character line comes straight back. */
.kz-crumbs + p,
.kz-crumbs + p.text-muted{ max-width:36rem; }
/* \u26a0 A heading and its sentence are often wrapped together in a flex column \u2014 reach that
   shape too, or the rule covers only half the pages it is written for. */
.card .p-3 > .page-title + p,
.kz-gridbar + p.text-muted{ max-width:36rem; }
/* ...and the prose that explains a card or a page WITHOUT sitting under a heading. Read
   from the DOM, not guessed: these are direct children of a card body, of a card's padded
   header, or of the page wrapper itself.
   \U0001F534 DIRECT CHILDREN ONLY (`>`). A paragraph inside `.kz-brief-cols` is a
   GRANDCHILD and must stay uncapped \u2014 its column already sets the measure, and capping it
   twice is exactly the narrow-column fault reported on the Process Sheet this morning. */
.card > p.text-muted,
.card > .p-3 > p.text-muted,
main.page-wrap > p.text-muted{ max-width:36rem; }
@media print{
  /* On paper the column is already narrow; a second cap would strand the text at the left. */
  .page-title + p, .page-title + p.text-muted{ max-width:none; }
}

/* ==== `.kz-crumbs` — the way back, told apart from the page's own sentence (2026-09-25)
   Owner: the links under a page title "looking so generic and simple". They were plain
   underlined links inside the explaining paragraph, so nothing but reading it told you
   which words were navigation. A breadcrumb is a CONTROL and gets to look like one.
   \u26a0 The separator is drawn with a border, not a glyph: a sprite name that does not exist
   draws nothing at all, which is exactly how the Build tab lost its icon (2026-09-25). */
.kz-crumbs{ display:flex; align-items:center; flex-wrap:wrap; gap:.15rem .1rem;
  margin:-.1rem 0 .45rem; font-size:.85rem; line-height:1.3; }
.kz-crumbs__a{
  display:inline-flex; align-items:center; gap:.34rem; padding:.2rem .5rem;
  border-radius:8px; color:var(--fg-muted); text-decoration:none; font-weight:500;
  transition:background .12s ease, color .12s ease; }
.kz-crumbs__a .kzi{ width:.95em; height:.95em; opacity:.85; }
@media (hover:hover){ .kz-crumbs__a:hover{
  background:color-mix(in srgb, var(--kz) 12%, transparent); color:var(--kz); } }
.kz-crumbs__a:focus-visible{ outline:2px solid var(--kz); outline-offset:1px; }
/* where you ARE: not a link, and it says so to a screen reader as well as to the eye */
.kz-crumbs__here{ padding:.2rem .4rem; color:var(--fg); font-weight:600; }
/* the chevron, drawn rather than fetched */
.kz-crumbs__sep{ width:.55rem; height:.55rem; margin:0 .1rem; flex:none;
  border-right:1.6px solid var(--fg-faint); border-top:1.6px solid var(--fg-faint);
  transform:rotate(45deg); opacity:.8; }
@media (max-width:575.98px){ .kz-crumbs{ font-size:.8rem; } }

.kz-verpick{ display:flex; align-items:center; gap:.4rem; flex-wrap:wrap; }
.kz-verpick__lbl{ font-size:.86rem; color:var(--fg-muted); margin-right:.1rem; }
.kz-verpick__v{
  min-width:30px; height:30px; padding:0 .5rem; border-radius:9px;
  border:1px solid var(--hairline); background:var(--surface-card);
  color:var(--fg); font-size:.84rem; font-weight:600; line-height:1;
  display:inline-grid; place-content:center; text-decoration:none; }
@media (hover:hover){ .kz-verpick__v:hover{ border-color:var(--kz); color:var(--kz); } }
/* \U0001F534 WHITE INK IN BOTH THEMES. `--kz` is the same indigo in dark as in light, so a
   near-black ink here measured **2.98** against it where 4.5 is the minimum \u2014 while the
   white the light theme uses measures 6.29 on the identical fill. A dark-mode override is
   not automatically the accessible choice; it is only right when the SURFACE changed. */
.kz-verpick__v.is-on{
  border-color:transparent; background:var(--kz); color:#fff; }

/* The action that makes a NEW version. \u26a0 `flex:none` and `flex-wrap:nowrap`: as a
   plain `ms-auto` flex item it was handed all the free space as margin and then squeezed
   below its own content width, splitting the input from its button. An action group sizes
   to its content and never breaks in half. */
.kz-verpick__act{ display:flex; align-items:center; gap:.5rem; flex:none;
  flex-wrap:nowrap; margin-left:auto; }
.kz-verpick__act .form-control{ width:clamp(180px, 22vw, 300px); }
@media (max-width:767.98px){
  /* On a phone the action drops under the chips rather than fighting them for the row. */
  .kz-verpick__act{ margin-left:0; width:100%; }
  .kz-verpick__act .form-control{ width:auto; flex:1 1 auto; }
}

.pc-wrap{ overflow-x:auto; padding:6px 2px 2px; }
/* ⚠ A viewBox'd SVG with width:100% STRETCHES to fill its container — measured 995px
   from a 660 viewBox, which blew every box up by half again. Cap it at the natural
   width so the chart only ever shrinks, never inflates. */
.pc{ display:block; margin:0 auto; width:100%; max-width:680px; height:auto; }
/* ⚠ EVERY NEW SHAPE JOINS THIS LIST. It sets the fill and stroke for the whole chart; a
   shape left out of it renders with the browser's default black fill, which in dark mode is
   an invisible box and in light mode a solid blob. There is a print override further down
   that needs the same names — change one, change both. */
.pc-box, .pc-term, .pc-dec, .pc-branch, .pc-doc, .pc-delay, .pc-sub{
  fill:var(--surface-card); stroke:var(--hairline-2);
  stroke-width:1.4; }
.pc-term{ fill:color-mix(in srgb, var(--kz) 14%, var(--surface-card)); stroke:var(--kz); }
.pc-end{ fill:color-mix(in srgb, var(--bad) 14%, var(--surface-card)); stroke:var(--bad); }
.pc-dec{ fill:color-mix(in srgb, var(--warn) 16%, var(--surface-card)); stroke:var(--warn); }
.pc-branch{ fill:var(--surface-sunken); stroke:var(--fg-faint); }
/* The three shapes added 2026-09-24. 🔴 Each is tinted by what it MEANS, not decorated:
   paperwork is neutral, waiting is amber because it is the time you are trying to remove,
   and a hand-off is cyan because it belongs to somebody else's process. */
.pc-doc{ fill:color-mix(in srgb, var(--fg) 4%, var(--surface-card)); }
.pc-delay{ fill:color-mix(in srgb, var(--warn) 13%, var(--surface-card)); stroke:var(--warn); }
.pc-sub{ fill:color-mix(in srgb, var(--accent) 11%, var(--surface-card)); stroke:var(--accent); }
.pc-subbar{ stroke:var(--accent); stroke-width:1.5; fill:none; }
/* A hand-off that resolves to a real process is a link, and it must look like one. */
.pc-a{ cursor:pointer; }
.pc-a .pc-sub{ transition:fill var(--dur-fast) var(--ease); }
@media (hover:hover){
  .pc-a:hover .pc-sub{ fill:color-mix(in srgb, var(--accent) 24%, var(--surface-card)); }
  .pc-a:hover .pc-txt{ fill:var(--accent); } }
.pc-a:focus-visible{ outline:2px solid var(--kz); outline-offset:2px; }
.pc-line{ fill:none; stroke:var(--fg-muted); stroke-width:1.4; }
.pc-arrow{ fill:var(--fg-muted); stroke:none; }
.pc-txt{ font-size:12px; font-weight:600; fill:var(--fg); }
.pc-who{ font-size:10px; fill:var(--fg-muted); }
/* The checklist and the turn-around. \u26a0 A THIRD weight, not a third copy of `.pc-who`:
   the box needs three distinguishable levels (do this / this person / this reference) or
   the eye reads the whole block as one paragraph. Italic marks it as a pointer to a
   document rather than a fact about the step. */
.pc-how{ font-size:9.5px; fill:var(--fg-faint); font-style:italic; }
.pc-lbl{ font-size:10px; font-weight:600; fill:var(--fg-muted); }
/* A headline FIGURE with a label — Turn Around Time on a process, the fixed/variable
   split on Expenses. Named neutrally because it is shared: a `pc-` (process chart) name
   on a finance page is the kind of misnaming that becomes a collision. The one number the programme asks the client to bring down, so it
   is the largest thing in its block. ⚠ `white-space:nowrap` on the figure: it is a
   measurement and must never break mid-number (§8). */
.kz-fig{ display:flex; flex-wrap:wrap; align-items:baseline; gap:6px 14px;
  padding:12px 14px; margin-bottom:12px; border-radius:var(--radius-sm);
  background:color-mix(in srgb, var(--kz) 7%, transparent);
  border:1px solid color-mix(in srgb, var(--kz) 22%, transparent); }
.kz-fig__v{ font-size:1.6rem; font-weight:700; line-height:1.1; color:var(--fg);
  white-space:nowrap; }
.kz-fig__v > span{ font-size:.82rem; font-weight:600; color:var(--fg-muted);
  margin-left:6px; }
.kz-fig__v--none{ font-size:1.15rem; color:var(--fg-muted); }
.kz-fig__v--good{ color:var(--good); }
.kz-fig__v--bad{ color:var(--bad); }
.kz-fig__l{ flex:1 1 260px; min-width:0; font-size:.86rem; color:var(--fg-muted);
  line-height:1.45; }
html[data-theme="dark"] .kz-fig{ background:rgba(99,102,241,.12);
  border-color:rgba(165,180,252,.28); }

/* Two charts side by side; one above the other once there is no room for two. */
.pc-cmp{ display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:16px; }
.pc-cmp > *{ min-width:0; }
@media (max-width: 991.98px){ .pc-cmp{ grid-template-columns:minmax(0, 1fr); } }
@media print{
  /* A chart printed as an outline of empty boxes is worse than no chart, so the
     fills are restated for paper rather than left to the theme. */
  .pc-wrap{ overflow:visible; }
  .pc-box, .pc-term, .pc-dec, .pc-branch, .pc-doc, .pc-delay, .pc-sub{
    fill:#fff !important; stroke:#334155 !important; }
  /* 🔴 On paper a shape must still be tellable APART, and the tints above are gone. The
     outline carries it: waiting is dashed, a hand-off keeps its side bars. A meaning carried
     only by colour does not survive an office laser (§9b's mono rule). */
  .pc-delay{ stroke-dasharray:5 3; }
  .pc-subbar{ stroke:#334155 !important; }
  .pc-txt{ fill:#0F172A !important; }
  .pc-who, .pc-lbl{ fill:#475569 !important; }
  .pc-how{ fill:#64748B !important; }
  .pc-line{ stroke:#475569 !important; }  .pc-arrow{ fill:#475569 !important; }
  .pc-cmp{ grid-template-columns:repeat(2, minmax(0, 1fr)); }
}

/* ==========================================================================
   RESPONSIVE OVERRIDES — THIS SECTION MUST STAY LAST IN THE FILE.
   🔴 These rules exist to BEAT component CSS, and `position` has no `min-*`
   equivalent to lean on, so their only advantage is source order. Written
   mid-file they lost to `.tb-tbl .tb-name{position:sticky}` two thousand
   lines below and the frozen columns stayed 454px wide on a 360px phone —
   measured, after the "fix". Anything added below here is an override; put
   ordinary component styles above.
   ========================================================================== */
/* --- WIDE TABLES ON A PHONE (owner-reported 2026-09-02) ---
   A table inside a horizontal scroller passes every overflow check and can still be
   unusable: the Trial Balance pinned 454px of frozen columns on a 360px screen, so not one
   month was reachable. §9t's rule ("freeze only what the screen can spare") is applied to
   the finance grids here, the first column is narrowed to leave room for actual data, and
   the scroll is finally made VISIBLE — a scroller with no scrollbar, no edge and no hint
   reads as a table that has been cut off. */
@media (max-width:991.98px){
  /* Release every frozen column but the first — the second one is what made the data
     unreachable. The first stays so a row can still be identified while scrolling. */
  .tb-tbl .tb-name, .tb-tbl .tb-no, .tb-tbl .tb-act,
  .inv-tbl .inv-c2, .inv-tbl .inv-uom,
  .exp-tbl .exp-c2, .exp-tbl .exp-type{ position:static; box-shadow:none; }
  .tb-tbl .tb-grp, .inv-tbl .inv-c1, .inv-tbl .inv-cat,
  .exp-tbl .exp-c1, .exp-tbl .exp-head{ box-shadow:2px 0 0 -1px var(--border); }
}
@media (max-width:575.98px){
  /* 🔴 The month column measured 184px against a declared 104: the table is
     `width:max-content`, so each column takes its content's PREFERRED width, and a month
     cell holds an `<input>` whose preferred width comes from its `size` attribute (~20
     characters). A specified CSS width on the INPUT replaces that contribution, so the
     column can honour its own. ⚠ Do NOT reach for `width:auto` on the table — measured, it
     collapses the ledger-name column to 28px, and `max-content` is what makes the frozen
     first column work at all (§9t). */
  /* 🔴 The paise width (`[data-live-total="2"]` = "this grid shows paise") exists because
     crore figures were silently truncating on a financial statement. It is not overridden
     away — the FIGURE is made smaller so the same string still fits whole in less room.
     Capping the input instead clipped "-41,00,000.41" outright (measured). */
  .tb-tbl[data-live-total="2"] .tb-m{ width:132px; }
  .tb-tbl .tb-m, .tb-tbl .tb-m .form-control, .tb-tbl .tb-m .tb-amt{ font-size:.7rem; }
  .tb-tbl .tb-m .form-control, .tb-tbl .tb-m .tb-amt{ width:100%; min-width:0; padding:.24rem .2rem; }
  /* A phone cannot spare 210px of label before the first figure. */
  .tb-tbl .tb-grp{ width:112px; }
  .tb-tbl .tb-name{ width:150px; }
  .tb-tbl .tb-m{ width:104px; }
  .tb-tbl th, .tb-tbl td{ padding:8px 7px; }
  /* A label column that cannot wrap has the whole name as its min-content, so a declared
     width is only a floor (§9t). Wrapping lets it actually shrink. Safe at this width
     because only the FIRST column is frozen here, so a taller row shifts no sticky offset. */
  .inv-tbl .inv-c1, .inv-tbl .inv-cat, .exp-tbl .exp-c1, .exp-tbl .exp-head{
    width:132px; white-space:normal; overflow-wrap:anywhere; }
}

/* ===================================================================================
   STORAGE & USAGE (`su-*`) — the ring, the module bars, the twelve-month spark.
   Added 2026-09-04 for /storage and /platform/storage.

   Colours come from ONE list of six `--su-N` tokens, so the ring segment, its legend dot
   and its bar are literally the same value; a chart whose legend disagrees with the chart
   is worse than no legend. Dark mode redefines only the tokens.
   =================================================================================== */
:root{
  --su-0:#4F46E5; --su-1:#06B6D4; --su-2:#7C3AED; --su-3:#0EA5E9;
  --su-4:#14B8A6; --su-5:#8B5CF6;
  --su-track:#e9edf5;
}
.su-c0{ --su:var(--su-0); } .su-c1{ --su:var(--su-1); } .su-c2{ --su:var(--su-2); }
.su-c3{ --su:var(--su-3); } .su-c4{ --su:var(--su-4); } .su-c5{ --su:var(--su-5); }

/* --- the ring ------------------------------------------------------------------- */
.su-ring{ position:relative; width:172px; height:172px; flex:0 0 172px; }
.su-ring svg{ width:100%; height:100%; transform:rotate(-90deg); }
.su-ring circle{ fill:none; stroke-width:13; }
.su-track{ stroke:var(--su-track); }
.su-seg{
  stroke:var(--su); stroke-linecap:butt;
  /* r=52 -> circumference 326.7, so 1% == 3.267 units; the template does that maths */
  stroke-dasharray:var(--dash) 326.7; stroke-dashoffset:var(--off);
}
.su-ring__mid{
  position:absolute; inset:0; display:grid; place-content:center; text-align:center;
}
.su-ring__v{ font-family:var(--font-display); font-weight:700; font-size:1.32rem;
  letter-spacing:-.02em; color:var(--fg); font-variant-numeric:tabular-nums; }
.su-ring__l{ font-size:var(--fs-caption); color:var(--fg-muted); }

/* --- legend ---------------------------------------------------------------------- */
.su-legend{ display:flex; flex-direction:column; gap:6px; }
.su-legend__row{ display:flex; align-items:center; gap:8px; font-size:var(--fs-body); }
.su-legend__n{ color:var(--fg-muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.su-legend__v{ margin-left:auto; font-weight:600; color:var(--fg); font-variant-numeric:tabular-nums; }
.su-dot{ width:10px; height:10px; border-radius:3px; background:var(--su); flex:0 0 10px; }

/* --- module bars ------------------------------------------------------------------ */
.su-bars{ display:flex; flex-direction:column; gap:var(--sp-4); }
.su-bar__head{ display:flex; align-items:baseline; gap:8px; margin-bottom:5px; }
.su-bar__n{ display:flex; align-items:center; gap:8px; font-weight:600; color:var(--fg); }
.su-bar__v{ margin-left:auto; font-weight:700; color:var(--fg); font-variant-numeric:tabular-nums; }
/* ⚠ --fg-muted, not --fg-faint: faint measures 3.36:1 on a card in dark mode and AA wants
   4.5 for text this size. Same finding as .kz-sec-label. */
.su-bar__p{ color:var(--fg-muted); font-weight:600; font-size:var(--fs-caption); margin-left:8px; }
.su-bar__track{ height:9px; border-radius:999px; background:var(--su-track); overflow:hidden; }
.su-bar__fill{ height:100%; border-radius:999px; background:var(--su); width:var(--w); }
.su-bar__meta{ font-size:var(--fs-caption); margin-top:4px; }

/* --- twelve-month spark ------------------------------------------------------------ */
.su-spark{ display:flex; align-items:flex-end; gap:6px; height:150px; }
.su-spark__col{ flex:1 1 0; display:flex; flex-direction:column; justify-content:flex-end;
  height:100%; min-width:0; }
.su-spark__bar{
  position:relative; height:var(--h); min-height:3px; border-radius:6px 6px 2px 2px;
  background:linear-gradient(180deg,var(--su-0),var(--su-1));
}
/* ⚠ A month with NOTHING keeps its column and shows a flat rule, not a stub of colour —
   a 3px coloured bar reads as "a little", and the truth is none. */
.su-spark__bar.is-nil{ background:var(--su-track); height:3px; }
.su-spark__x{ font-size:.62rem; color:var(--fg-muted); text-align:center; margin-top:5px;
  font-variant-numeric:tabular-nums; }
.su-spark__tip{
  position:absolute; bottom:calc(100% + 6px); left:50%; transform:translateX(-50%);
  background:var(--fg); color:var(--surface-card); font-size:.68rem; white-space:nowrap;
  padding:3px 7px; border-radius:6px; pointer-events:none; z-index:3;
  /* display:none, not opacity:0 — an invisible tip on the last column still stuck 20px past
     the phone's edge and gave /storage a sideways scroll (responsive sweep, 2026-09-09) */
  display:none;
}
.su-spark__col:hover .su-spark__tip,
.su-spark__col:focus-within .su-spark__tip{ display:block; }
.su-spark__col:last-child .su-spark__tip,
.su-spark__col:nth-last-child(2) .su-spark__tip{ left:auto; right:0; transform:none; }

/* --- the machine gauge (super admin) ------------------------------------------------ */
.su-gauge{ height:14px; border-radius:999px; background:var(--su-track); overflow:hidden;
  display:flex; }
.su-gauge__seg{ height:100%; background:var(--su); }
.su-gauge__seg.is-free{ background:transparent; }
.su-keys{ display:flex; flex-wrap:wrap; gap:var(--sp-3) var(--sp-5); margin-top:var(--sp-3); }
.su-key{ display:flex; align-items:center; gap:7px; font-size:var(--fs-caption); color:var(--fg-muted); }
.su-key b{ color:var(--fg); font-variant-numeric:tabular-nums; }

/* --- entrance motion ----------------------------------------------------------------
   Everything animates FROM a visible resting state where it can, and the bars/ring are the
   one exception: they grow from zero, which is the point. `--i` staggers them. */
@media (prefers-reduced-motion: no-preference){
  .su-seg{ animation:su-dash .85s var(--ease) both; animation-delay:calc(var(--i) * .08s); }
  @keyframes su-dash{ from{ stroke-dasharray:0 326.7; } }
  .su-bar__fill{ animation:su-grow .8s var(--ease) both; animation-delay:calc(var(--i) * .07s); }
  @keyframes su-grow{ from{ width:0; } }
  .su-spark__bar{ animation:su-rise .6s var(--ease) both; animation-delay:calc(var(--i) * .035s); }
  @keyframes su-rise{ from{ height:0; } }
  .su-legend__row{ animation:su-in .45s var(--ease) both; animation-delay:calc(var(--i) * .05s); }
  .su-row{ animation:su-in .4s var(--ease) both; animation-delay:calc(var(--i) * .03s); }
  @keyframes su-in{ from{ opacity:0; transform:translateY(5px); } }
  .su-gauge__seg{ animation:su-grow .9s var(--ease) both; }
}

@media (max-width:640px){
  .su-hero .d-flex{ flex-direction:column; align-items:stretch !important; gap:var(--sp-4) !important; }
  .su-ring{ margin:0 auto; }
  .su-spark{ height:120px; gap:3px; }
  .su-spark__x{ font-size:.55rem; }
}

@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --su-0:#818CF8; --su-1:#22D3EE; --su-2:#A78BFA; --su-3:#38BDF8;
    --su-4:#2DD4BF; --su-5:#C4B5FD; --su-track:#2b3648;
  }
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .kz-stickytable .fin-table thead th{
    box-shadow:inset 0 -1px 0 var(--hairline), 0 5px 9px rgba(0,0,0,.42); }
  :root:not([data-theme="light"]) .kz-stickytable .fin-table tfoot th,
  :root:not([data-theme="light"]) .kz-stickytable .fin-table tfoot td{
    box-shadow:inset 0 1px 0 var(--hairline), 0 -5px 9px rgba(0,0,0,.42); }
}
:root[data-theme="dark"] .kz-stickytable .fin-table thead th{
  box-shadow:inset 0 -1px 0 var(--hairline), 0 5px 9px rgba(0,0,0,.42); }
:root[data-theme="dark"] .kz-stickytable .fin-table tfoot th,
:root[data-theme="dark"] .kz-stickytable .fin-table tfoot td{
  box-shadow:inset 0 1px 0 var(--hairline), 0 -5px 9px rgba(0,0,0,.42); }

:root[data-theme="dark"]{
  --su-0:#818CF8; --su-1:#22D3EE; --su-2:#A78BFA; --su-3:#38BDF8;
  --su-4:#2DD4BF; --su-5:#C4B5FD; --su-track:#2b3648;
}

/* ===================================================================================
   STICKY TABLE HEADINGS — `.kz-stickytable` (2026-09-05)

   Wrap any `.fin-table` that is long enough to scroll past its own headings. The heading
   row stays put while the rows move, and a `tfoot` totals row sticks to the bottom, so
   "which column is this?" and "what do they add up to?" are both always answerable.

   🔴 THE WRAPPER MUST BE THE SCROLL CONTAINER, WHICH IS WHY IT HAS A HEIGHT.
   `position:sticky` resolves against the nearest scrolling ancestor. Bootstrap's
   `.table-responsive` sets `overflow-x:auto`, and CSS turns the OTHER axis into `auto`
   too the moment one axis is not `visible` — so that wrapper is already a scroll
   container in both directions. A header stuck to the viewport therefore never moves
   relative to it and looks like it simply does not work. Giving the wrapper a max-height
   is what makes `top:0` mean something.

   ⚠ The backgrounds must be OPAQUE tokens. A translucent heading lets the rows scroll
   through it and the text becomes unreadable at exactly the moment it matters.
   =================================================================================== */
.kz-stickytable{ overflow:auto; max-height:var(--stick-h, 68vh); }
.kz-stickytable .fin-table thead th{
  position:sticky; top:0; z-index:3;
  background:var(--surface-sunken);
  /* 🔴 NO NEGATIVE SPREAD. `0 6px 10px -6px` shrinks the shadow 6px on EVERY side, and
     because each `th` is its own box that leaves a 12px gap at every cell boundary — the
     heading renders as a row of separate raised blocks with white slots between them,
     which is worse than the flat version it replaced. Owner-reported. A plain blur with
     low alpha overlaps cleanly between neighbours and reads as one continuous band.
     🔴 A hairline alone is not enough either. A row scrolling under the heading is clipped mid-height,
     and with only a 1px line that reads as a BROKEN half-row rather than as content passing
     underneath — which is exactly how the owner described it. The drop shadow is what makes
     it legible as depth. inset for the rule (a collapsed border does not travel with a
     sticky cell in every engine), plus a real shadow below for the lift. */
  box-shadow:inset 0 -1px 0 var(--hairline), 0 4px 7px rgba(15,23,42,.10);
}
.kz-stickytable .fin-table tfoot th,
.kz-stickytable .fin-table tfoot td{
  position:sticky; bottom:0; z-index:3;
  background:var(--surface-card); font-weight:700;
  box-shadow:inset 0 1px 0 var(--hairline), 0 -4px 7px rgba(15,23,42,.10);
}
/* the last body row would otherwise finish flush under the sticky totals row, so the two
   read as one block; a little breathing space keeps the totals distinct */
.kz-stickytable .fin-table tbody tr:last-child td{ padding-bottom:14px; }
/* the scrollbar itself, so a scrollable panel looks deliberate rather than clipped */
.kz-stickytable::-webkit-scrollbar{ width:10px; height:10px; }
.kz-stickytable::-webkit-scrollbar-thumb{ background:var(--hairline-2); border-radius:99px; }
.kz-stickytable::-webkit-scrollbar-track{ background:transparent; }
@media (max-width:640px){ .kz-stickytable{ max-height:none; } }

/* The brand mark is a LINK (it goes to the current surface's dashboard), so it must not
   inherit link styling — no underline, no accent colour — and it must show a focus ring
   for keyboard users, which a bare <div> never needed. */
/* ⚠ No padding/margin of its own. A higher-specificity `.brand` rule already sets
   `padding:16px 20px`, so my `padding` lost the cascade while the negative `margin` still
   applied — pulling the block 3px/7px out of alignment for no benefit. The existing padding
   is already a comfortable box for the hover background. */
/* 🔴 DECLARES NO COLOUR AT ALL — not even `inherit`. `.sidebar .brand` sets `color:#fff`
   at specificity (0,2,0); this rule's `:hover` is (0,2,1) and therefore OUTRANKS it, so a
   `color:inherit` here silently replaced the white with the sidebar's own dark text colour —
   the logo turned black on hover. Owner-reported. Saying nothing about colour lets the
   existing rule keep owning it in every state, which is what a hover effect should do. */
a.brand{ display:block; text-decoration:none; border-radius:12px; }
a.brand img{ transition:transform var(--dur) var(--ease); }
/* 🔴 The brand NEVER changes colour on hover — recolouring a logo is recolouring the brand,
   and the earlier `opacity:.88` read as the text going dull rather than as a hover.
   ⚠ It does not change `font-weight` either: that re-lays out the text and the whole block
   twitches sideways. `text-shadow` in `currentColor` thickens the glyphs by a fraction of a
   pixel with IDENTICAL metrics — it reads bolder and nothing moves. */
a.brand:hover, a.brand:focus-visible{
  /* ⚠ no `color` line here either, deliberately — see above. */
  background:rgba(255,255,255,.14);
  /* stacked shadows in the text's OWN colour: three passes read as a genuine weight change
     rather than the faint blur the single pass gave, and the metrics never move. */
  text-shadow:0 0 .5px currentColor, 0 0 .5px currentColor, 0 0 1px currentColor;
}
a.brand:hover img{ transform:scale(1.06); }
a.brand:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
@media (prefers-reduced-motion: reduce){
  a.brand img{ transition:none; }
  a.brand:hover img{ transform:none; }
}

/* --- Section label + advisory card (2026-09-07) -------------------------------------
   `.kz-sec-label` gives a long console page rhythm: a small caps label above each group,
   so "the machine" / "what is stored" / "per client" read as separate ideas instead of
   one undifferentiated stack of cards.
   `.kz-advis` leads with the FIGURE. An advisory whose number is buried mid-paragraph is
   an advisory nobody reads — the eye stops on a number, not on prose. */
.kz-sec-label{ font-size:var(--fs-label); text-transform:uppercase;
  letter-spacing:var(--tracking-caps); color:var(--fg-muted); font-weight:700;
  /* ⚠ --fg-muted, NOT --fg-faint. Faint measured 4.10:1 on the dark page ground at
     11.5px bold, and AA wants 4.5 — 'large text' starts at 18.66px bold, so a small
     caps label gets no concession. Caught by checking the class I had just written. */
  margin:var(--sp-5) 0 var(--sp-2); }
.kz-sec-label:first-child{ margin-top:0; }
.kz-advis{ border-left:3px solid var(--warn); }
.kz-advis__n{ font-family:var(--font-display); font-weight:700; font-size:1.5rem;
  line-height:1.15; color:var(--warn); white-space:nowrap; min-width:88px;
  font-variant-numeric:tabular-nums; }
@media (max-width:640px){
  .kz-advis .d-flex{ flex-direction:column; gap:var(--sp-2) !important; }
  .kz-advis__n{ min-width:0; }
}
/* ── Sign-in message composer (/users/messages) ─────────────────────────────── 2026-09-07 */
.kzm-step{ display:inline-grid; place-content:center; width:22px; height:22px; border-radius:7px;
  background:var(--kz-grad); color:#fff; font-size:.74rem; font-weight:700; margin-right:6px;
  font-family:var(--font-mono); }

/* the people list — a scroll pane, because a 40-person business must not push step 2 off
   the screen, and the two columns are meant to be read side by side */
.kzm-people{ max-height:min(62vh, 560px); overflow:auto; border:1px solid var(--hairline);
  border-radius:var(--radius-sm); background:var(--surface-sunken); padding:6px;
  display:flex; flex-direction:column; gap:6px; }
.kzm-person{ display:flex; align-items:flex-start; gap:10px; padding:9px 11px; cursor:pointer;
  background:var(--surface-card); border:1px solid var(--hairline); border-radius:var(--radius-sm);
  transition:border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease); }
.kzm-person:hover{ border-color:var(--kz); }
.kzm-person.is-on{ border-color:var(--kz); box-shadow:inset 3px 0 0 var(--kz); }
.kzm-person .form-check-input{ margin-top:3px; flex:0 0 auto; }
.kzm-person__b{ min-width:0; display:flex; flex-direction:column; gap:3px; }
.kzm-person__n{ font-weight:600; font-size:.9rem; color:var(--fg); display:flex;
  align-items:center; gap:6px; flex-wrap:wrap; }
.kzm-person__m{ font-size:var(--fs-caption); color:var(--fg-muted); }
.kzm-person__s{ display:flex; flex-wrap:wrap; gap:4px; }
/* 🔴 The service pills WRAP. They can hold a merged list of every service sharing one
   sign-in address, which at 390px ran to 937px — off the screen and unreadable. The shared
   `.kz-pill` is nowrap for status badges, and stays that way everywhere else. */
.kzm-person__s .kz-pill{ white-space:normal; text-align:left; max-width:100%; }
.kzm-av{ flex:0 0 auto; width:30px; height:30px; border-radius:9px; display:grid;
  place-content:center; background:var(--kz-grad); color:#fff; font-weight:700;
  font-size:.82rem; }

/* an option that is a whole clickable block, not a bare radio with a word next to it */
.kzm-variants, .kzm-two{ display:flex; flex-direction:column; gap:6px; }
.kzm-two{ display:grid; grid-template-columns:1fr 1fr; }
.kzm-opt{ display:flex; align-items:flex-start; gap:9px; padding:9px 11px; cursor:pointer;
  border:1px solid var(--hairline); border-radius:var(--radius-sm); background:var(--surface-card);
  transition:border-color var(--dur-fast) var(--ease); }
.kzm-opt:hover{ border-color:var(--kz); }
.kzm-opt:has(input:checked){ border-color:var(--kz); background:var(--surface-raised); }
.kzm-opt .form-check-input{ margin-top:2px; flex:0 0 auto; }
.kzm-opt span{ min-width:0; display:flex; flex-direction:column; gap:2px; }
.kzm-opt b{ font-size:.86rem; font-weight:600; color:var(--fg); }
.kzm-opt em{ font-style:normal; font-size:var(--fs-caption); color:var(--fg-muted);
  line-height:1.35; }

/* the generated messages */
.kzm-cards{ display:grid; gap:12px; grid-template-columns:repeat(auto-fit, minmax(320px, 1fr)); }
.kzm-card{ padding:12px; }
.kzm-card__hd{ display:flex; align-items:center; gap:9px; margin-bottom:8px; }
.kzm-ta{ font-family:var(--font-mono); font-size:.8rem; line-height:1.5; resize:vertical; }
.kzm-out{ border-color:var(--good); }

/* the action bar follows you down the page — on a phone the button is a long way from the
   checkboxes, and hunting for it is the whole friction */
.kzm-bar{ position:sticky; bottom:0; z-index:5; display:flex; align-items:center; gap:10px;
  flex-wrap:wrap; margin-top:14px; padding:10px 14px; border:1px solid var(--hairline);
  border-radius:var(--radius); background:var(--surface-card); box-shadow:var(--elev-2); }

@media (max-width:575.98px){
  .kzm-two{ grid-template-columns:1fr; }
  .kzm-people{ max-height:none; }
  .kzm-bar{ padding:9px 11px; }
  .kzm-bar .btn{ width:100%; }
}

/* ── Record drawer + the vertical booking form ──────────────────────────── 2026-09-07 ── */
.kzd[hidden]{ display:none !important; }
.kzd{ position:fixed; inset:0; z-index:1060;
  /* ⚠ the app runs at zoom:0.8 on desktop, so a 100vw/100vh overlay covers only 80% of
     the screen — the same trap the modal backdrop hit (kizuna-ui §5). */
  width:calc(100vw / 0.8); height:calc(100vh / 0.8); }
@media screen and (max-width:1023.98px){ .kzd{ width:100vw; height:100vh; } }
.kzd__veil{ position:absolute; inset:0; background:rgba(15,23,42,.45); }
.kzd__panel{ position:absolute; top:0; right:0; bottom:0; width:min(560px, 100%);
  background:var(--surface-card); box-shadow:var(--elev-3); overflow:auto;
  border-left:1px solid var(--hairline); }
.kzd__x{ position:absolute; top:10px; right:12px; z-index:2; border:0; background:transparent;
  width:34px; height:34px; border-radius:9px; color:var(--fg-muted); }
.kzd__x:hover{ background:var(--surface-sunken); color:var(--fg); }
.kzd__body{ padding:18px; }
.kzd__load{ padding:40px 0; text-align:center; color:var(--fg-muted); }
body.kzd-open{ overflow:hidden; }
@media (prefers-reduced-motion: no-preference){
  .kzd__panel{ animation:kzd-in .18s var(--ease); }
  @keyframes kzd-in{ from{ transform:translateX(16px); opacity:.6 } to{ transform:none; opacity:1 } }
}

.kzb-hd{ display:flex; align-items:flex-start; gap:10px; padding-right:34px; margin-bottom:12px; }
.kzb-name{ font-family:var(--font-display); font-weight:700; margin:0; font-size:1.12rem;
  overflow-wrap:anywhere; }
.kzb-sub{ font-size:var(--fs-caption); color:var(--fg-muted); }

/* the three figures the accountant checks first */
.kzb-tot{ display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:8px;
  padding:11px 13px; border-radius:var(--radius-sm); background:var(--surface-sunken);
  border:1px solid var(--hairline); margin-bottom:14px; }
.kzb-tot__l{ display:block; font-size:var(--fs-label); letter-spacing:.06em;
  text-transform:uppercase; color:var(--fg-muted); font-weight:700; }
.kzb-tot__v{ display:block; font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  font-weight:700; font-size:.92rem; color:var(--fg); white-space:nowrap; }
.kzb-tot__v.is-due{ color:var(--bad); }

.kzb-g{ border:1px solid var(--hairline); border-radius:var(--radius-sm); padding:12px 13px;
  margin-bottom:11px; background:var(--surface-card); }
.kzb-g__t{ display:flex; align-items:center; gap:7px; font-size:.86rem; font-weight:700;
  margin:0 0 9px; color:var(--fg); }
.kzb-n{ display:inline-grid; place-content:center; width:20px; height:20px; border-radius:6px;
  background:var(--kz-grad); color:#fff; font-size:.7rem; font-weight:700;
  font-family:var(--font-mono); flex:0 0 auto; }
/* a derived value reads as a FACT, not a field — it must not look editable */
.kzb-derived{ font-family:var(--font-mono); font-variant-numeric:tabular-nums; font-weight:600;
  padding:5px 2px; color:var(--fg); }

.kzb-bar{ display:flex; align-items:center; gap:10px; position:sticky; bottom:-18px;
  padding:11px 0 2px; background:var(--surface-card); border-top:1px solid var(--hairline); }

.kzb-docs{ display:flex; flex-direction:column; gap:7px; }
.kzb-doc{ display:flex; align-items:center; gap:9px; padding:8px 10px;
  border:1px solid var(--hairline); border-radius:var(--radius-sm); }
.kzb-doc__n{ font-size:.85rem; font-weight:600; overflow-wrap:anywhere; }
.kzb-doc__m{ font-size:var(--fs-caption); color:var(--fg-muted); }
.kzb-doc__a{ margin-left:auto; display:flex; gap:5px; flex-wrap:wrap; }

.kzb-rec{ display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:8px; }
.kzb-rec__v{ display:block; font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  font-weight:700; font-size:.9rem; white-space:nowrap; }
.kzb-rec__v.is-due{ color:var(--bad); }

/* the register's own link into all of this */
.kz-txtlink{ color:var(--kz2); text-decoration:none; font-weight:600; }
.kz-txtlink:hover{ text-decoration:underline; }
html[data-theme="dark"] .kz-txtlink{ color:#A5B4FC; }
html[data-theme="dark"] .kzd__veil{ background:rgba(2,6,23,.6); }

@media (max-width:575.98px){
  .kzd__panel{ width:100%; top:auto; height:92vh; border-left:0;
    border-top-left-radius:var(--radius); border-top-right-radius:var(--radius); }
  .kzb-tot, .kzb-rec{ grid-template-columns:1fr 1fr; }
}
/* the document, read in place */
.kzb-view{ margin:0; border:1px solid var(--hairline); border-radius:var(--radius-sm);
  overflow:hidden; background:var(--surface-sunken); }
.kzb-view[hidden]{ display:none !important; }
.kzb-view__bar{ display:flex; align-items:center; gap:7px; padding:7px 9px;
  background:var(--surface-card); border-bottom:1px solid var(--hairline);
  font-size:var(--fs-caption); font-weight:600; color:var(--fg); min-width:0; }
.kzb-view__body{ display:flex; align-items:center; justify-content:center; min-height:120px; }
.kzb-view__img{ max-width:100%; max-height:62vh; display:block; }
/* ⚠ a PDF needs a real height — an iframe defaults to ~150px and shows a sliver of page one */
.kzb-view__pdf{ width:100%; height:min(62vh, 620px); border:0; display:block;
  background:var(--surface-card); }

/* ── WhatsApp for Debtors Recovery ─────────────────────────────────────── 2026-09-07 ── */
.wa-cfg{ display:flex; flex-direction:column; gap:7px; }
.wa-cfg__row{ display:flex; align-items:flex-start; gap:10px; padding:8px 10px;
  border:1px solid var(--hairline); border-radius:var(--radius-sm); }
.wa-cfg__row code{ font-size:.78rem; }
.wa-pre{ background:var(--surface-sunken); border:1px solid var(--hairline);
  border-radius:var(--radius-sm); padding:10px 12px; font-family:var(--font-mono);
  font-size:.76rem; overflow-x:auto; margin:0; white-space:pre; }
.wa-hook{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-top:6px; }
.wa-hook code{ background:var(--surface-sunken); padding:5px 9px;
  border-radius:var(--radius-sm); font-size:.76rem; overflow-wrap:anywhere; }

/* a template, as it must be created in Meta */
.wa-tpl{ border:1px solid var(--hairline); border-radius:var(--radius-sm); padding:11px 13px;
  margin-bottom:10px; }
.wa-tpl__hd{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:4px; }
.wa-tpl__n{ font-size:.84rem; font-weight:700; }
/* ⚠ Shown as it will look in WhatsApp, so the owner can compare it against the Meta preview
   rather than reading a spec and hoping. */
/* ⚠ `display:block` because this is a `<code>` element (see the template for why) and
   a bubble has to be a block; the face stays the body font, not mono — it is a message
   the customer will read, shown as they will see it. */
.wa-tpl__body{ display:block; background:#e7ffdb; color:#0f172a; border-radius:10px;
  padding:9px 11px; font-size:.84rem; line-height:1.45; white-space:pre-wrap;
  overflow-wrap:anywhere; font-family:var(--bs-body-font-family); }
html[data-theme="dark"] .wa-tpl__body{ background:#1f3d2c; color:#e9f7ef; }
.wa-tpl__p{ display:flex; flex-wrap:wrap; gap:6px; margin-top:8px; }
.wa-var{ font-size:var(--fs-caption); background:var(--surface-sunken);
  border:1px solid var(--hairline); border-radius:999px; padding:3px 9px; }
.wa-var code{ font-family:var(--font-mono); margin-right:3px; font-weight:700; }

.wa-thread{ display:flex; flex-direction:column; gap:8px; }
.wa-msg{ max-width:min(560px, 100%); padding:8px 11px; border-radius:12px;
  background:var(--surface-sunken); }
.wa-msg.is-in{ background:#e7ffdb; color:#0f172a; }
html[data-theme="dark"] .wa-msg.is-in{ background:#1f3d2c; color:#e9f7ef; }
.wa-msg__who{ font-size:var(--fs-caption); font-weight:700; margin-bottom:2px; }
.wa-msg__b{ font-size:.86rem; white-space:pre-wrap; overflow-wrap:anywhere; }
.wa-body{ max-width:420px; white-space:pre-wrap; overflow-wrap:anywhere; font-size:.82rem; }

.wa-opts{ display:flex; flex-direction:column; gap:7px; }
.wa-opt{ display:flex; align-items:center; gap:10px; padding:8px 10px;
  border:1px solid var(--hairline); border-radius:var(--radius-sm); }

/* the send control on a debtor's row */
.wa-send{ display:inline-flex; align-items:center; gap:4px; }
.wa-tick{ font-size:.7rem; }
.wa-tick.is-good{ color:var(--good); }
.wa-tick.is-bad{ color:var(--bad); }

/* ── WhatsApp conversations (the agent's inbox) ────────────────────────────────────────
   Two panes that scroll INDEPENDENTLY and fit the viewport, because the alternative is a
   page that grows to the length of the longest thread: the agent scrolls past two hundred
   messages to reach the box they came to type in, and the conversation list scrolls off
   the top with it. The height is clamped rather than fixed — at 80% zoom the page is
   effectively 25% taller, and a hard `calc(100vh - 300px)` leaves a lake of empty card
   under the composer. */
.wac{ display:grid; grid-template-columns:330px minmax(0,1fr); gap:14px; align-items:stretch;
      --wac-h:clamp(520px, calc(100vh - 205px), 880px); }
.wac__list,.wac__pane{ height:var(--wac-h); display:flex; flex-direction:column;
      overflow:hidden; }
.wac__search{ padding:11px 12px; border-bottom:1px solid var(--hairline); flex:0 0 auto; }
.wac__convs{ overflow-y:auto; flex:1 1 auto; }

/* one conversation in the queue */
.wac__c{ display:flex; gap:10px; padding:10px 12px; text-decoration:none; color:inherit;
      border-bottom:1px solid var(--hairline); transition:background var(--dur-fast) var(--ease); }
.wac__c:hover{ background:var(--surface-sunken); }
.wac__c.is-active{ background:var(--surface-sunken);
      box-shadow:inset 3px 0 0 var(--kz); }
.wac__av{ flex:0 0 auto; width:34px; height:34px; border-radius:50%; display:grid;
      place-items:center; font-weight:700; font-size:.82rem; color:#fff;
      background:linear-gradient(120deg,var(--kz),var(--kz2)); }
.wac__av.is-unread{ background:linear-gradient(120deg,#157a5b,#0f9d76); }
.wac__c-b{ min-width:0; flex:1 1 auto; display:flex; flex-direction:column; gap:2px; }
.wac__c-t{ display:flex; align-items:baseline; gap:8px; justify-content:space-between; }
.wac__c-n{ font-size:.86rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.wac__c-w{ flex:0 0 auto; font-size:var(--fs-caption); color:var(--fg-faint);
      font-variant-numeric:tabular-nums; }
.wac__c-p{ font-size:.79rem; color:var(--fg-muted); overflow:hidden; text-overflow:ellipsis;
      white-space:nowrap; }
.wac__c-o{ font-size:.7rem; color:var(--fg-faint); margin-right:2px; }
.wac__c-m{ display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-top:1px; }
.wac__c-os{ font-size:var(--fs-caption); color:var(--fg-muted); }

/* the thread */
.wac__hd{ display:flex; align-items:center; gap:10px; padding:10px 13px;
      border-bottom:1px solid var(--hairline); flex:0 0 auto; }
.wac__hd-n{ font-weight:700; font-size:.95rem; overflow:hidden; text-overflow:ellipsis;
      white-space:nowrap; }
.wac__hd-s{ font-size:var(--fs-caption); color:var(--fg-muted); }
.wac__msgs{ flex:1 1 auto; min-height:190px; overflow-y:auto; padding:14px 13px;
      background:var(--surface-sunken); display:flex; flex-direction:column; gap:9px; }
.wac__m{ display:flex; flex-direction:column; max-width:min(560px,86%); }
.wac__m.is-out{ align-self:flex-end; align-items:flex-end; }
.wac__m.is-in{ align-self:flex-start; }
/* 🔴 An outbound bubble must not share a colour with the thread it sits on. `.wa-msg`
   defaults to `--surface-sunken`, and the conversation ground is `--surface-sunken` too —
   so what we sent was invisible against the backdrop and a thread read as a column of
   customer messages with gaps between them. Nobody could tell what had been said on the
   client's behalf. Caught in dark mode, where the two are identical; it was wrong in light
   too, only subtly enough to look intentional. */
.wac__m .wa-msg{ max-width:100%; background:var(--surface-card);
      border:1px solid var(--hairline); }
.wac__m .wa-msg.is-in{ border-color:transparent; }
.wac__m-tpl{ font-size:var(--fs-caption); font-weight:700; opacity:.72;
      letter-spacing:var(--tracking-caps); text-transform:uppercase; margin-bottom:3px; }
.wac__m-f{ display:flex; align-items:center; gap:6px; justify-content:flex-end;
      margin-top:4px; font-size:var(--fs-caption); opacity:.7;
      font-variant-numeric:tabular-nums; }
.wac__m-err{ font-size:var(--fs-caption); color:var(--bad); margin-top:3px;
      max-width:100%; overflow-wrap:anywhere; }

/* the composer */
.wac__comp{ flex:0 0 auto; padding:11px 13px; border-top:1px solid var(--hairline);
      max-height:56%; overflow-y:auto; }
.wac__tabs .nav-link{ font-size:.8rem; padding:4px 11px; border-radius:999px; }
.wac__type{ display:flex; gap:8px; align-items:flex-end; }
.wac__type textarea{ resize:vertical; min-height:44px; }
.wac__quick{ display:flex; flex-wrap:wrap; gap:6px; margin-top:7px; }
.wac__q{ font-size:var(--fs-caption); padding:3px 10px; border-radius:999px;
      border:1px solid var(--hairline); background:var(--surface-card); color:var(--fg-muted);
      cursor:pointer; transition:all var(--dur-fast) var(--ease); }
.wac__q:hover{ border-color:var(--kz); color:var(--kz); }

/* the approved-message picker: the WHOLE card is the button, because the text it will send
   is the only thing that tells the agent which one they want. A row of four labelled
   buttons makes them guess. */
.wac__tpls{ display:grid; grid-template-columns:repeat(auto-fit,minmax(228px,1fr)); gap:8px; }
.wac__tpl-b{ width:100%; height:100%; text-align:left; display:flex; flex-direction:column;
      gap:4px; padding:9px 11px; border:1px solid var(--hairline);
      border-radius:var(--radius-sm); background:var(--surface-card); cursor:pointer;
      transition:all var(--dur-fast) var(--ease); }
.wac__tpl-b:hover:not(:disabled){ border-color:var(--kz); box-shadow:var(--elev-1); }
.wac__tpl-b:disabled{ opacity:.5; cursor:not-allowed; }
.wac__tpl-n{ font-size:.83rem; font-weight:700; }
.wac__tpl-why{ font-size:var(--fs-caption); color:var(--warn); line-height:1.42; }
.wac__docs{ display:flex; flex-direction:column; gap:7px; max-height:240px; overflow-y:auto; }
.wac__doc{ display:flex; align-items:center; gap:10px; padding:8px 10px;
      border:1px solid var(--hairline); border-radius:var(--radius-sm);
      background:var(--surface-card); }
.wac__doc-i{ flex:0 0 auto; width:30px; height:30px; border-radius:8px; display:grid;
      place-items:center; background:var(--surface-sunken); color:var(--kz); }
.wac__doc-b{ min-width:0; flex:1 1 auto; display:flex; flex-direction:column; gap:1px; }
.wac__doc-n{ font-size:.83rem; font-weight:600; overflow:hidden; text-overflow:ellipsis;
      white-space:nowrap; }
.wac__doc-m{ font-size:var(--fs-caption); color:var(--fg-muted); overflow:hidden;
      text-overflow:ellipsis; white-space:nowrap; }
.wac__doc-a{ flex:0 0 auto; display:flex; align-items:center; gap:5px; }
/* ⚠ The composer is capped at 56% so a long template list cannot push the conversation
   off the card. Opening a document inside it needs that ceiling raised, or the viewer opens
   into a 200px slot and the agent scrolls a PDF inside a scrolling panel. `:has` is already
   safe here — this stylesheet uses `color-mix`, which is newer. */
.wac__comp:has(.kzb-view:not([hidden])){ max-height:78%; }
.wac__comp .kzb-view__pdf{ height:min(44vh, 360px); }
.wac__comp .kzb-view__img{ max-height:min(44vh, 360px); }
@media (max-width:575.98px){
  .wac__doc{ flex-wrap:wrap; }
  .wac__doc-a{ width:100%; justify-content:flex-end; }
}

/* ── Debtors Recovery: the bulk bar ───────────────────────────────────────────────────
   It appears only once something is ticked. A permanently visible bar full of disabled
   controls trains the agent to ignore the row where the most consequential button lives. */
.dbt-tools{ display:flex; justify-content:space-between; align-items:center; gap:10px;
      flex-wrap:wrap; }
.dbt-bulk{ display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:10px;
      padding:9px 12px; border-radius:var(--radius-sm); border:1px solid var(--kz);
      background:color-mix(in srgb, var(--kz) 7%, var(--surface-card)); }
.dbt-bulk[hidden]{ display:none; }
.dbt-bulk__n{ font-size:.85rem; font-weight:600; white-space:nowrap; }
.dbt-bulk form{ margin:0; }
.dbt-chk,[data-dbt-all]{ width:15px; height:15px; cursor:pointer; }

/* 🔴 THE TABLE SCROLLS, IT DOES NOT SQUEEZE. `.table-responsive` only scrolls once the
   content is wider than its box, and a table whose every cell wraps is never wider than its
   box — it just grows taller and less readable. Give it a floor and the horizontal
   scroller does its job; without one, opening the conversation panel broke the client cell
   over four lines and stacked "2026-09-05" vertically. */
#dbtGrid{ min-width:900px; }   /* Billed/Paid left the row (2026-09-28): the name got its width back */
/* ⚠ Per-column, NOT a blanket nowrap: the customer name must still be free to wrap, or a
   long one pushes every figure off the screen. Everything that reads as one token — a
   figure, a date, a pill, a button — must never break. */
#dbtGrid td.num,#dbtGrid th.num,
#dbtGrid .dbt-callon,#dbtGrid td:last-child,#dbtGrid th:last-child{ white-space:nowrap; }
#dbtGrid td.dbt-client{ white-space:normal; min-width:186px; }
.dbt-client__n{ display:flex; align-items:center; gap:6px; flex-wrap:wrap; line-height:1.35; }
.dbt-client__m{ display:block; font-size:var(--fs-caption); color:var(--fg-muted); /* wraps: it now carries billed + paid (2026-09-28) */
      margin-top:2px; white-space:nowrap; }
/* the aging pill is a label, not a sentence */
#dbtGrid .badge-status{ white-space:nowrap; }
/* the expanded per-invoice table has the same problem for the same reason */
.dbt-detail-tbl{ min-width:940px; }
.dbt-detail-tbl td.num,.dbt-detail-tbl th.num,
.dbt-detail-tbl td:last-child,.dbt-detail-tbl th:last-child{ white-space:nowrap; }

/* the last-chased and action cells read as one token each */
.dbt-lc{ white-space:nowrap; }
.dbt-act{ display:inline-flex; align-items:center; gap:4px; vertical-align:middle; }
.dbt-actions{ white-space:nowrap; }
.dbt-actions .dbt-act{ margin-right:6px; }
.dbt-aging{ display:flex; flex-wrap:wrap; gap:6px; }
/* a page-level or card-level explainer that is not adjacent to its title */
.kz-explain{ max-width:36rem; }

/* ── The WhatsApp share composer (2026-09-28) ─────────────────────────────────────────
   The message is shown as the customer will see it — a WhatsApp-green sheet — because the
   agent is choosing words that go out over the client's name. */
.wash{ display:flex; flex-direction:column; gap:10px; }
.wash__who{ display:flex; align-items:center; gap:10px; padding:9px 11px; min-width:0;
      border:1px solid var(--hairline); border-radius:var(--radius-sm); background:var(--surface-sunken); }
.wash__av{ flex:0 0 auto; width:34px; height:34px; border-radius:50%; display:grid; place-items:center;
      font-weight:700; font-size:.85rem; color:#fff; background:linear-gradient(120deg,#157a5b,#0f9d76); }
.wash__n{ font-weight:700; font-size:.92rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.wash__s{ font-size:var(--fs-caption); color:var(--fg-muted); }
.wash__when{ font-size:var(--fs-caption); color:var(--fg-muted); margin:4px 0 0; }
.wash__ta{ min-height:150px; resize:vertical; font-size:.9rem; line-height:1.5; border-radius:12px;
      background:#e7ffdb; border-color:#cbe6bd; color:#0f172a; }
.wash__ta:focus{ background:#eefee6; border-color:var(--kz); }
html[data-theme="dark"] .wash__ta{ background:#1f3d2c; border-color:#2f5d42; color:#e9f7ef; }
html[data-theme="dark"] .wash__ta:focus{ background:#234631; }
.wash__ct{ display:flex; justify-content:space-between; gap:8px; flex-wrap:wrap; margin-top:4px; /* --fg-muted: --fg-faint measured 3.36:1 on the dark card */
      font-size:var(--fs-caption); color:var(--fg-muted); font-variant-numeric:tabular-nums; }
.wash__gaps{ color:var(--warn); }
.wash__gaps[hidden]{ display:none; }
.wash__acts{ display:flex; gap:8px; flex-wrap:wrap; align-items:center; }
.wash__note{ font-size:var(--fs-caption); color:var(--fg-muted); margin:0; }
.wash__done{ font-size:.82rem; color:var(--good); font-weight:600; }
.wash__done[hidden]{ display:none; }
.wac__comp .wash__ta{ min-height:120px; }
/* the client's own templates on the WhatsApp page */
.wa-tpl__ta{ font-size:.84rem; line-height:1.45; }
.wa-tpl--new{ border-style:dashed; }

/* 🔴 A MODAL MUST OUTRANK THE PANEL. Maximised, the dock sits at 1055 — the same
   layer Bootstrap gives a modal — so opening Edit while maximised put the form BEHIND the
   conversation. 1040 keeps the dock above the page and below every dialog. */
.wa-dock-max .wadock{ z-index:1040; }
@media (max-width:991.98px){
  .dbt-bulk{ align-items:stretch; flex-direction:column; }
  .dbt-bulk .ms-auto{ margin-left:0 !important; }
}
.wac__tpl-b:disabled .wac__tpl-n{ opacity:.8; }
.wac__tpl-x{ font-size:var(--fs-caption); color:var(--fg-muted); line-height:1.42;
      display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical;
      overflow:hidden; }

/* ⚠ Below 992px the two panes become ONE, showing the list or the thread but never both
   squeezed side by side — a 160px-wide conversation column is unreadable and a 160px
   thread is worse. `.is-open` is set by the server when a thread is selected, so the back
   arrow in the thread header is the way out. */
@media (max-width:991.98px){
  .wac{ grid-template-columns:minmax(0,1fr); --wac-h:calc(100vh - 240px); }
  .wac.is-open .wac__list{ display:none; }
  .wac:not(.is-open) .wac__pane{ display:none; }
}
html[data-theme="dark"] .wac__av{ color:#0f172a; }
html[data-theme="dark"] .wac__q{ background:var(--surface-raised); }

/* ── The conversation dock ─────────────────────────────────────────────────────────────
   A fixed right-hand panel that PUSHES the page rather than covering it: the agent reads a
   customer's reply and that customer's bills at the same time, so hiding the register would
   remove the reason they opened the conversation. `--wa-dock-w` is set on <html> by the JS
   and drives both the panel and the main column, so the two can never disagree. */
:root{ --wa-dock-w:0px; }
.wadock{ position:fixed; top:var(--wa-dock-top,56px); right:0; bottom:0; width:var(--wa-dock-w);
      z-index:1035; display:flex; flex-direction:column; background:var(--surface-card);
      border-left:1px solid var(--hairline); box-shadow:-8px 0 26px rgba(15,23,42,.10); }
html[data-theme="dark"] .wadock{ box-shadow:-8px 0 26px rgba(0,0,0,.42); }
.wadock[hidden]{ display:none; }
/* the page gives up exactly the dock's width — no magic numbers, no double gutter */
html.wa-dock-on #main.page-wrap{ padding-right:calc(30px + var(--wa-dock-w)); }
html.wa-dock-on.wa-dock-max #main.page-wrap{ padding-right:30px; }
/* ⚠ Maximise fills the CONTENT area, not the screen. Covering the sidebar while leaving
   the topbar visible reads as a panel that has broken out of its frame, and it takes the
   navigation away from someone who is still working a register — they maximise to read a
   long thread, not to leave the section. `.content` carries the same 250px offset, so the
   two cannot drift; when the sidebar is collapsed or on a phone, that offset is 0. */
.wa-dock-max .wadock{ width:auto; left:250px; max-width:100%; }
html.sb-collapsed .wa-dock-max .wadock{ left:0; }
@media (max-width:991.98px){ .wa-dock-max .wadock{ left:0; } }

.wadock__hd{ display:flex; align-items:center; gap:8px; padding:9px 11px; flex:0 0 auto;
      border-bottom:1px solid var(--hairline); background:var(--surface-raised); }
.wadock__t{ font-size:.86rem; font-weight:700; display:flex; align-items:center; gap:6px;
      white-space:nowrap; overflow:hidden; }
.wadock__acts{ margin-left:auto; display:flex; align-items:center; gap:2px; }
.wadock__b{ display:grid; place-items:center; width:27px; height:27px; border-radius:8px;
      border:0; background:transparent; color:var(--fg-muted); cursor:pointer;
      text-decoration:none; font-size:.82rem; transition:all var(--dur-fast) var(--ease); }
.wadock__b:hover{ background:var(--surface-sunken); color:var(--fg); }
.wadock__b.is-on{ background:var(--kz); color:#fff; }
.wadock__body{ flex:1 1 auto; overflow:hidden; padding:10px; display:flex; }
.wadock__body > .wac{ flex:1 1 auto; min-width:0; }
.wadock__load{ margin:auto; display:flex; align-items:center; gap:9px; color:var(--fg-muted);
      font-size:.85rem; }

/* the drag handle. ⚠ It is also focusable and answers the arrow keys — a control that can
   ONLY be dragged is unusable without a mouse, and this one sets how much of the screen the
   agent's main tool gets. */
.wadock__grip{ position:absolute; left:-3px; top:0; bottom:0; width:7px; cursor:col-resize;
      z-index:2; }
.wadock__grip::after{ content:""; position:absolute; left:2px; top:0; bottom:0; width:3px;
      border-radius:3px; background:transparent; transition:background var(--dur-fast) var(--ease); }
.wadock__grip:hover::after,.wadock__grip:focus-visible::after,
.wa-dock-drag .wadock__grip::after{ background:var(--kz); }
.wa-dock-max .wadock__grip{ display:none; }
/* ⚠ while dragging, the iframe-less page still selects text under the cursor without this */
.wa-dock-drag{ user-select:none; }
.wa-dock-drag *{ cursor:col-resize !important; }

/* the reopen tab — a closed dock must never be a one-way door */
.wadock__tab{ position:fixed; right:0; top:50%; transform:translateY(-50%); z-index:1030;
      border:0; border-radius:12px 0 0 12px; padding:12px 9px; cursor:pointer; color:#fff;
      background:linear-gradient(135deg,var(--kz),var(--kz2)); box-shadow:var(--elev-2);
      display:flex; flex-direction:column; align-items:center; gap:5px; font-size:1.02rem; }
.wadock__tab[hidden]{ display:none; }
.wadock__tab-n{ font-size:var(--fs-caption); font-weight:700; background:#fff;
      color:var(--bad); border-radius:999px; min-width:18px; padding:0 4px; line-height:1.5; }
.wadock__tab-n[hidden]{ display:none; }

/* Docked, the console is narrow, so it behaves exactly as the full page does on a phone:
   the list OR the thread, never both squeezed side by side. Same rule, same CSS, reached by
   a class instead of a media query — not a second layout to keep in step. */
.wac--dock{ grid-template-columns:minmax(0,1fr); --wac-h:100%; height:100%; }
.wac--dock .wac__list,.wac--dock .wac__pane{ height:100%; border:0; box-shadow:none; }
.wac--dock.is-open .wac__list{ display:none; }
.wac--dock:not(.is-open) .wac__pane{ display:none; }
.wac--dock .wac__tpls{ grid-template-columns:minmax(0,1fr); }
/* maximised there IS room for both, so give it back rather than wasting the width */
.wa-dock-max .wac--dock{ grid-template-columns:320px minmax(0,1fr); gap:12px; }
.wa-dock-max .wac--dock .wac__list{ display:flex; border:1px solid var(--hairline); }
.wa-dock-max .wac--dock .wac__pane{ display:flex; border:1px solid var(--hairline); }
.wa-dock-max .wac--dock .wac__tpls{ grid-template-columns:repeat(auto-fit,minmax(228px,1fr)); }
.wa-dock-max .wac__back{ display:none; }
/* the full-page view has its own back button at ≤991px; the dock always needs one */
.wac:not(.wac--dock) .wac__back{ display:none; }
@media (max-width:991.98px){
  .wac:not(.wac--dock) .wac__back{ display:inline-flex; }
  /* ⚠ On a phone a side-by-side dock is nonsense — it takes the whole screen or nothing. */
  html.wa-dock-on #main.page-wrap{ padding-right:14px; }
  /* 100vw and a min-width of 0 — the sweep found the dock 23px wider than a 360px phone,
     its header row refusing to shrink (2026-09-09) */
  .wadock{ width:100vw; max-width:100vw; min-width:0; left:0; }
  .wadock__hd{ flex-wrap:wrap; min-width:0; }
  .wadock__hd > *{ min-width:0; }
}

/* ============================================================================
   Site Progress — live project tracking (2026-09-09). Built on the tokens: the completion
   bar, project cards, the pipeline list, update cards with photo strips. Dark-paired.
   ============================================================================ */
.sp-live{ display:inline-flex; align-items:center; gap:6px; margin-left:10px; font-size:.78rem; font-weight:600; color:var(--ok); }
.sp-live .bi{ animation:spPulse 2s ease-in-out infinite; }
@keyframes spPulse{ 0%,100%{opacity:1} 50%{opacity:.35} }
.sp-bar{ height:10px; border-radius:999px; background:var(--surface-sunken); overflow:hidden; }
.sp-bar.sm{ height:7px; flex:1 1 auto; }
.sp-bar__fill{ display:block; height:100%; border-radius:999px; background:var(--kz-grad, linear-gradient(120deg,var(--kz),var(--kz2) 55%,var(--accent))); transition:width .6s cubic-bezier(.16,1,.3,1); }
.sp-bar__fill.is-done{ background:var(--ok); }
.sp-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:14px; }
.sp-card{ display:flex; flex-direction:column; gap:10px; padding:18px; text-decoration:none; color:inherit; transition:transform .16s, box-shadow .16s; }
.sp-card:hover{ transform:translateY(-2px); box-shadow:var(--elev-2); color:inherit; }
.sp-card__top{ display:flex; align-items:flex-start; justify-content:space-between; gap:10px; }
.sp-card__name{ font-family:var(--font-display); font-weight:700; font-size:1.06rem; letter-spacing:-.015em; }
.sp-card__pct{ display:flex; align-items:baseline; gap:10px; }
.sp-card__big{ font-family:var(--font-display); font-weight:700; font-size:2rem; letter-spacing:-.03em; line-height:1; }
.sp-card__stage{ font-size:.92rem; }
.sp-card__meta{ display:flex; flex-wrap:wrap; gap:6px 14px; font-size:.82rem; color:var(--fg-muted); }
.sp-card__last{ border-top:1px solid var(--hairline); padding-top:8px; }
.sp-hero__pct{ font-family:var(--font-display); font-weight:700; font-size:2.6rem; letter-spacing:-.035em; line-height:1.05; margin:4px 0 10px; }
.sp-pipe{ list-style:none; margin:0; padding:0; }
.sp-stage{ display:flex; gap:14px; padding:14px 16px; border-bottom:1px solid var(--hairline); }
.sp-stage:last-child{ border-bottom:0; }
.sp-stage__n{ flex:0 0 30px; width:30px; height:30px; border-radius:50%; display:grid; place-items:center; font-weight:700; font-size:.82rem; background:var(--surface-sunken); color:var(--fg-muted); }
.sp-stage.is-active .sp-stage__n{ background:var(--kz); color:#fff; box-shadow:0 0 0 4px color-mix(in srgb,var(--kz) 18%,transparent); }
.sp-stage.is-done .sp-stage__n{ background:var(--ok); color:#fff; }
.sp-stage__body{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:6px; }
.sp-stage__head{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.sp-stage__act{ display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-top:4px; }
.sp-act{ display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
.sp-act--prog{ background:var(--surface-sunken); border-radius:12px; padding:8px 10px; }
.sp-stage__edit{ display:flex; flex-wrap:wrap; gap:6px; align-items:center; margin-top:6px; padding-top:8px; border-top:1px dashed var(--hairline); }
.sp-team{ display:flex; flex-wrap:wrap; gap:6px 16px; }
.sp-feed{ display:flex; flex-direction:column; gap:10px; }
.sp-feed--wide .sp-upd{ max-width:920px; }
.sp-upd{ background:var(--surface-card); border:1px solid var(--hairline); border-radius:14px; padding:12px 14px; display:flex; flex-direction:column; gap:8px; }
.sp-upd.is-flagged{ border-color:color-mix(in srgb,var(--warn) 45%,var(--hairline)); background:color-mix(in srgb,var(--warn) 5%,var(--surface-card)); }
.sp-upd__head{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.sp-upd__note{ margin:0; white-space:pre-wrap; }
.sp-upd__foot{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.sp-photos{ display:grid; grid-template-columns:repeat(auto-fill,minmax(110px,1fr)); gap:6px; }
.sp-photo{ display:block; aspect-ratio:4/3; border-radius:10px; overflow:hidden; background:var(--surface-sunken); }
.sp-photo img{ width:100%; height:100%; object-fit:cover; display:block; transition:transform .3s; }
.sp-photo:hover img{ transform:scale(1.04); }
@media (max-width:640px){ .sp-stage{ padding:12px; gap:10px; } .sp-act--prog{ width:100%; } .sp-card__big{ font-size:1.7rem; } .sp-hero__pct{ font-size:2.2rem; } }

/* ---- Table navigator (2026-09-09): jump / fast-scroll through long registers ------------ */
.kz-tnav{ position:fixed; left:14px; bottom:16px; z-index:1035;
  display:none; align-items:center; gap:2px; padding:4px; border-radius:999px;
  background:var(--surface-card); border:1px solid var(--hairline-2, var(--border)); box-shadow:var(--elev-3, var(--shadow-lg)); }
.kz-tnav.is-on{ display:flex; }
/* room for the pill: it is fixed bottom-left, where the last row of controls lands when the page is
   scrolled to its end, so the page grows by the pill's height while it is on (audit 2026-09-11) */
body.has-tnav #main{ padding-bottom:88px; }
.kz-tnav button{ width:34px; height:34px; border:0; border-radius:50%; background:transparent; color:var(--fg, var(--ink)); cursor:pointer;
  display:grid; place-items:center; font-size:1rem; transition:background .12s; }
.kz-tnav button:hover{ background:var(--surface-sunken); }
.kz-tnav button:active{ background:color-mix(in srgb, var(--kz) 18%, transparent); color:var(--kz); }
.kz-tnav__pos{ font-family:var(--font-mono); font-size:.74rem; color:var(--fg-muted); padding:0 8px; white-space:nowrap; min-width:110px; text-align:center; }
.kz-tnav__go input{ width:64px; height:30px; border:1px solid var(--hairline-2, var(--border)); border-radius:999px; background:var(--surface-sunken); color:var(--fg, var(--ink));
  font-size:.78rem; padding:0 10px; text-align:center; -moz-appearance:textfield; }
.kz-tnav__go input::-webkit-outer-spin-button, .kz-tnav__go input::-webkit-inner-spin-button{ -webkit-appearance:none; }
tr.kz-tnav-hit > td{ box-shadow:inset 0 0 0 999px color-mix(in srgb, var(--kz) 12%, transparent); transition:box-shadow .6s; }
@media print{ .kz-tnav{ display:none !important; } }
@media (max-width:640px){ .kz-tnav{ left:10px; bottom:10px; } .kz-tnav__pos{ min-width:0; padding:0 4px; font-size:.68rem; } .kz-tnav__go{ display:none; } }

/* ============================================================================
   Guided demo (2026-09-10): the sidebar lists the tour's stops and nothing else. */
.sidebar .tour-step{ display:flex; align-items:flex-start; gap:10px; }
.sidebar .tour-n{ flex:0 0 22px; height:22px; border-radius:50%; background:rgba(255,255,255,.14); color:#fff;
  font-size:.72rem; font-weight:700; display:grid; place-items:center; margin-top:2px; }
.sidebar .tour-step.active .tour-n{ background:#fff; color:#3730A3; }
.sidebar .tour-t{ display:flex; flex-direction:column; min-width:0; line-height:1.25; }
.sidebar .tour-t small{ font-size:.72rem; opacity:.75; white-space:normal; }

/* ============================================================================
   Sales Office — the build ledger (2026-10-02). Built on the tokens, dark-paired
   through them.
   The five --so-N slots are the chart palette, in a FIXED order (material, labour,
   material + labour, machine, service) and never cycled: a sixth type would fold into
   "other", not take a generated hue. Checked against both card surfaces for
   colour-blind separation; on the light card three of them sit under 3:1, which is why
   every figure they colour is also printed as text beside the mark.
   ============================================================================ */
:root{ --so-1:#2a78d6; --so-2:#eb6834; --so-3:#1baf7a; --so-4:#eda100; --so-5:#e87ba4;
  --so-plan:#aab1bd; --so-plan2:#d8dce3; --so-late:#C9342F; }
html[data-theme="dark"]{ --so-1:#3987e5; --so-2:#d95926; --so-3:#199e70; --so-4:#c98500; --so-5:#d55181;
  --so-plan:#66728a; --so-plan2:#3d4759; --so-late:#E0554F; }
.so-strip{ display:flex; flex-wrap:wrap; align-items:center; gap:6px 16px; margin:.3rem 0 1rem;
  font-size:.84rem; color:var(--fg-muted); }
.so-strip b{ color:var(--fg); font-weight:600; }
.so-strip .form-select, .so-strip .kzs{ width:auto; min-width:180px; }
/* money still owed: the app's own warning ink, which holds AA on both card surfaces */
.so-due{ color:var(--warn); }
/* a figure that is wrong or late: the app's negative ink (AA on the zebra row too, which
   Bootstrap's .text-danger misses at 4.26:1) */
.so-bad{ color:var(--bad); }
/* Get the app */
.so-appicon{ border-radius:16px; flex:0 0 auto; }
.so-steps{ margin:0; padding-left:1.25rem; display:grid; gap:8px; }
.so-steps li{ padding-left:4px; }
.so-facts{ list-style:none; margin:0; padding:0; display:grid; gap:12px; font-size:.92rem; }
.so-facts li{ padding-left:14px; border-left:2px solid var(--hairline-2, var(--border)); color:var(--fg-muted); }
.so-facts b{ color:var(--fg); }
.so-key-ic{ display:inline-grid; place-items:center; width:26px; height:26px; border-radius:7px; vertical-align:middle;
  background:var(--mut-bg); color:var(--fg); }
.so-url{ display:inline-block; padding:7px 12px; border-radius:10px; background:var(--surface-sunken); color:var(--fg);
  font-family:var(--font-mono); font-size:.9rem; }
/* Save appears once the grid has been unlocked — before that there is nothing to save. It
   STAYS after "Done editing": Done then Save is the natural order, and Save must be there. */
form:not(:has(table[data-kz-edited])) .so-save{ display:none; }
.so-meter{ height:4px; border-radius:3px; background:var(--surface-sunken); overflow:hidden; margin-top:8px; }
.so-meter > i{ display:block; height:100%; border-radius:3px; background:var(--so-1); }
.so-meter > i.over{ background:var(--so-late); }
/* a ranked list: the name, its figure, its share, and one bar — magnitude in ONE hue */
.so-rank{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:11px; }
.so-rank li{ display:grid; grid-template-columns:minmax(0,1fr) auto 48px; column-gap:10px; row-gap:5px;
  align-items:baseline; font-size:.9rem; }
.so-rank .t{ font-weight:600; color:inherit; text-decoration:none; overflow-wrap:anywhere; min-width:0; }
.so-rank a.t:hover{ color:var(--kz); text-decoration:underline; }
.so-rank .v{ font-family:var(--font-mono); font-variant-numeric:tabular-nums; font-weight:600; white-space:nowrap; }
.so-rank .p{ text-align:right; font-size:.78rem; color:var(--fg-muted); font-variant-numeric:tabular-nums; }
.so-rank .b{ grid-column:1 / -1; height:6px; border-radius:4px; background:var(--surface-sunken); overflow:hidden; }
.so-rank .b > i{ display:block; height:100%; min-width:2px; border-radius:4px; background:var(--so-1); }
/* the split by type of cost: adjacent fills are separated by a 2px gap of the surface */
.so-split{ display:flex; gap:2px; height:12px; border-radius:6px; overflow:hidden; }
.so-split > i{ display:block; height:100%; min-width:3px; }
.so-key{ display:flex; flex-wrap:wrap; gap:6px 16px; margin:10px 0 0; padding:0; list-style:none;
  font-size:.82rem; color:var(--fg-muted); }
.so-key li{ display:inline-flex; align-items:center; gap:6px; }
.so-key i{ width:10px; height:10px; border-radius:3px; flex:0 0 auto; }
.so-key b{ color:var(--fg); font-weight:600; font-family:var(--font-mono); font-variant-numeric:tabular-nums; }
.so-c1{ background:var(--so-1); } .so-c2{ background:var(--so-2); } .so-c3{ background:var(--so-3); }
.so-c4{ background:var(--so-4); } .so-c5{ background:var(--so-5); }
/* billed against paid, month by month. The plot has a definite height, so a bar's own
   percentage resolves; each month is two bars, 2px apart, standing on one baseline. */
.so-months{ display:flex; align-items:flex-end; gap:10px; height:168px; padding:6px 2px 0;
  border-bottom:1px solid var(--hairline-2, var(--border)); }
.so-month{ flex:1 1 0; min-width:0; height:100%; display:flex; align-items:flex-end; justify-content:center; gap:2px; }
.so-month > i{ display:block; flex:0 1 22px; min-width:5px; min-height:2px; border-radius:4px 4px 0 0; }
.so-month > i.b{ background:var(--so-1); } .so-month > i.p{ background:var(--so-2); }
.so-months-x{ display:flex; gap:10px; padding:5px 2px 0; }
.so-months-x span{ flex:1 1 0; min-width:0; text-align:center; font-size:.72rem; color:var(--fg-muted); white-space:nowrap; }
/* the side lists on the overview: who is owed, what is running late, what just came in */
.so-list{ list-style:none; margin:0; padding:0; }
.so-list li{ display:flex; align-items:center; gap:10px; padding:10px 0; border-bottom:1px solid var(--hairline); }
.so-list li:first-child{ padding-top:0; } .so-list li:last-child{ border-bottom:0; padding-bottom:0; }
.so-list .g{ flex:1 1 auto; min-width:0; }
.so-list .t{ font-weight:600; overflow-wrap:anywhere; color:inherit; text-decoration:none; }
.so-list a.t:hover{ color:var(--kz); }
.so-list .s{ font-size:.78rem; color:var(--fg-muted); }
.so-list .n{ flex:0 0 auto; text-align:right; font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  font-weight:600; white-space:nowrap; }
.so-totals{ display:flex; flex-wrap:wrap; gap:6px 20px; font-size:.84rem; color:var(--fg-muted); }
.so-totals b{ color:var(--fg); font-family:var(--font-mono); font-variant-numeric:tabular-nums; font-weight:600; }
.so-filters{ display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
.so-filters .form-control, .so-filters .form-select{ width:auto; min-width:0; }
/* the custom select wraps each <select> in a full-width block; in the filter bar it is one of
   several controls on a line, so it takes a share of the row instead */
.so-filters .kzs{ width:auto; flex:0 1 168px; min-width:130px; }
.so-filters .so-q{ flex:1 1 220px; }
.so-paylog{ list-style:none; margin:0; padding:0; font-size:.86rem; }
.so-paylog li{ display:flex; justify-content:space-between; gap:12px; padding:6px 0; border-bottom:1px dashed var(--hairline); }
.so-paylog li:last-child{ border-bottom:0; }
.so-bal{ font-family:var(--font-display); font-weight:700; font-size:1.2rem; font-variant-numeric:tabular-nums; }
/* planned against actual: every position is a % of the chart's own span */
.so-gantt{ overflow-x:auto; }
.so-g{ min-width:820px; }
.so-g__head, .so-g__row{ display:grid; grid-template-columns:250px minmax(0,1fr); }
.so-g__head{ border-bottom:1px solid var(--hairline); }
.so-g__hname{ align-self:end; padding:8px 14px; font-size:var(--fs-label); letter-spacing:.07em;
  text-transform:uppercase; font-weight:700; color:var(--fg-muted); }
/* three rows, so the three kinds of label never sit on each other: months, the TODAY tag, weeks */
.so-g__scale{ position:relative; height:58px; }
.so-g__month{ position:absolute; top:4px; height:16px; line-height:16px; padding-left:6px;
  border-left:1px solid var(--hairline-2, var(--border)); font-size:.74rem; font-weight:600;
  color:var(--fg-muted); white-space:nowrap; }
.so-g__week{ position:absolute; bottom:4px; transform:translateX(-50%); font-size:.68rem; color:var(--fg-muted); }
.so-g__row{ border-bottom:1px solid var(--hairline); color:inherit; text-decoration:none; }
.so-g__row:last-child{ border-bottom:0; }
a.so-g__row:hover{ background:var(--surface-sunken); color:inherit; }
.so-g__name{ min-width:0; padding:9px 14px; border-right:1px solid var(--hairline); }
.so-g__name .t{ font-weight:600; font-size:.88rem; overflow-wrap:anywhere; }
.so-g__name .s{ display:flex; flex-wrap:wrap; align-items:center; gap:4px 6px; margin-top:3px;
  font-size:.74rem; color:var(--fg-muted); }
.so-g__track{ position:relative; height:54px; }
.so-g__grid{ position:absolute; top:0; bottom:0; border-left:1px dashed var(--hairline); }
.so-g__today{ position:absolute; top:0; bottom:0; border-left:2px solid var(--so-late); z-index:2; }
.so-g__todaylbl{ position:absolute; top:23px; transform:translateX(-50%); z-index:3; padding:0 5px;
  border-radius:3px; background:#C9342F; color:#fff; font-size:.6rem; font-weight:700; letter-spacing:.04em; }
.so-g__bar{ position:absolute; height:13px; border-radius:4px; }
.so-g__bar.plan{ top:9px; border:1px solid var(--so-plan);
  background:repeating-linear-gradient(135deg, var(--so-plan) 0 4px, var(--so-plan2) 4px 8px); }
.so-g__bar.act{ top:28px; background:var(--so-1); }
.so-g__bar.act.late{ background:var(--so-late); }
.so-g__bar.act.open{ border-radius:4px 0 0 4px; }
.so-g__lab{ position:absolute; top:26px; padding-left:6px; font-size:.7rem; color:var(--fg-muted); white-space:nowrap; }
.so-sw{ display:inline-block; width:22px; height:9px; border-radius:3px; vertical-align:middle; margin-right:6px; }
.so-sw.plan{ border:1px solid var(--so-plan);
  background:repeating-linear-gradient(135deg, var(--so-plan) 0 4px, var(--so-plan2) 4px 8px); }
.so-sw.act{ background:var(--so-1); } .so-sw.late{ background:var(--so-late); }
.so-sw.today{ width:2px; height:14px; background:var(--so-late); }
.so-range{ display:flex; align-items:center; gap:10px; }
.so-range output{ flex:0 0 46px; text-align:right; font-family:var(--font-mono); font-weight:600; }
@media (max-width:575.98px){ .so-months{ gap:5px; height:140px; } .so-months-x{ gap:5px; }
  .so-months-x span{ font-size:.64rem; } .so-g__head, .so-g__row{ grid-template-columns:170px minmax(0,1fr); }
  .so-filters .form-control, .so-filters .form-select, .so-filters .kzs{ flex:1 1 140px; } }
@media print{ .so-g{ min-width:0; } .so-g__bar, .so-month > i, .so-rank .b > i, .so-split > i, .so-key i,
  .so-meter > i{ -webkit-print-color-adjust:exact; print-color-adjust:exact; } }


/* ==========================================================================
   PRINT REPAINT — dark theme onto white paper.  MUST BE LAST IN THIS FILE.
   Reported by the owner 2026-09-18: the MIS Report's Consolidated column printed BLANK for
   a furniture client. Nothing had failed to compute -- the figures were painted white on
   pale lavender, 1.02:1.
   🔴 WHY IT LIVES AT THE BOTTOM: these are colour overrides with `!important` at the
   same specificity as the dark-theme rules they replace, and a tie is broken by SOURCE
   ORDER. Written in the middle of the file they were silently undone by every
   `html[data-theme="dark"] .text-muted{...!important}` declared further down. Anything
   added below this block wins over it -- so nothing should be.
   Guard: `testing/print_contrast.py` (print media + dark seeded, which is what every client
   actually prints now that dark is the default).
   ========================================================================== */
@media print{
  /* 🔴 THE LINE ABOVE IS NOT ENOUGH ON ITS OWN, AND BELIEVING IT WAS COST A CLIENT A
     BLANK COLUMN (owner-reported 2026-09-18, MIS Report, furniture client).
     Two mechanisms defeat it:
       1. `body{color:...}` cannot repaint text inside a painted ancestor. Colour INHERITS
          from the nearest ancestor that sets it, and `.card` sets its own `#E2E8F0` in dark
          -- so `!important` on body loses to plain inheritance from a nearer element.
       2. A surface painted with a hard-coded hex (not a token) keeps that hex on paper.
     Result: a dark-theme viewer printed dark cards with light ink -- and wherever print ALSO
     forced a light background on one column, the figures went white-on-pale and VANISHED.
     The MIS "Consolidated" column is exactly that: `.mtot{background:#e7e8fb}` under inherited
     `#E2E8F0` ink is a contrast ratio of 1.02. Nothing was missing; it was painted invisible.
     ⚠ THIS REACHES EVERY CLIENT, because dark is the default for everyone (kizuna-ui \u00a70a) --
     it was never "a dark-mode user's problem".

     So print RESTORES THE LIGHT PALETTE rather than patching page by page: the tokens first
     (which fixes everything var()-driven at once), then the handful of surfaces whose dark
     paint is a literal hex. Semantic ink -- a red loss, a status tone, the brand accent --
     keeps its own colour, because that is information and not decoration. */
  html[data-theme="dark"]{
    --surface-ground:#F5F7FB; --surface-card:#FFFFFF; --surface-raised:#FFFFFF; --surface-sunken:#EEF1F8;
    --hairline:#E5E9F2; --hairline-2:#D9DEEC;
    --fg:#0F1626; --fg-muted:#5A6576; --fg-faint:#8A94A6;
    --muted:#5A6576; --border:#e7ebf3; --ink:#0F172A;
    --good:#0E9F6E; --good-bg:#E6F6EF; --warn:#9a6a16; --warn-bg:#fbf1dc;
    --bad:#c4362f;  --bad-bg:#fdeceb;  --info:#1565a6; --info-bg:#e8f1fb;
    --ok:#157a5b;   --ok-bg:#e6f6ef;   --mut-bg:#eef1f5;
    --acc-teal:#157a6e; --acc-purple:#6a1b9a; --acc-clay:#c1503a;
    --acc-ochre:#9a6a16; --acc-blue:#1565a6; --acc-slate:#64748b;
    --bs-secondary-color:#5A6576;
    --elev-1:none; --elev-2:none; --elev-3:none;
  }
  /* 🔴 BOOTSTRAP HAS A DARK THEME OF ITS OWN, and it is a SECOND source of dark paint
     that no sweep of our own rules can see. `base.html` sets `data-bs-theme="dark"` beside
     our `data-theme`, and Bootstrap then paints tables, close buttons, form text and
     borders from its own variables — which is where the #212529 slab under "Less: COGS"
     came from on the Overview. Flip its variables too, or half the repaint below is
     undone by a stylesheet we do not own. */
  [data-bs-theme="dark"]{
    --bs-body-bg:#fff; --bs-body-color:#0f172a; --bs-emphasis-color:#0f172a;
    --bs-secondary-bg:#eef1f7; --bs-tertiary-bg:#f6f8fc;
    --bs-secondary-color:#475569; --bs-tertiary-color:#64748b;
    --bs-table-bg:transparent; --bs-table-color:#0f172a; --bs-table-striped-color:#0f172a;
    --bs-table-border-color:#d9deec; --bs-border-color:#cbd2df;
    --bs-link-color:#4f46e5; --bs-link-hover-color:#4338ca;
    --bs-heading-color:#0f172a;
  }

  /* surfaces whose dark paint is a literal hex, so no token flip can reach them */
  html[data-theme="dark"] .card,
  html[data-theme="dark"] .stat-card,
  html[data-theme="dark"] .list-group-item,
  html[data-theme="dark"] .kz-note,
  html[data-theme="dark"] .bg-white:not(.badge),
  html[data-theme="dark"] .table,
  html[data-theme="dark"] .fin-table,
  html[data-theme="dark"] .fin-stmt,
  html[data-theme="dark"] .tb-tbl,
  html[data-theme="dark"] table.mis{ background:#fff !important; color:#0f172a !important; }
  html[data-theme="dark"] .table thead th,
  html[data-theme="dark"] .fin-table thead th,
  html[data-theme="dark"] .fin-stmt thead th,
  html[data-theme="dark"] .tb-tbl thead th,
  html[data-theme="dark"] table.mis thead th{ background:#eef1f7 !important; color:#334155 !important; }
  html[data-theme="dark"] .tb-tbl tfoot td,
  html[data-theme="dark"] .fin-table tfoot td{ background:#f3f5fa !important; color:#0f172a !important; }
  html[data-theme="dark"] .tb-tbl tbody tr:hover td,
  html[data-theme="dark"] .table-hover tbody tr:hover > *{ background:transparent !important; }
  html[data-theme="dark"] .stat-card .v,
  html[data-theme="dark"] .empty-state h6{ color:#0f172a !important; }
  html[data-theme="dark"] .stat-card .l,
  html[data-theme="dark"] .stat-card .d,
  html[data-theme="dark"] .text-secondary,
  html[data-theme="dark"] .text-muted{ color:#5A6576 !important; }
  html[data-theme="dark"] .text-success{ color:#157a5b !important; }
  /* ⚠ A LOSS ON A TINTED CELL. The light-mode red is 4.43:1 on the MIS Consolidated
     column's lavender -- under AA by a hair, and only there, because every other surface it
     lands on is plain white. The tint is what the column is for, so the INK darkens rather
     than the column flattening. */
  .text-danger, html[data-theme="dark"] .text-danger{ color:#a32b25 !important; }
  html[data-theme="dark"] .text-warning{ color:#9a6a16 !important; }
  html[data-theme="dark"] .bs-mut{ color:#566 !important; }
  /* ⚠ a hairline that was authored for a dark ground is invisible on paper */
  html[data-theme="dark"] .card{ border-color:#cbd2df !important; }
  /* ⚠ AND THE LIFTED INKS, which the print sweep found once the invisible column was gone.
     #A5B4FC is the indigo raised to read on a DARK ground; on white paper it is 1.99:1 --
     and that is every page and card HEADING in the product, the first thing a client reads.
     Same story for the dark link blue and the outline-button ink. Not a separate defect from
     the one above: the same one, one layer further in. */
  html[data-theme="dark"] .page-title,
  html[data-theme="dark"] .kz-note > summary,
  html[data-theme="dark"] .kz-scrollx + .kz-swipe,
  html[data-theme="dark"] .acc-todo__go,
  html[data-theme="dark"] .todo-col__pct{ color:var(--kz) !important; background:none !important; }
  html[data-theme="dark"] a,
  html[data-theme="dark"] .btn-link{ color:var(--kz) !important; }
  /* ⚠ REPAINT THE SURFACE IN THE SAME BREATH AS THE INK. Darkening the button text
     without lightening `.btn-light`'s #243246 traded 58 low-contrast readings for 12
     INVISIBLE ones — a straight downgrade, caught only because the sweep grades those
     separately. An ink rule and a surface rule are one change, never two. */
  html[data-theme="dark"] .btn,
  html[data-theme="dark"] .btn-light,
  html[data-theme="dark"] .hero-chip,
  html[data-theme="dark"] .kz-chord{ color:#0f172a !important; background:#fff !important;
         --bs-btn-color:#0f172a; --bs-btn-bg:#fff;
         border-color:#cbd2df !important; --bs-btn-border-color:#cbd2df; }
  html[data-theme="dark"] .table-light,
  html[data-theme="dark"] .table-active{ --bs-table-bg:#eef1f7; --bs-table-color:#0f172a; }

  /* 🔴 EVERY DARK-PAINTED SURFACE AND EVERY LIFTED INK, GENERATED FROM THIS
     STYLESHEET rather than listed by hand. A hand-written list missed four surfaces
     (#0f1a2e, #131c2b and two more) and the client would have found them one report at a
     time. This is the output of walking every `html[data-theme="dark"]` block and measuring
     the LUMINANCE of what it paints: a background under 0.25 is a dark surface, a colour
     over 0.55 is an ink lifted for a dark ground, and a desaturated colour over 0.22 is a
     mid slate — which reads as a quiet caption on a card and as 2.4:1 on paper.
     ⚠ A colour written as `var(--e2,#E2E8F0)` is still a colour: matching only a bare hex
     missed `.ea-legend-val b`, which printed white on white.
     ⚠ SEMANTIC FILLS ARE EXCLUDED ON PURPOSE — a reconciliation bar, a status tone, a
     locked week. There the colour IS the information, and flattening it deletes it.
     ⚠ A SURFACE AND ITS INK MOVE TOGETHER: repainting ink alone turned 58 low-contrast
     readings into 12 invisible ones, which is a downgrade, not a partial fix.
     Regenerate with scratchpad/gen_print_rule.py when the dark theme gains a surface;
     `testing/print_contrast.py` is what tells you it did. */
  html[data-theme="dark"] :is(
    .bg-light:not(.badge), .seg-tab, .form-control,
    .form-select, .grid-input, .input-group-text,
    .onboard-step, .sk, .table-sort tr[data-href],
    .plan-card, .plan-card.featured, .plan-card.inactive,
    .rt-stat, .dbt-detail, .dt-day .dt-dot,
    .dt-day, .dt-mile, .tb-tbl thead th,
    .tb-tbl tfoot td, .inv-cat, .tb-tbl tbody tr.inv-avg td,
    .wc-flag, .wc-flag td, .inv-tbl tbody .inv-uom,
    .inv-tbl tbody tr.inv-avg .inv-uom, .exp-head, .exp-tbl tbody .exp-type,
    .ac-m, .ac-tbl .ac-c1, .ac-tbl .ac-c2,
    .ac-tbl .ac-c3, .ac-tbl .ac-c4, .ac-tbl tbody tr td.ac-c1,
    .ac-tbl tbody tr td.ac-c2, .ac-tbl tbody tr td.ac-c3, .ac-tbl tbody tr td.ac-c4,
    .ac-tbl tbody tr:nth-child(even) td.ac-c1, .ac-tbl tbody tr:nth-child(even) td.ac-c2, .ac-tbl tbody tr:nth-child(even) td.ac-c3,
    .ac-tbl tbody tr:nth-child(even) td.ac-c4, .ac-tbl thead .ac-c1, .ac-tbl thead .ac-c2,
    .ac-tbl thead .ac-c3, .ac-tbl thead .ac-c4, .ac-tbl tr.ac-grp td,
    .pc-cre, .py-addfirm, .pl-tbl .pl-c1,
    .pl-tbl thead .pl-c1, .pl-tbl tbody tr.table-light .pl-c1, .hm .hm-head,
    .hm-cell, .hm-cell.zero, .ea-track,
    .ea-ba, .ea-bud, .table .ea-trow td,
    .ea-var-pro thead th, .ea-var-pro .ea-trow td, .tb-tbl .tb-act,
    .tb-tbl .tb-no, .tb-tbl .tb-name, .tb-tbl .tb-grp,
    .tb-tbl thead .tb-act, .tb-tbl thead .tb-no, .tb-tbl thead .tb-name,
    .tb-tbl thead .tb-grp, .tb-tbl tfoot .tb-act, .tb-tbl tfoot .tb-no,
    .tb-tbl tfoot .tb-name, .tb-tbl tfoot .tb-grp, .tb-tbl .tb-bulk,
    .tb-tbl thead .tb-bulk, .tb-tbl tfoot .tb-bulk, .bs-subblock,
    .fh-note, .cogs-tbl tbody tr.cogs-row td, .cogs-tbl tbody tr.cogs-row .inv-uom,
    .cogs-tbl .cogs-gap, .cogs-tbl thead th.cogs-gap, .gw-week,
    .gw-week.locked, .gw-key, .gw-msg,
    .cf-files a, .gw-wpill, .gw-wpill.locked,
    .cf-card, .cf-nav, .doc-edit-box,
    table.mis thead th, .mise-tbl thead th
  ){ background:#fff !important; border-color:#cbd2df !important; }
  html[data-theme="dark"] :is(
    .kz-um-role, .quick-link, .form-control,
    .form-select, .grid-input, .input-group-text,
    .onboard-step, .plan-card, .dt-preview-banner,
    .tb-tbl tfoot td, .exp-type-tag, .ac-m.is-on,
    .ac-tbl .ac-wk.is-now, .ac-tbl thead .ac-d.is-today, .ea-legend-val b,
    .hm-cell, .rc-foot b, .rc2-lg b,
    .rc-back-d, .rc-vals span.on, .gw-week,
    .cf-files a, .gw-wpill, .wl-text,
    .cf-card, .cf-note span
  ){ color:#0f172a !important; }
  html[data-theme="dark"] :is(
    .kz-um-sub, .kz-seglegend .it, .stat-card .l,
    .stat-card .d, .seg-tab, .sch-grip,
    .text-muted, .plan-feats li.muted-feat, select.form-select:required:invalid,
    .text-secondary, .bs-mut, .settings-tabs .nav-link,
    .pl-pct, .cat-uom, .gw-week.locked,
    .gw-wpill.locked, .cf-tasks li.done .t, .cf-tasks li .d,
    .kz-kpi__l
  ){ color:#475569 !important; }
  html[data-theme="dark"] #rtLegend .chl-btn{ color:#0f172a !important; }
  html[data-theme="dark"] #expTrendLegend .etl-btn{ color:#0f172a !important; }
  /* the frozen / header / total cells of the month-wide grids: a quiet grey, not a dark slab */
  html[data-theme="dark"] :is(.tb-tbl, .inv-tbl, .ac-tbl, .pl-tbl, .ea-var-pro, .hm) :is(
    thead th, tfoot td, tr.ac-grp td, tr.inv-avg td, .ea-trow td, .hm-head,
    .tb-act, .tb-no, .tb-name, .tb-grp, .tb-bulk, .ac-c1, .ac-c2, .ac-c3, .ac-c4,
    .pl-c1, .inv-uom
  ){ background:#eef1f7 !important; color:#334155 !important; border-color:#d9deec !important; }

  /* 🔴 `--bs-table-color` IS SET ON `.table` ITSELF, so flipping it at the root does
     nothing: Bootstrap paints every cell with
     `.table>:not(caption)>*>*{color:var(--bs-table-color...)}`, which beats anything
     inherited from the card. The variable has to be re-set on the same element that
     declares it. This is what kept "Operating" and the P&L figures white on white paper
     after the surface beneath them had already been fixed. */
  html[data-theme="dark"] .table,
  html[data-theme="dark"] .fin-table,
  html[data-theme="dark"] .tb-tbl{ --bs-table-color:#0f172a !important;
        --bs-table-bg:transparent !important; --bs-table-striped-color:#0f172a !important;
        --bs-table-active-color:#0f172a !important; --bs-table-hover-color:#0f172a !important; }

  /* ⚠ A TOOLTIP IS NOT PART OF A REPORT. `.kz-tip` is hidden by opacity, not by display,
     so it still occupies the page and printed as near-black on its own dark chip. */
  .kz-tip, .kz-chord, .wac, .wa-dock, .wa-compose{ display:none !important; }

  /* ⚠ HARD-CODED GREYS THAT ARE NOT A DARK-MODE PROBLEM AT ALL. #94A3B8 and #9AA7B8 are
     written straight into these rules in BOTH themes, and on white paper they are 2.4:1 --
     so these captions printed badly before dark mode existed and nobody had measured a
     printed page to notice. They take the same secondary slate as the rest. */
  .bs-subblock .sd, .bs-panel-head .tot .k, .kz-seglegend .it, .pl-pct,
  .dbt-aging .it, .dbt-aging b, .bs-subempty{ color:#475569 !important; }

  /* ⚠ A WARN BADGE MISSES AA BY A HAIR, AND IT DOES SO IN BOTH THEMES: #9a6a16 on the
     warn tint is 4.21:1 where 4.5 is needed. On screen it has never been reported and on
     paper it is the "not confirmed" flag next to a client's own cost classification — the
     one word on the sheet they are being asked to act on. The INK darkens; the tint is what
     makes it a warning. */
  .badge-status.bs-warn, .kz-pill.bs-warn{ color:#7d5511 !important; }

  /* ⚠ a link inside a tinted alert: the tint is a dark red in dark mode, and Bootstrap's
     own alert variables are a second source of it */
  [data-bs-theme="dark"]{ --bs-alert-bg:#fdeceb; --bs-alert-color:#7a2420; --bs-alert-border-color:#f3c9c6; }
  html[data-theme="dark"] :is(.alert, .kz-note){ background:#f6f8fc !important; color:#0f172a !important; }
}

/* ==== Sort indicator — visible, free, and it must not touch `position` (2026-09-23) ====
   🔴 NEVER SET `position` ON A SORTABLE HEADER. A frozen column's header is
   `position:sticky`, and a `table.table-sort th.sortable{position:relative}` rule is
   specificity (0,2,2) against the page's (0,2,0) — so it WON and turned the frozen header
   static while its own column kept pinning. Measured on the vendor summary after a 600px
   scroll: body cell at x=225, its header at x=-255. The header row then labelled the wrong
   columns and the wing tick boxes painted over the vendor names (owner-reported, live).
   ⚠ It was never needed: `position:sticky` already establishes a containing block, so an
   absolutely positioned child would have anchored correctly anyway.
   The glyphs are ZERO-WIDTH inline boxes instead — they overflow their own box, so they
   cost no column width (the whole reason the first version reached for absolute) and they
   change no other property. `.si-up` and `.si-dn` both start at the same x because a
   zero-width box advances nothing, and `top` stacks them. */
table.table-sort th.sortable .si{
  display:inline-block; width:0; overflow:visible; position:relative;
  font-size:.58rem; line-height:1; margin:0; opacity:.3; pointer-events:none;
}
table.table-sort th.sortable .si-up{ top:-5px; }
table.table-sort th.sortable .si-dn{ top:1px; }
/* the active direction is the only one that lights up; the other stays a hint */
table.table-sort th.sortable.asc .si-up,
table.table-sort th.sortable.desc .si-dn{ opacity:1; color:var(--kz); }
table.table-sort th.sortable:hover .si{ opacity:.6; }
table.table-sort th.sortable.asc:hover .si-up,
table.table-sort th.sortable.desc:hover .si-dn{ opacity:1; }
table.table-sort.re-reg th.sortable{ white-space:normal; }

