:root{color-scheme:light;--green:#176b42;--forest:#103d29;--leaf:#a8d66d;--canvas:#f3f7f3;--surface:#fff;--surface-soft:#f8fbf8;--surface-elevated:#fff;--line:#d9e3dc;--ink:#153127;--muted:#66766e;--danger:#b8423b;--danger-soft:#fbe9e7;--info:#2f6fb0;--info-soft:#e9f0f9;--purple:#7c5cbf;--purple-soft:#f1ecfa;--warning:#a8710f;--warning-soft:#fdf1dd;--radius:16px;--radius-lg:22px;--radius-sm:10px;--shadow:0 10px 30px rgba(16,61,41,.08);--shadow-elevated:0 20px 46px rgba(16,61,41,.14);--font-display:'Manrope',sans-serif;--font-body:'DM Sans',sans-serif;--ease:180ms ease;--ease-slow:220ms ease;--gradient-brand:linear-gradient(135deg, #1c8f5c, #0a2b1c);
/* Phase 10 — UI/UX redesign token notes: --surface-elevated/--info/--purple/
   --warning/--shadow-elevated/--radius-sm/--gradient-brand are new tokens
   added for the redesign's card-hierarchy and strategic-accent-colour
   requirements. --radius/--radius-lg were nudged 14/20 -> 16/22 for the
   "softer rounded cards" direction — this is safe across every existing
   breakpoint because no @media block in this file ever redefines --radius
   itself (only literal padding/gap px values are ever overridden per
   breakpoint), so the corner radius stays consistent at every width
   automatically via the existing var(--radius) usages already threaded
   through .card/.metric-card/.leads-card/.analytics-card/etc. Existing
   tokens (--green/--forest/--leaf/--canvas/--surface/--surface-soft/--line/
   --ink/--muted/--danger/--danger-soft/--font-* /--ease/--space-*) are
   UNCHANGED in :root — only extended, never redefined, so every rule
   already using them keeps its exact current light-theme appearance except
   where a new Phase 10 rule explicitly overrides it further down this file.

   PHASE 12 — comment-corruption fix (discovered during Phase 12 spacing
   verification, real Chromium computed-style check, not a hypothetical).
   CAUTION FOR ANY FUTURE EDITOR OF THIS COMMENT: do not type an asterisk
   immediately followed by a slash anywhere in this paragraph, even inside
   quotes describing the bug — a CSS comment has no quoting/escaping, so
   that exact two-character sequence ends the comment wherever it occurs,
   which is precisely the bug being described here.
   The line above used to read the token list with no space between the
   trailing wildcard star of the font-token entry and the following slash.
   That two-character sequence (an asterisk directly followed by a slash)
   is the standard CSS comment terminator, so it silently closed this
   comment right there, exactly like it would anywhere else in a
   stylesheet. Everything after that accidental close, up to the next
   genuine comment-open/comment-close pair (the "Phase 7: global spacing
   system" comment immediately below), was thereby parsed as actual CSS,
   not comment text — plain English prose with no semicolons, so it became
   a malformed declaration attempt that silently swallowed the very next
   real declaration in this block: the custom property that sets the
   spacing used for tight control groups (the one --space-control-gap
   token with the extra "-tight" suffix). That custom property was never
   registered at all: querying its computed value on the document root
   returned an empty string in real Chromium, and every rule using it via
   var() (`.filter-row{gap:...}` — the status-filter-and-Clear-filters row
   shared by Leads/Quotations/Invoices, live since Phase 7) computed to no
   gap at all — confirmed with real getBoundingClientRect measurements
   showing 0px between adjacent filter controls at every width. Inserting
   one space so the wildcard star and the following slash are no longer
   adjacent is the entire fix — it changes zero real style rules, only
   restores this pre-existing comment to actually behaving as a comment,
   letting the browser's own already-correct `.filter-row` gap declaration
   (unchanged, still exactly where Phase 7 put it) finally take effect the
   way it always should have. Re-verified with tinycss2 (0 parse errors,
   and the previously swallowed declaration now appears in the parsed
   :root rule) and with a real Chromium computed-style check (now resolves
   to 8px, matching the other spacing tokens). See the Phase 12 report for
   the full before/after measurement. */
/* Phase 7: global spacing system. Named tiers mirror the --radius/--radius-lg
   two-size precedent already established above. Values are the ones already
   governing the app's real layout rhythm (verified by frequency across
   styles.css) — this file, not a new one, so applying these tokens at the
   named landmarks below is a zero-visual-diff change except where a genuine
   inconsistency is called out inline (search "spacing bug fix"). Spacing is
   not theme-dependent, so these are defined once here and apply identically
   under [data-theme=dark]. */
--space-control-gap-tight:8px;--space-control-gap:10px;
--space-card-gap:16px;--space-card-gap-mobile:10px;
--space-heading-gap:18px;
--space-section:28px;--space-section-mobile:21px;
--space-section-lg:35px;--space-section-lg-mobile:25px;
--space-section-emphasis:20px;--space-section-emphasis-mobile:15px}
/* Phase 10 — dark theme is the approved flagship direction (dark forest-
   green SaaS reference). Rebuilt with THREE distinct surface tiers instead
   of the previous two (--canvas page bg < --surface card bg < NEW
   --surface-elevated for hovered/lifted/modal-level surfaces) so cards read
   as genuinely layered rather than "identical dark rectangles" (the exact
   monotony complaint in the brief). --green becomes a vivid emerald accent
   (was a paler mint) for primary buttons/active nav/positive trends, close
   to the reference mockup's bright accent green. New --info/--purple/
   --warning get dark-appropriate brighter values (a color tuned for text on
   white paper reads too dim on a near-black surface) so the "strategic
   accent colours" system stays legible in dark mode — see the print
   section far below, which is untouched and uses its OWN fixed --pd-* vars
   specifically so it never inherits any of this. */
[data-theme=dark]{color-scheme:dark;--green:#35d68c;--forest:#0a2b1c;--leaf:#b9e08a;--canvas:#0a1712;--surface:#11221b;--surface-soft:#0d1c16;--surface-elevated:#17291f;--line:#213a2e;--ink:#eaf3ee;--muted:#8fa89c;--danger:#ff7b6e;--danger-soft:#2e1512;--info:#6ba6f5;--info-soft:#132539;--purple:#b699f0;--purple-soft:#231a38;--warning:#f2b84e;--warning-soft:#2e2110;--shadow:0 18px 46px rgba(0,0,0,.4);--shadow-elevated:0 26px 60px rgba(0,0,0,.52);--gradient-brand:linear-gradient(135deg,#1c8f5c,#0a2b1c)}
*{box-sizing:border-box}
/* Restore native [hidden] behavior: several author rules below (e.g.
   label{display:grid}, .field-error{display:block}) set `display`
   unconditionally, which — per normal CSS cascade rules — beats the
   browser's own default `[hidden]{display:none}` UA-stylesheet rule
   regardless of selector specificity, since author-origin rules always win
   over user-agent-origin rules at equal importance. That silently made the
   `hidden` attribute a no-op on any element also matched by one of those
   rules (validation messages, conditionally shown fields, etc. stayed
   visible). This single rule re-asserts `hidden` as an override of last
   resort, exactly matching native browser behavior — it does not change
   what the app's JS decides to hide/show, only guarantees that `hidden`
   itself works once JS (or the initial markup) sets it. */
[hidden]{display:none!important}
body{min-width:320px;margin:0;background:var(--canvas);color:var(--ink);font-family:var(--font-body);overflow-x:hidden}button,input,select,textarea{font:inherit}button{cursor:pointer}button:disabled{cursor:wait;opacity:.65}:focus-visible{outline:3px solid color-mix(in srgb,var(--green) 45%,transparent);outline-offset:2px}.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}.page-loader,.fatal-error{min-height:100vh;display:grid;place-content:center;gap:12px;text-align:center;color:var(--muted)}.spinner{display:inline-block;width:20px;height:20px;border:2px solid var(--line);border-top-color:var(--green);border-radius:50%;animation:spin .75s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}
/* Login */.auth-page{position:relative;isolation:isolate;min-height:100vh;display:grid;place-items:center;padding:48px 24px;overflow:hidden}.auth-page:before{content:'';position:absolute;z-index:-2;inset:0;background:radial-gradient(circle at 50% 0,color-mix(in srgb,var(--leaf) 24%,transparent),transparent 42%)}.auth-shape{position:absolute;z-index:-1;border:1px solid color-mix(in srgb,var(--green) 20%,transparent);border-radius:50%}.auth-shape-one{width:520px;height:520px;top:-340px;left:-130px}.auth-shape-two{width:380px;height:380px;right:-160px;bottom:-220px}.auth-panel{width:min(100%,430px);padding:38px;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-lg);box-shadow:var(--shadow);text-align:center}.auth-brand{height:106px;display:grid;place-items:center;margin-bottom:23px}.auth-logo{width:112px;height:112px;object-fit:contain;mix-blend-mode:multiply}[data-theme=dark] .auth-logo,[data-theme=dark] .sidebar-logo,[data-theme=dark] .mobile-logo{mix-blend-mode:screen}.eyebrow{margin:0 0 9px;color:var(--green);font-size:.69rem;font-weight:800;letter-spacing:.13em}.auth-panel h1,.page-heading h1,.modal h2{margin:0;font-family:var(--font-display);font-weight:800;letter-spacing:-.035em}.auth-panel h1{font-size:2rem}.muted{margin:9px 0 0;color:var(--muted);font-size:.94rem;line-height:1.55}.form-stack{display:grid;gap:var(--space-card-gap);margin-top:29px;text-align:left}label{display:grid;gap:7px;font-size:.83rem;font-weight:700}input,select,textarea{width:100%;padding:11px 12px;border:1px solid var(--line);border-radius:10px;background:var(--surface);color:var(--ink);outline:0}input:focus,select:focus,textarea:focus{border-color:var(--green);box-shadow:0 0 0 3px color-mix(in srgb,var(--green) 15%,transparent)}.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:40px;padding:9px 14px;border:1px solid transparent;border-radius:10px;font-size:.86rem;font-weight:800;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.btn-spinner{display:inline-block;flex:0 0 auto;width:14px;height:14px;border:2px solid color-mix(in srgb,currentColor 30%,transparent);border-top-color:currentColor;border-radius:50%;animation:spin .75s linear infinite}.btn-primary{background:var(--green);color:#fff}.btn-primary:hover{filter:brightness(.92)}[data-theme=dark] .btn-primary{color:#0e1f18}.btn-secondary{border-color:var(--line);background:var(--surface);color:var(--ink)}.btn-block{width:100%}.auth-note{margin:25px 0 0;color:var(--muted);font-size:.76rem}.form-error{margin:0;color:var(--danger);font-size:.83rem;font-weight:600}.theme-toggle{display:inline-flex;align-items:center;justify-content:center;gap:7px;min-height:38px;padding:8px 10px;border:1px solid var(--line);border-radius:9px;background:var(--surface);color:var(--ink);font-size:.8rem;font-weight:700}.auth-theme-toggle{position:absolute;top:24px;right:24px}
/* Shell */.app-shell{display:grid;grid-template-columns:252px minmax(0,1fr);min-height:100vh}.sidebar{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;padding:24px 16px 18px;border-right:1px solid var(--line);background:var(--surface)}.sidebar-brand{padding:0 10px 23px;border-bottom:1px solid var(--line)}.sidebar-logo{display:block;width:172px;height:66px;object-fit:contain;object-position:left center;mix-blend-mode:multiply}.product-name{display:block;margin-top:5px;color:var(--muted);font-size:.7rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase}.side-nav{display:grid;gap:5px;margin-top:21px}.nav-item{display:flex;align-items:center;gap:10px;width:100%;min-height:42px;padding:9px 11px;border:0;border-radius:9px;background:transparent;color:var(--muted);font-size:.88rem;font-weight:750;text-align:left}.nav-item:hover{background:var(--surface-soft);color:var(--ink)}.nav-item.is-active{background:color-mix(in srgb,var(--green) 12%,transparent);color:var(--green)}.nav-icon{width:18px;text-align:center}.sidebar-footer{display:grid;gap:11px;margin-top:auto;padding-top:16px;border-top:1px solid var(--line)}.sidebar-theme-toggle{justify-content:flex-start}.account-card{display:flex;align-items:center;gap:10px;min-width:0;padding:8px 3px}.account-avatar{display:grid;place-items:center;flex:0 0 auto;width:32px;height:32px;border-radius:50%;background:color-mix(in srgb,var(--green) 15%,transparent);color:var(--green);font-size:.78rem;font-weight:800}.account-card strong,.account-card small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.account-card strong{max-width:162px;font-size:.76rem}.account-card small{margin-top:3px;color:var(--muted);font-size:.7rem}.logout-button{display:flex;align-items:center;gap:10px;width:100%;padding:9px 11px;border:0;border-radius:9px;background:transparent;color:var(--muted);font-size:.82rem;font-weight:750;text-align:left;transition:background var(--ease),color var(--ease)}.logout-button:hover{background:var(--danger-soft);color:var(--danger)}.logout-button:focus-visible{background:var(--danger-soft);color:var(--danger)}
/* Phase 7 — Fix 2: logout icon redesign. .nav-icon (used by the plain
   Dashboard/Leads/Team nav items) is sized for a dense vertical list and
   reads as too small/thin for a distinct, deliberate action like signing
   out. .logout-icon is a dedicated, larger, more visually weighted variant
   — same single-glyph-no-icon-library approach as the rest of the app,
   just tuned for this one control. Used on both the desktop sidebar button
   and the mobile icon-only header button so the two stay visually
   consistent with each other. */
.logout-icon{display:inline-grid;place-items:center;width:20px;font-size:1.2rem;line-height:1}.mobile-header,.mobile-nav{display:none}.main-content{width:min(100%,1400px);margin:auto;padding:44px clamp(24px,4vw,56px) 60px}.page-heading{display:flex;align-items:end;justify-content:space-between;gap:20px}.page-heading h1{font-size:clamp(2rem,3vw,2.45rem)}
/* Content */.metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--space-card-gap);margin:var(--space-section-lg) 0 var(--space-section)}.metric-card,.card{border:1px solid var(--line);background:var(--surface);box-shadow:0 1px 2px rgba(0,0,0,.02),var(--shadow)}.metric-card{display:flex;justify-content:space-between;min-height:125px;padding:20px;border-radius:var(--radius);transition:transform var(--ease),box-shadow var(--ease)}.metric-card:hover{transform:translateY(-2px);box-shadow:0 18px 36px color-mix(in srgb,var(--forest) 14%,transparent)}.metric-card span:not(.metric-icon){color:var(--muted);font-size:.8rem;font-weight:700}.metric-card strong{display:block;margin-top:8px;font-family:var(--font-display);font-size:2rem;letter-spacing:-.04em}.metric-icon{display:grid;place-items:center;width:34px;height:34px;border-radius:10px;background:color-mix(in srgb,var(--green) 12%,transparent);color:var(--green);font-weight:800;font-size:1.05rem;line-height:1}.leads-card{overflow:hidden;border-radius:var(--radius)}.leads-card-header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:22px 22px 15px}.leads-card-header h2{margin:0;font-family:var(--font-display);font-size:1.05rem}.result-count{margin:5px 0 0;color:var(--muted);font-size:.79rem}.clear-filters{min-height:34px;padding:6px 10px;font-size:.77rem}.toolbar{display:flex;justify-content:space-between;gap:14px;padding:13px 22px 17px;border-bottom:1px solid var(--line)}.search{position:relative;display:block;flex:1;max-width:470px}.search>span{position:absolute;top:9px;left:12px;color:var(--muted);font-size:1.15rem}.search input{padding-left:36px}.filter-row{display:flex;gap:var(--space-control-gap-tight)}.select-field{display:block}.filter-row select{min-width:145px;font-size:.81rem;font-weight:650}table{width:100%;min-width:840px;border-collapse:collapse}.table-wrap{overflow-x:auto}th,td{padding:15px 18px;border-bottom:1px solid var(--line);font-size:.84rem;text-align:left;vertical-align:middle}th{background:var(--surface-soft);color:var(--muted);font-size:.68rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase}tbody tr:hover{background:color-mix(in srgb,var(--green) 4%,transparent)}td strong,td small{display:block}td small{margin-top:3px;color:var(--muted);font-size:.75rem}.badge,.domain-badge,.source-tag{display:inline-flex;align-items:center;gap:5px;border-radius:999px;font-size:.72rem;font-weight:750;white-space:nowrap}.badge{padding:5px 8px;background:color-mix(in srgb,var(--muted) 10%,transparent);color:var(--muted)}.status-new{background:color-mix(in srgb,var(--green) 14%,transparent);color:var(--green)}.status-converted{background:color-mix(in srgb,var(--leaf) 28%,transparent);color:#477315}.status-lost{background:var(--danger-soft);color:var(--danger)}.status-contacted,.status-proposal-sent{background:#e9eff8;color:#426998}.status-meeting-scheduled,.status-meeting-completed{background:#f0eaf8;color:#74549c}[data-theme=dark] .status-contacted,[data-theme=dark] .status-proposal-sent{background:#1c3447;color:#9fc0e9}[data-theme=dark] .status-meeting-scheduled,[data-theme=dark] .status-meeting-completed{background:#382d48;color:#cdb7e8}.domain-badge{padding:5px 8px;border:1px solid var(--line);background:var(--surface-soft)}.source-tag{color:var(--muted)}.source-tag span{color:var(--green);font-size:1.1rem}.actions{white-space:nowrap}.icon-button{padding:6px 7px;border:0;border-radius:7px;background:transparent;color:var(--green);font-size:.76rem;font-weight:800}.icon-button:hover{background:color-mix(in srgb,var(--green) 11%,transparent)}.icon-button.danger{color:var(--danger)}.icon-button.danger:hover{background:var(--danger-soft)}.empty-state{display:grid;justify-items:center;padding:58px 24px;color:var(--muted);text-align:center}.empty-state h3{margin:11px 0 4px;color:var(--ink);font-family:var(--font-display);font-size:1.05rem}.empty-state p{margin:0 0 17px;font-size:.85rem}.empty-icon{display:grid;place-items:center;width:42px;height:42px;border-radius:50%;background:var(--surface-soft);color:var(--green);font-size:1.25rem}.table-loading,.table-error{padding:38px;color:var(--muted);text-align:center}.table-error{color:var(--danger)}
/* Modal and feedback */.modal-backdrop{position:fixed;z-index:30;inset:0;display:grid;place-items:center;padding:24px;background:rgba(7,25,17,.58);backdrop-filter:blur(4px)}.modal{width:min(760px,100%);max-height:90vh;overflow:auto;padding:26px;border:1px solid var(--line);border-radius:18px;background:var(--surface);box-shadow:0 24px 58px rgba(0,0,0,.28)}.modal header{display:flex;justify-content:space-between;gap:18px;margin-bottom:22px}.modal h2{font-size:1.45rem}.modal header p:last-child{margin:7px 0 0;color:var(--muted);font-size:.84rem}.modal-close{width:34px;height:34px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--muted);font-size:1.3rem}.form-section{margin:0;padding:17px 0;border:0;border-top:1px solid var(--line)}.form-section:first-of-type{padding-top:0;border-top:0}.form-section legend{font-size:.82rem;font-weight:800}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:15px;margin-top:12px}.full{grid-column:1/-1}.form-grid label:has(>small)+label{align-content:start}.modal footer{display:flex;justify-content:flex-end;gap:var(--space-control-gap);margin-top:7px}.modal footer .btn{min-width:110px}.toast-region{position:fixed;z-index:40;right:20px;bottom:20px;display:grid;gap:8px;max-width:min(360px,calc(100vw - 40px))}.toast{padding:12px 14px;border:1px solid var(--line);border-radius:10px;background:var(--surface);box-shadow:var(--shadow);font-size:.84rem;font-weight:650}.toast.error{border-color:var(--danger);color:var(--danger)}
/* Management dashboard */.analytics-dashboard{display:grid;gap:var(--space-card-gap);margin:0 0 var(--space-section)}.analytics-grid{display:grid;gap:var(--space-card-gap)}.analytics-top{grid-template-columns:minmax(0,1.8fr) minmax(240px,.7fr)}.analytics-breakdowns{grid-template-columns:repeat(2,minmax(0,1fr))}.analytics-bottom{grid-template-columns:repeat(2,minmax(0,1fr))}.analytics-card{min-width:0;padding:21px;border:1px solid var(--line);border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow)}.analytics-heading{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:var(--space-heading-gap)}.analytics-heading h2{margin:2px 0 0;font-family:var(--font-display);font-size:1.02rem;letter-spacing:-.02em}.section-kicker{margin:0;color:var(--green);font-size:.65rem;font-weight:800;letter-spacing:.12em}.analytics-note{padding:4px 8px;border-radius:999px;background:var(--surface-soft);color:var(--muted);font-size:.73rem;font-weight:750}.pipeline-list{display:grid;gap:12px}.pipeline-row{display:grid;grid-template-columns:130px minmax(0,1fr) 22px;align-items:center;gap:11px;font-size:.76rem}.pipeline-status{color:var(--muted);font-weight:700}.pipeline-row strong{font-size:.82rem;text-align:right}.progress-track{height:7px;overflow:hidden;border-radius:999px;background:var(--surface-soft)}.progress-fill{display:block;height:100%;min-width:0;border-radius:inherit;background:var(--green)}.pipeline-contacted{background:#5982bb}.pipeline-meeting-scheduled,.pipeline-meeting-completed{background:#8c6bbb}.pipeline-proposal-sent{background:#d59a4b}.pipeline-converted{background:#78a62b}.pipeline-lost{background:var(--danger)}.conversion-card{display:flex;flex-direction:column}.conversion-value{display:grid;gap:4px;margin:15px 0 auto}.conversion-value strong{font-family:var(--font-display);font-size:3.2rem;letter-spacing:-.07em;line-height:1}.conversion-value strong small{font-size:1.35rem}.conversion-value span,.conversion-detail{color:var(--muted);font-size:.78rem}.conversion-track{height:9px;margin-top:22px;overflow:hidden;border-radius:999px;background:var(--surface-soft)}.conversion-track span{display:block;height:100%;background:var(--leaf)}.conversion-detail{margin:10px 0 0}.conversion-detail strong{color:var(--ink)}.breakdown-list{display:grid;gap:14px}.breakdown-row{display:grid;grid-template-columns:minmax(0,1fr) 32px;gap:6px 12px;align-items:center}.breakdown-label{display:flex;justify-content:space-between;gap:12px;min-width:0;font-size:.78rem}.breakdown-label span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--muted);font-weight:700}.breakdown-label strong{color:var(--ink);font-size:.8rem}.breakdown-row .progress-track{grid-column:1/2}.breakdown-row small{grid-column:2;grid-row:2;color:var(--muted);font-size:.71rem;text-align:right}.progress-fill.source{background:#5c83ba}.progress-fill.domain{background:var(--green)}.text-link{padding:0;border:0;background:transparent;color:var(--green);font-size:.76rem;font-weight:800}.text-link:hover{text-decoration:underline}.activity-list,.meeting-list{display:grid;gap:2px}.activity-item,.meeting-item{display:flex;align-items:center;gap:10px;padding:10px 0;border-top:1px solid var(--line)}.activity-item:first-child,.meeting-item:first-child{border-top:0}.activity-avatar{display:grid;place-items:center;flex:0 0 auto;width:31px;height:31px;border-radius:50%;background:color-mix(in srgb,var(--green) 14%,transparent);color:var(--green);font-size:.76rem;font-weight:800}.activity-copy{display:grid;min-width:0;gap:3px}.activity-copy strong,.meeting-item>div strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.8rem}.activity-copy span,.meeting-item>div span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--muted);font-size:.72rem}.activity-meta{display:grid;gap:4px;margin-left:auto;text-align:right}.activity-meta small{color:var(--muted);font-size:.68rem}.mini-domain{color:var(--ink);font-size:.68rem;font-weight:700}.meeting-item time{display:grid;place-items:center;flex:0 0 auto;width:37px;height:37px;border-radius:9px;background:var(--surface-soft);color:var(--green)}.meeting-item time strong{font-size:.82rem;line-height:1}.meeting-item time span{font-size:.61rem;font-weight:800;text-transform:uppercase}.meeting-item>div{display:grid;min-width:0;gap:3px;flex:1}.meeting-item .badge{font-size:.65rem}.dashboard-empty{display:grid;place-items:center;min-height:150px;color:var(--muted);text-align:center}.dashboard-empty span{display:grid;place-items:center;width:35px;height:35px;margin-bottom:8px;border-radius:50%;background:var(--surface-soft);color:var(--green)}.dashboard-empty p{margin:0;font-size:.8rem}
/* Leads workspace refinements */.leads-card-header{padding-bottom:var(--space-heading-gap)}.leads-add-button{min-width:112px}.toolbar{align-items:center;background:color-mix(in srgb,var(--surface-soft) 45%,transparent)}.search input{padding-right:38px}.clear-search{position:absolute;top:5px;right:5px;width:30px;height:30px;border:0;border-radius:7px;background:transparent;color:var(--muted);font-size:1.2rem;line-height:1}.clear-search:hover{background:var(--surface);color:var(--ink)}.filter-row{align-items:center}.active-filter-chips{display:flex;flex-wrap:wrap;gap:7px;padding:0 22px 14px;border-bottom:1px solid var(--line);background:color-mix(in srgb,var(--surface-soft) 45%,transparent)}.filter-chip{display:inline-flex;align-items:center;gap:7px;min-height:29px;padding:5px 8px 5px 10px;border:1px solid color-mix(in srgb,var(--green) 28%,var(--line));border-radius:999px;background:var(--surface);color:var(--green);font-size:.72rem;font-weight:750}.filter-chip:hover{background:color-mix(in srgb,var(--green) 10%,transparent)}.type-tag{display:inline-flex;padding:4px 7px;border-radius:6px;background:var(--surface-soft);color:var(--muted);font-size:.71rem;font-weight:700}.meeting-date{color:var(--ink);font-size:.78rem;font-weight:750}.meeting-date.no-meeting{color:var(--muted);font-weight:650}.status-new:before,.status-contacted:before,.status-meeting-scheduled:before,.status-meeting-completed:before,.status-proposal-sent:before,.status-converted:before,.status-lost:before{content:'';width:5px;height:5px;border-radius:50%;background:currentColor}.table-error{display:grid;justify-items:center;gap:7px}.table-error strong{font-family:var(--font-display);font-size:.95rem}.table-error span{font-size:.8rem}.skeleton-row td{padding:18px 22px}.skeleton-row td>span{display:inline-block;height:12px;margin-right:12px;border-radius:999px;background:linear-gradient(90deg,var(--surface-soft),color-mix(in srgb,var(--line) 65%,transparent),var(--surface-soft));background-size:200% 100%;animation:skeleton 1.4s ease infinite}.skeleton-row td>span:nth-child(1){width:28%}.skeleton-row td>span:nth-child(2){width:19%}.skeleton-row td>span:nth-child(3){width:12%}@keyframes skeleton{to{background-position:-200% 0}}
@media(max-width:1199px){.app-shell{grid-template-columns:232px minmax(0,1fr)}.main-content{padding:36px 32px 52px}.sidebar-logo{width:160px}.metrics{gap:12px}.metric-card{padding:17px}}
@media(max-width:899px){.app-shell{grid-template-columns:minmax(0,1fr)}.sidebar{display:none}.mobile-header{display:flex;align-items:center;justify-content:space-between;min-height:68px;padding:8px 24px;border-bottom:1px solid var(--line);background:var(--surface)}.mobile-logo{width:128px;height:50px;object-fit:contain;object-position:left center;mix-blend-mode:multiply}.mobile-header-actions{display:flex;gap:7px}.icon-only{width:38px;padding:0}.mobile-nav{display:flex;gap:5px;padding:8px 24px;border-bottom:1px solid var(--line);background:var(--surface)}.mobile-nav .nav-item{width:auto;min-height:34px;padding:6px 10px;font-size:.78rem}.main-content{padding:32px 24px 48px}.metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.toolbar{flex-direction:column}.search{max-width:none}.filter-row{display:grid;grid-template-columns:1fr 1fr}.filter-row select{width:100%;min-width:0}}
@media(max-width:899px){.analytics-top{grid-template-columns:1fr}.analytics-breakdowns,.analytics-bottom{grid-template-columns:1fr}}
@media(max-width:719px){.auth-page{align-items:start;padding:72px 18px 32px}.auth-panel{padding:31px 23px 26px}.auth-theme-toggle{top:16px;right:16px;width:38px;padding:0}.theme-toggle-label{display:none}.mobile-header,.mobile-nav{padding-inline:16px}.main-content{padding:26px 16px 40px}.page-heading{align-items:flex-start;flex-direction:column;gap:16px}.page-heading .btn{width:100%}.metrics{gap:var(--space-card-gap-mobile);margin:var(--space-section-lg-mobile) 0 var(--space-section-mobile)}.metric-card{min-height:103px;padding:14px}.metric-card strong{font-size:1.6rem}.metric-icon{width:29px;height:29px}.leads-card-header{align-items:flex-start;padding:18px 16px 12px}.toolbar{padding:12px 16px 15px}.filter-row{grid-template-columns:1fr 1fr}.table-wrap{overflow:visible}.table-wrap table,.table-wrap thead,.table-wrap tbody,.table-wrap tr,.table-wrap th,.table-wrap td{display:block;min-width:0}.table-wrap thead{display:none}.table-wrap tbody{display:grid;gap:10px;padding:10px}.table-wrap tr{padding:13px 12px 11px;border:1px solid var(--line);border-radius:11px}.table-wrap td{display:grid;grid-template-columns:92px minmax(0,1fr);gap:9px;padding:5px 0;border:0}.table-wrap td:before{content:attr(data-label);color:var(--muted);font-size:.68rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}.table-wrap td:first-child{display:block;padding-bottom:9px;border-bottom:1px solid var(--line)}.table-wrap td:first-child:before,.table-wrap td.actions:before{display:none}.table-wrap td.actions{display:flex;justify-content:flex-end;gap:5px;padding-top:8px}.modal-backdrop{align-items:end;padding:0}.modal{width:100%;max-height:92vh;padding:21px 17px 22px;border-radius:18px 18px 0 0}.form-grid{grid-template-columns:1fr}.full{grid-column:auto}.modal footer .btn{flex:1;min-width:0}.toast-region{right:12px;bottom:12px;max-width:calc(100vw - 24px)}}
@media(max-width:719px){.leads-card-header{align-items:center}.leads-add-button{width:auto!important;min-width:auto!important}.active-filter-chips{padding:0 16px 12px}.table-wrap td.actions .icon-button{min-height:36px;padding:8px 11px}.skeleton-row td{padding:15px}.skeleton-row{border:1px solid var(--line);border-radius:10px}.skeleton-row td>span{display:block;width:100%!important;margin:7px 0}.skeleton-row td>span:nth-child(2){width:72%!important}.skeleton-row td>span:nth-child(3){width:45%!important}}
@media(max-width:719px){.analytics-dashboard{gap:var(--space-card-gap-mobile);margin-bottom:var(--space-section-mobile)}.analytics-card{padding:17px}.pipeline-row{grid-template-columns:104px minmax(0,1fr) 18px;gap:8px;font-size:.7rem}.breakdown-list{gap:11px}.activity-meta{max-width:92px}.meeting-item{align-items:flex-start}.meeting-item .badge{display:none}.dashboard-priority{margin-top:var(--space-section-emphasis-mobile)}}
@media(prefers-reduced-motion:reduce){*,*:before,*:after{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}
/* Lead form experience */.lead-modal{padding:30px}.lead-modal header{padding-bottom:20px;border-bottom:1px solid var(--line)}.lead-modal .eyebrow{margin-bottom:7px}.lead-modal header p:last-child{max-width:520px;line-height:1.5}.lead-modal form{display:grid;gap:0}.lead-modal .form-section{padding:20px 0}.lead-modal .form-section legend{display:flex;align-items:center;gap:8px;color:var(--ink);font-family:var(--font-display);font-size:.88rem}.lead-modal .form-section legend span{display:grid;place-items:center;width:22px;height:22px;border-radius:50%;background:color-mix(in srgb,var(--green) 12%,transparent);color:var(--green);font-family:var(--font-body);font-size:.65rem;font-weight:800}.lead-modal label{position:relative;gap:7px}.required-indicator{margin-left:auto;color:var(--danger);font-size:.65rem;font-weight:800;text-transform:uppercase}.lead-modal input,.lead-modal select,.lead-modal textarea{min-height:43px;padding:11px 12px}.lead-modal textarea{min-height:128px;resize:vertical;line-height:1.5}.lead-modal label>small:not(.field-error){color:var(--muted);font-size:.71rem;font-weight:500}.field-error{display:block;margin-top:-2px;color:var(--danger);font-size:.71rem;font-weight:700}.lead-modal [aria-invalid=true]{border-color:var(--danger);box-shadow:0 0 0 3px color-mix(in srgb,var(--danger) 15%,transparent)}.lead-modal footer{position:sticky;bottom:-30px;display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 -30px -30px;padding:16px 30px;background:var(--surface);border-top:1px solid var(--line)}.lead-modal footer>div{display:flex;gap:var(--space-control-gap)}.form-footer-note{margin:0;color:var(--muted);font-size:.73rem}.form-footer-note span{color:var(--danger);font-weight:800}.lead-modal #lead-save-button:disabled{min-width:130px}.lead-modal .modal-close{flex:0 0 auto}
@media(max-width:719px){.lead-modal{padding:21px 17px 18px}.lead-modal header{margin-bottom:4px;padding-bottom:16px}.lead-modal .form-section{padding:17px 0}.lead-modal footer{bottom:-18px;align-items:stretch;flex-direction:column;margin:0 -17px -18px;padding:13px 17px;background:var(--surface)}.lead-modal footer>div{display:grid;grid-template-columns:1fr 1fr}.form-footer-note{font-size:.7rem}.lead-modal textarea{min-height:112px}}
/* Phase 5B: follow-up status badges — reuses existing badge shape and theme tokens */
.followup-badge{display:inline-flex;align-items:center;padding:5px 8px;border-radius:999px;font-size:.72rem;font-weight:750;white-space:nowrap}
.followup-overdue{background:var(--danger-soft);color:var(--danger)}
.followup-due-today{background:color-mix(in srgb,var(--green) 14%,transparent);color:var(--green)}
.followup-upcoming{background:#e9eff8;color:#426998}
.followup-no-action{background:color-mix(in srgb,var(--muted) 10%,transparent);color:var(--muted)}
[data-theme=dark] .followup-upcoming{background:#1c3447;color:#9fc0e9}
.meeting-item .followup-badge{font-size:.65rem}
/* Phase 5C: lead activity timeline (prefixed lead-activity- to avoid colliding with the unrelated .activity-item/.activity-list dashboard classes) */
.lead-activity-add{display:grid;gap:12px;margin-bottom:18px}
.lead-activity-add-button{justify-self:start}
.lead-activity-empty{margin:0;color:var(--muted);font-size:.82rem}
.lead-activity-timeline{display:grid}
.lead-activity-item{position:relative;display:grid;grid-template-columns:16px minmax(0,1fr);gap:12px;padding-bottom:18px}
.lead-activity-item:last-child{padding-bottom:0}
.lead-activity-marker{position:relative;top:6px;justify-self:center}
.lead-activity-marker:before{content:'';display:block;width:9px;height:9px;border-radius:50%;background:var(--green)}
.lead-activity-item:not(:last-child) .lead-activity-marker:after{content:'';position:absolute;top:11px;bottom:-18px;left:50%;width:2px;margin-left:-1px;background:var(--line)}
.lead-activity-body{min-width:0;display:grid;gap:4px}
.lead-activity-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.lead-activity-top strong{font-size:.85rem}
.lead-activity-date{color:var(--muted);font-size:.72rem;font-weight:700}
.lead-activity-author{color:var(--green);font-size:.74rem;font-weight:700}
.lead-activity-notes{margin:2px 0 0;color:var(--ink);font-size:.83rem;line-height:1.5;overflow-wrap:anywhere}
.lead-activity-footer{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:2px}
.lead-activity-logged{color:var(--muted);font-size:.68rem}
.lead-activity-delete{padding:2px 6px;font-size:.68rem}

/* Phase 5D: reusable confirmation modal */
.confirm-modal{width:min(420px,100%);text-align:left}
.confirm-modal h2{margin:0 0 10px;font-size:1.2rem}
.confirm-modal p{margin:0 0 8px;color:var(--ink);font-size:.88rem;line-height:1.5}
.confirm-warning{color:var(--danger);font-weight:700;font-size:.82rem}
.confirm-actions{display:flex;justify-content:flex-end;gap:var(--space-control-gap);margin-top:20px}
.confirm-actions .btn{min-width:110px}
.btn-danger{background:var(--danger);color:#fff}
.btn-danger:hover{filter:brightness(.92)}
[data-theme=dark] .btn-danger{color:#2b0a08}
/* Phase 22: Country Code / Salutation compound fields — a small dropdown
   sitting beside its paired input inside one .form-grid cell, reusing the
   existing label/input/select styling (no new colors, no new control
   shapes). Exact mirror of the CRM's own copy in its styles.css. */
/* SECOND BATCH — Change 1: same reasoning/values as the CRM's identical fix
   (js/../styles.css) — this rule is shared by both Salutation+Customer name
   and Country code+Phone pairs, so tightening it once keeps them
   proportionate to each other. Now that the country-code select's closed
   state only ever shows a short code (see quotations-ui.js/invoices-ui.js's
   countryCodeOptionList + setCountryCodeExpanded), both selects only need
   room for a handful of characters plus the native dropdown arrow. */
.field-pair{display:grid;grid-template-columns:minmax(72px,88px) 1fr;gap:8px}
.field-pair label{gap:7px}
@media(max-width:379px){.field-pair{grid-template-columns:1fr}}

/* Phase 5D: dashboard action-first hierarchy polish */
/* Phase 7 — spacing bug fix: the previous 6C-4 value (margin:16px 0 28px)
   was a one-off invented purely for this selector — 16px matched nothing
   else in the spacing system, and because .dashboard-priority is a CSS
   Grid item inside .analytics-dashboard, its own margin does NOT collapse
   with the grid's own `gap` (var(--space-card-gap)) the way sibling block
   margins collapse elsewhere. That meant the 28px bottom value silently
   stacked with the grid gap (28+16=44px) — a different, uneven rhythm from
   every other row boundary in this same grid, which only ever gets the
   plain var(--space-card-gap). Fix: use the SAME two-tier token pattern
   already established by --radius/--radius-lg (see :root) — the "before"
   gap is var(--space-section) (28px, collapsed from .metrics' own
   margin-bottom, the same value used for every other section-to-section
   boundary on this page) PLUS var(--space-section-emphasis) (20px) of
   deliberate extra emphasis on top of it as a non-collapsing grid-item
   margin, both named/reusable tokens rather than an invented pixel value.
   The bottom margin is removed entirely so the grid's own gap — identical
   to every sibling row's spacing — governs the transition into the
   pipeline/conversion cards below, restoring uniform internal rhythm. */
.dashboard-priority{margin:var(--space-section-emphasis) 0 0}
.dashboard-priority .section-kicker{margin:0 0 10px}
.dashboard-priority .metrics{margin:0}
.metric-card-action{cursor:pointer}
.followup-meeting-item{cursor:pointer}
.followup-meeting-item:hover{background:color-mix(in srgb,var(--green) 4%,transparent)}

/* Phase 5D: activity timeline polish */
.lead-activity-timeline-label{margin:0 0 8px;color:var(--muted);font-size:.68rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}
.lead-activity-empty{padding:16px 0;text-align:center}

/* Phase 6C-3: Team Management — reuses .leads-card/.table-wrap/table/.badge
   styling and the existing generic .table-wrap mobile card-conversion rules
   (data-label based) above, so only the role select itself needs a rule.
   Fix 6 (Phase 6C-4): a read-only viewer's role cell reuses this same
   .badge markup already used elsewhere — no new CSS needed for that. */
.team-role-select{min-width:150px;font-size:.82rem;font-weight:650}
@media(max-width:719px){.team-role-select{min-width:0}}

/* Phase 6C-3: Team Management — reuses .leads-card/.table-wrap/table/.badge
   styling and the existing generic .table-wrap mobile card-conversion rules
   (data-label based) above, so only the role select itself needs a rule.
   Fix 6 (Phase 6C-4): a read-only viewer's role cell reuses this same
   .badge markup already used elsewhere — no new CSS needed for that. */

#team-management {
  margin-top: var(--space-section);
}

.team-role-select {
  min-width: 150px;
  font-size: .82rem;
  font-weight: 650;
}

@media(max-width:719px) {
  .team-role-select {
    min-width: 0;
  }
}

/* Phase 6C-4 — Fix 1: leads table horizontal overflow. The table already
   scrolls inside its own #leads-workspace .table-wrap (overflow-x:auto)
   rather than the page, but reaching Edit/Delete required scrolling all
   the way right on narrower desktop widths. Pinning the last column (the
   Actions cell) to the scroll container's own right edge keeps it always
   reachable, with a soft edge shadow so the overlap reads as intentional.
   Scoped to #leads-workspace specifically so Team Management's unrelated
   2-column table (Fix 6/9) is never affected. */
#leads-workspace .table-wrap th:last-child,
#leads-workspace .table-wrap td:last-child{position:sticky;right:0;background:var(--surface);box-shadow:-8px 0 8px -8px rgba(0,0,0,.12)}
#leads-workspace .table-wrap th:last-child{background:var(--surface-soft)}
#leads-workspace .table-wrap tbody tr:hover td:last-child{background:color-mix(in srgb,var(--green) 4%,var(--surface))}
@media(max-width:719px){#leads-workspace .table-wrap th:last-child,#leads-workspace .table-wrap td:last-child{position:static;box-shadow:none;background:transparent}}

/* Phase 6C-4 — Fix 7: password visibility toggle. Mirrors the existing
   .search/.clear-search positioned-icon-in-field pattern above. */
.password-field{position:relative;display:block}
.password-field input{padding-right:44px}
.password-toggle{position:absolute;top:50%;right:4px;transform:translateY(-50%);display:grid;place-items:center;width:32px;height:32px;padding:0;border:0;border-radius:7px;background:transparent;color:var(--muted);font-size:1rem}
.password-toggle:hover{background:var(--surface-soft);color:var(--ink)}

/* Phase 8 — Change 3b: Member Edit Lead read-only sections. Reinforces the
   native <fieldset disabled> treatment (js/ui.js openLeadModal) with an
   explicit, theme-aware dimming so a disabled section reads clearly as
   "read-only", not "broken" — browsers already dim disabled controls
   somewhat by default, but inconsistently across themes, so this makes it
   deliberate using the same tokens as the rest of the form. */
.lead-modal fieldset:disabled{opacity:.6}
.lead-modal fieldset:disabled legend{color:var(--muted)}
.lead-modal fieldset:disabled input,
.lead-modal fieldset:disabled select,
.lead-modal fieldset:disabled textarea{cursor:not-allowed;background:var(--surface-soft)}
.lead-readonly-note{margin:0 0 18px;padding:10px 12px;border:1px solid var(--line);border-radius:9px;background:var(--surface-soft);color:var(--muted);font-size:.78rem;line-height:1.5}

/* =========================================================================
   Phase 9 — Invoice system. Additive only, per the established Phase 7/8
   convention (no existing rule above this point is changed). Reuses
   .card/.leads-card/.table-wrap/table/.badge/.metric-card/.form-grid/
   .empty-state/.toolbar/.search/.filter-row/.select-field verbatim wherever
   the shape already matches — only what's genuinely new gets a new class.
   ========================================================================= */

/* Manual-test fix (spacing bug, Aug 2026): #invoices-workspace is a bare
   <section> that sits directly after #leads-workspace in the DOM (see
   js/ui.js's renderApp — the two are adjacent siblings inside .main-content,
   which is plain block flow, not a grid/flex container). Every other
   section-to-section boundary on this page gets its gap from the PRECEDING
   element's own margin-bottom (.metrics's bottom margin before
   #dashboard-analytics; .analytics-dashboard's bottom margin before
   #leads-workspace) — but neither .leads-card nor #invoices-workspace ever
   carried one, so the Invoices card sat flush against the bottom of the
   Leads card at every viewport width, with no gap to collapse. Fixed with
   the SAME two tokens already used for those other boundaries — no invented
   pixel value: var(--space-section) (28px) holds from desktop down through
   the tablet breakpoint (the 1199px query doesn't touch spacing tokens, so
   this rhythm is intentionally unchanged there), then the existing 719px
   mobile breakpoint below reduces it to var(--space-section-mobile) (21px),
   matching how .analytics-dashboard's own margin-bottom is reduced at that
   same breakpoint. A margin-top (not padding, not a wrapper element) so it
   collapses normally with ordinary sibling margins and needs no change to
   .leads-card/.card, card widths, or the table's own overflow-x:auto
   handling — nothing here is a stacking-context or layout change, so cards
   still stack (mobile's .table-wrap card-row layout, 719px query) with the
   same internal rhythm as before; only the section boundary above the
   Invoices card gained space.

   Note on placement: the 719px override below is deliberately its OWN
   media block down here rather than folded into the existing 719px block
   near the top of this file (with .analytics-dashboard/.dashboard-priority)
   — both rules would carry equal specificity (a single ID selector), so
   whichever occurs LATER in source order wins the cascade regardless of
   which media query actually matches. Keeping the override after the
   desktop/tablet declaration guarantees the mobile value always wins at
   narrow widths; putting it earlier in the file silently would not
   (confirmed with a real-browser measurement during this fix — the
   earlier placement measured 28px at 375px/320px viewports instead of the
   intended 21px). */
#invoices-workspace{margin-top:var(--space-section)}
@media(max-width:719px){#invoices-workspace{margin-top:var(--space-section-mobile)}}

/* Invoice status badges — same shape as STATUS_BADGE_CLASSES/.badge above,
   an independent color set since invoice status is a different domain. */
.invoice-status-draft{background:color-mix(in srgb,var(--muted) 12%,transparent);color:var(--muted)}
.invoice-status-sent{background:#e9eff8;color:#426998}
.invoice-status-partial{background:#fdf3e0;color:#9c6b1a}
.invoice-status-paid{background:color-mix(in srgb,var(--leaf) 28%,transparent);color:#477315}
.invoice-status-cancelled{background:var(--danger-soft);color:var(--danger)}
.invoice-status-overdue{background:var(--danger-soft);color:var(--danger);font-weight:800}
[data-theme=dark] .invoice-status-sent{background:#1c3447;color:#9fc0e9}
[data-theme=dark] .invoice-status-partial{background:#3a2c12;color:#e3b969}

/* Invoice list — the summary cards reuse .metrics/.metric-card verbatim
   (see js/invoices-ui.js renderInvoicesWorkspaceShell); only the spacing
   between the cards and the toolbar below them needs a small addition. */
.invoice-summary-metrics{margin:0 0 var(--space-card-gap)}

/* Full-screen invoice editor (§12/§13) — a fixed-inset overlay so showing
   it is a plain show/hide (removing the `hidden` attribute) with no need
   to touch the rest of the app shell's markup or scroll state; matches the
   plan's "no router needed" note. Sits above the modal layer's z-index
   floor (30) is NOT required since the editor and #modal-root are never
   meant to be open at the same time in normal use, but the payment modal
   IS opened from inside the editor — so the editor's own z-index must stay
   below .modal-backdrop's (30) for that to layer correctly.

   PHASE 1 FIX 1 (editor header overlap): the box used to be `inset:0`, i.e.
   it started at viewport y=0 — exactly where the sticky .bs-topbar sits.
   The topbar's z-index (40) is higher than this one's (25), so the topbar
   painted over the top of the editor and hid the editor's own
   .invoice-editor-header (and its Back button), which is only 32px down
   from the editor box's own top edge (.invoice-editor-shell's padding).
   The client asked for the topbar to STAY visible while an editor is open,
   so the fix is a physical top offset, not a z-index change: the editor now
   begins immediately below the topbar. --bs-topbar-h is a single static
   62px with no per-breakpoint override, so one value is correct at every
   width. z-index stays at 25 — see the payment-modal note above.

   FIX 4 (editor overlaps sidebar at 1280/1024): same reasoning applied to
   the left edge. The box used to be `left:0`, i.e. it started under the
   PERSISTENT desktop sidebar too (only visible because .bs-sidebar's
   z-index:70 painted over it) — at 1280/1024 the editor's own centred
   content column (.invoice-editor-shell) sits close enough to the left
   edge that its heading text got visually cut by the sidebar. Reusing the
   exact same variables .bs-shell's own grid keys off (--bs-sidebar-w,
   --bs-rail-w) rather than a new arbitrary margin, so this can never drift
   out of sync with the sidebar's real rendered width: expanded is the
   default (left:var(--bs-sidebar-w)); the collapsed rail and the mobile/
   tablet drawer each get one override below, mirroring .bs-shell's own
   :root[data-sidebar=...] pattern exactly. In drawer state the persistent
   sidebar isn't on screen at all (it's off-canvas until opened), so the
   editor correctly reclaims the full width — when the drawer IS opened on
   top of it, .bs-sidebar's unchanged z-index:70 still overlays the editor's
   25, i.e. "overlay only when intentionally opened" needed no new rule. */
.invoice-editor-page{position:fixed;z-index:25;top:var(--bs-topbar-h);right:0;bottom:0;left:var(--bs-sidebar-w);overflow-y:auto;background:var(--canvas)}
:root[data-sidebar=collapsed] .invoice-editor-page{left:var(--bs-rail-w)}
:root[data-sidebar=drawer] .invoice-editor-page{left:0}
/* grid-template-columns:minmax(0,1fr) is required, not cosmetic: a plain
   `display:grid` implicit column's automatic minimum size is the min-content
   of its widest item, which here is the line-items table (min-width:840px
   from the global `table` rule). Without this, that 840px floor drags the
   WHOLE shell (header, Back button included) wider than the viewport at
   768px, and .table-wrap's own overflow-x:auto never gets a chance to do its
   job because the grid track never actually shrinks. minmax(0,1fr) lets the
   track shrink to the shell's own width and pushes the overflow down into
   .table-wrap's internal scrollbar, which is what it was already built for.
   Pre-existing at every width before Phase 1 Fix 1 (inset:0 hid the symptom
   behind the topbar at exactly the widths where it bites); this is the fix
   for "Back button ... not clipped" at 768px from the current QA sheet. */
/* VISUAL-FIX ROUND (post-Phase-3): widened from 980px to 1180px. At 1440px
   with the sidebar expanded, the editor box (.invoice-editor-page) already
   had 1172px of real available width (1440 - 268px sidebar), but this
   column capped itself at 980px and centered with auto margins — leaving
   ~190px unused on each side while the Line Items & Tax table (whose real
   content width is well over 1000px once HSN/Qty/Unit/Rate/Discount/
   Tax %/Total/Actions are all shown) had less room than it needed. Still a
   responsive `min(100%, Npx)`, not a hardcoded viewport width — on a
   genuinely narrow viewport (1024px and below) this still simply resolves
   to 100%, same as before; only the desktop ceiling moved up. */
.invoice-editor-shell{width:min(100%,1180px);margin:0 auto;padding:32px clamp(20px,4vw,48px) 80px;display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-card-gap)}
.invoice-editor-header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}
.invoice-editor-header h1{margin:4px 0 0;font-family:var(--font-display);font-size:1.7rem;letter-spacing:-.03em}
.invoice-editor-header-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.invoice-editor-card{padding:22px}
.invoice-editor-card h2{display:flex;align-items:center;gap:8px;margin:0 0 16px;color:var(--ink);font-family:var(--font-display);font-size:.92rem}
.invoice-editor-card h2 span{display:grid;place-items:center;width:22px;height:22px;border-radius:50%;background:color-mix(in srgb,var(--green) 12%,transparent);color:var(--green);font-family:var(--font-body);font-size:.65rem;font-weight:800}

/* Invoice items table — inputs live inside table cells, so they need to
   shed the default label/input vertical rhythm (.form-grid isn't used
   here) and fit the row's compact height. */
.invoice-items-table th,.invoice-items-table td{padding:8px 10px}
.invoice-items-table input{min-height:36px;padding:7px 9px;font-size:.83rem}
.invoice-items-table .item-description{min-width:180px}
.invoice-items-table .item-hsn{width:110px}
.invoice-items-table .item-quantity,.invoice-items-table .item-tax{width:80px}
.invoice-items-table .item-unit{width:90px}
.invoice-items-table .item-rate,.invoice-items-table .item-discount{width:110px}
.invoice-items-table .item-line-total{font-weight:750;white-space:nowrap}
.invoice-items-empty-row td{padding:20px;color:var(--muted);font-size:.83rem;text-align:center}
.invoice-field-invalid{border-color:var(--danger)!important;box-shadow:0 0 0 3px color-mix(in srgb,var(--danger) 15%,transparent)}
[data-add-item-row]{margin-top:12px}

/* Phase 21 (CGST/SGST/IGST) — Advanced Tax panel: an optional, collapsed-by-
   default per-item subsection styled as an embedded card using the same
   surface/border/radius/typography tokens as the rest of Finance (brief
   §2/§12: "styled to match existing Finance dark-green design"). Shared
   byte-for-byte between quotations-ui.js and invoices-ui.js (brief §10 —
   "no two different tax UX systems"), so these rules apply identically to
   both editors. The toggle+panel row's own <td> spans every column
   (colspan matches the table's column count, same technique already used by
   .invoice-items-empty-row), and because it is the lone/first child of its
   <tr>, the existing mobile .table-wrap rules already render it full-width
   with no data-label prefix and no extra CSS of their own — only the two
   grids inside the panel need their own mobile column collapsing below. */
.item-tax-row td{padding:10px 10px 12px}
.item-tax-toggle-line{display:flex;justify-content:flex-end}
.tax-toggle-button{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:30px;padding:6px 10px;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface);color:var(--green);font-size:.76rem;font-weight:750;transition:background var(--ease),border-color var(--ease)}
.tax-toggle-button:hover{background:color-mix(in srgb,var(--green) 10%,transparent);border-color:color-mix(in srgb,var(--green) 35%,var(--line))}
.item-tax-panel{margin-top:9px;padding:14px 16px;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface-soft)}
.item-tax-panel-title{margin:0 0 3px;color:var(--ink);font-family:var(--font-display);font-size:.86rem;font-weight:800}
.item-tax-panel-hint{margin:0 0 14px;color:var(--muted);font-size:.78rem;line-height:1.5}
.item-tax-rate-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}
.item-tax-rate-grid label{display:grid;gap:6px;font-size:.76rem;font-weight:700;color:var(--muted)}
.item-tax-rate-grid input{min-height:36px;padding:7px 9px;font-size:.83rem}
.item-tax-amount-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-top:14px;padding-top:14px;border-top:1px solid var(--line)}
.item-tax-amount-grid>div{display:grid;gap:4px}
.item-tax-amount-grid span{color:var(--muted);font-size:.71rem;font-weight:700;letter-spacing:.03em;text-transform:uppercase}
.item-tax-amount-grid strong{font-family:var(--font-display);font-size:.92rem;letter-spacing:-.01em}
.item-total-tax-amount{color:var(--green)}

.invoice-totals-summary{display:grid;gap:8px;margin-top:18px;padding-top:16px;border-top:1px solid var(--line);justify-items:end}
.invoice-totals-summary>div{display:flex;justify-content:space-between;gap:24px;width:min(100%,320px);font-size:.85rem}
.invoice-totals-summary>div span{color:var(--muted)}
.invoice-totals-grand{padding-top:10px;border-top:1px solid var(--line);font-size:1rem}
.invoice-totals-grand strong{font-family:var(--font-display);font-size:1.15rem}

.invoice-outstanding-note{margin:14px 0 0;font-size:.85rem}

.invoice-editor-footer{position:sticky;bottom:0;display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;margin-top:8px;padding:16px 0 0;border-top:1px solid var(--line);background:var(--canvas)}
.invoice-editor-footer-right{display:flex;gap:var(--space-control-gap);flex-wrap:wrap}
.invoice-cancel-button{color:var(--danger);border-color:color-mix(in srgb,var(--danger) 35%,var(--line))}
.invoice-cancel-button:hover{background:var(--danger-soft)}

@media(max-width:719px){
  .invoice-editor-shell{padding:22px 16px 64px}
  .invoice-editor-header h1{font-size:1.4rem}
  .invoice-editor-footer{flex-direction:column;align-items:stretch}
  .invoice-editor-footer-right{flex-direction:column}
  .invoice-editor-footer-right .btn{width:100%}
  /* Phase 21 (CGST/SGST/IGST): stack the rate inputs and shrink the amount
     tiles to a 2-column grid so nothing gets unusably narrow at 390px —
     brief §11 ("mobile must stack fields, no horizontal scrolling, no
     unusably shrunk inputs"). The .item-tax-row <td> itself needs no
     override here — see the doc comment where these classes are defined. */
  .item-tax-toggle-line{justify-content:stretch}
  .tax-toggle-button{width:100%}
  .item-tax-rate-grid{grid-template-columns:1fr}
  .item-tax-amount-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* =========================================================================
   PHASE 10 — UI/UX REDESIGN. Screen-only, CSS-only, additive (per the exact
   convention every prior phase in this file already follows — see the
   Phase 9 banner above: "no existing rule above this point is changed").
   Every rule below overrides an existing selector's COLOR/BORDER/SHADOW/
   BACKGROUND/transition properties only, never a property any @media block
   elsewhere in this file also sets (padding/gap/grid-template-columns/font-
   size/flex-direction/display) — those are pure layout/breakpoint concerns
   with zero color content, so this section can never fight the cascade
   against a narrower-viewport rule the way a careless append could (see the
   "Note on placement" comment on #invoices-workspace above for exactly the
   bug this discipline avoids: a later, unconditional rule silently beating
   an earlier media-query rule at its own breakpoint because CSS specificity
   ties break on source order, not on which media query is "more specific").
   Where a genuine SIZE change was wanted (radius, a few paddings called out
   inline below) it was made either via the token block at the top of this
   file (zero breakpoints touch --radius/--radius-lg, confirmed by grep) or
   as a direct in-place edit to the one original rule, never appended here.
   Nothing in this section touches @media print below — that block is
   completely self-contained on its own --pd-* custom properties (see its
   own header comment) and none of the selectors below share a name with
   any .print-doc-* selector, so the printed quotation/tax invoice is
   provably unaffected by anything in this section. */

/* ---- Buttons: stronger primary, softer secondary, all with a deliberate
   lift+glow on hover/focus and a real disabled state (150-250ms range). ---- */
.btn{border-radius:var(--radius-sm);transition:transform var(--ease),box-shadow var(--ease),background var(--ease),border-color var(--ease),filter var(--ease)}
.btn-primary{background:var(--gradient-brand);box-shadow:0 8px 20px color-mix(in srgb,var(--green) 35%,#4CAF50)}
.btn-primary:hover:not(:disabled){filter:brightness(1.08);transform:translateY(-1px);box-shadow:0 12px 26px color-mix(in srgb,var(--green) 45%,transparent)}
.btn-primary:active:not(:disabled){transform:translateY(0);filter:brightness(.97)}
.btn-secondary{background:var(--surface);box-shadow:none}
.btn-secondary:hover:not(:disabled){border-color:color-mix(in srgb,var(--green) 40%,var(--line));background:var(--surface-elevated);color:var(--ink)}
.btn-danger{box-shadow:0 8px 20px color-mix(in srgb,var(--danger) 30%,transparent)}
.btn-danger:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 12px 26px color-mix(in srgb,var(--danger) 40%,transparent)}
.btn:disabled{transform:none!important;box-shadow:none!important}
.icon-button{border-radius:var(--radius-sm);transition:background var(--ease),color var(--ease)}
.theme-toggle{transition:border-color var(--ease),background var(--ease)}
.theme-toggle:hover{border-color:color-mix(in srgb,var(--green) 35%,var(--line));background:var(--surface-elevated)}

/* ---- Inputs: dark-aware surface, clearer focus ---- */
input,select,textarea{background:var(--surface);border-radius:var(--radius-sm);transition:border-color var(--ease),box-shadow var(--ease)}
[data-theme=dark] input,[data-theme=dark] select,[data-theme=dark] textarea{background:var(--surface-elevated)}

/* ---- Cards: three-tier depth (canvas < surface < elevated-on-hover),
   accent top edge, real border-radius everywhere. .metric-card/.card share
   the base; .analytics-card/.leads-card opt in via the same var(--radius). ---- */
.metric-card,.card{border-radius:var(--radius);border-color:color-mix(in srgb,var(--line) 80%,transparent);transition:transform var(--ease),box-shadow var(--ease),border-color var(--ease),background var(--ease)}
.metric-card{position:relative;overflow:hidden;border-radius:var(--radius)}
.metric-card:before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:var(--line);opacity:.6}
.metric-card:hover{border-color:color-mix(in srgb,var(--green) 30%,var(--line));box-shadow:var(--shadow-elevated)}
[data-theme=dark] .metric-card:hover,[data-theme=dark] .analytics-card:hover{background:var(--surface-elevated)}
.metric-icon{border-radius:var(--radius-sm)}
.analytics-card{border-radius:var(--radius);border-color:color-mix(in srgb,var(--line) 80%,transparent);transition:border-color var(--ease),background var(--ease),box-shadow var(--ease)}
.analytics-card:hover{border-color:color-mix(in srgb,var(--green) 22%,var(--line))}
.leads-card{border-color:color-mix(in srgb,var(--line) 80%,transparent)}

/* Metric-card accent colours — "differentiated metric accents", scoped per
   grid via existing aria-label/class hooks already in the markup (no HTML
   change needed: js/ui.js's Lead-summary <section> already carries
   aria-label="Lead summary", js/ui.js's priority grid already has the
   .dashboard-priority ancestor class, and js/invoices-ui.js's invoice
   summary grid already has .invoice-summary-metrics — all three grids
   render a FIXED, unconditional 4-card order every time, confirmed by
   reading both render functions, so nth-child position reliably maps to
   the same metric every render). Position 2 is left at the base green
   rule intentionally (no override) rather than repeating it. */
.metrics[aria-label="Lead summary"] .metric-card:nth-child(1):before,
.invoice-summary-metrics .metric-card:nth-child(1):before{background:var(--info)}
.metrics[aria-label="Lead summary"] .metric-card:nth-child(1) .metric-icon,
.invoice-summary-metrics .metric-card:nth-child(1) .metric-icon{background:color-mix(in srgb,var(--info) 16%,transparent);color:var(--info)}
.metrics[aria-label="Lead summary"] .metric-card:nth-child(3):before{background:var(--warning)}
.metrics[aria-label="Lead summary"] .metric-card:nth-child(3) .metric-icon{background:color-mix(in srgb,var(--warning) 16%,transparent);color:var(--warning)}
.metrics[aria-label="Lead summary"] .metric-card:nth-child(4):before,
.dashboard-priority .metric-card:nth-child(4):before{background:var(--purple)}
.metrics[aria-label="Lead summary"] .metric-card:nth-child(4) .metric-icon,
.dashboard-priority .metric-card:nth-child(4) .metric-icon{background:color-mix(in srgb,var(--purple) 16%,transparent);color:var(--purple)}
.dashboard-priority .metric-card:nth-child(1):before{background:var(--warning)}
.dashboard-priority .metric-card:nth-child(1) .metric-icon{background:color-mix(in srgb,var(--warning) 16%,transparent);color:var(--warning)}
.dashboard-priority .metric-card:nth-child(2):before{background:var(--danger)}
.dashboard-priority .metric-card:nth-child(2) .metric-icon{background:color-mix(in srgb,var(--danger) 16%,transparent);color:var(--danger)}
.dashboard-priority .metric-card:nth-child(3):before,
.invoice-summary-metrics .metric-card:nth-child(1):before{background:var(--info)}
.dashboard-priority .metric-card:nth-child(3) .metric-icon{background:color-mix(in srgb,var(--info) 16%,transparent);color:var(--info)}
.invoice-summary-metrics .metric-card:nth-child(3):before{background:var(--warning)}
.invoice-summary-metrics .metric-card:nth-child(3) .metric-icon{background:color-mix(in srgb,var(--warning) 16%,transparent);color:var(--warning)}
.invoice-summary-metrics .metric-card:nth-child(4):before{background:var(--danger)}
.invoice-summary-metrics .metric-card:nth-child(4) .metric-icon{background:color-mix(in srgb,var(--danger) 16%,transparent);color:var(--danger)}

/* ---- Status badges: one consistent pill language (dot + tint + text),
   reusing the SAME semantic tokens across lead status, invoice status, and
   follow-up badges — replaces several one-off hex pairs (the old
   .status-contacted/.status-proposal-sent blue, .status-meeting-scheduled/
   .status-meeting-completed purple, .invoice-status-partial amber, and
   their separate [data-theme=dark] hex overrides) with var(--info)/
   var(--purple)/var(--warning) so there's one source of truth per colour
   instead of four near-duplicate hardcoded values. Dot indicator (already
   used by lead statuses via ::before) extended to invoice statuses too, so
   every status pill in the app now shares the same visual construction. ---- */
.badge,.followup-badge,[class*=invoice-status-]{border-radius:999px;transition:background var(--ease)}
/* .status-converted / .invoice-status-paid both originally hardcoded
   color:#477315 (a dark olive tuned for light-mode contrast on a pale
   --leaf-tinted background) with no [data-theme=dark] override anywhere in
   the file — on the new, much darker canvas this reads as dim/washed out
   (caught via real-browser screenshot review, not just code reading).
   var(--green) is already theme-aware (dark forest text on light bg, vivid
   emerald on dark bg) and IS the "success/paid/completed" token per the
   brief's own colour table, so swapping to it fixes contrast in both
   themes and removes another one-off hex in favour of the shared token. */
.status-converted,.invoice-status-paid{background:color-mix(in srgb,var(--green) 16%,transparent);color:var(--green)}
.status-contacted,.status-proposal-sent{background:var(--info-soft);color:var(--info)}
.status-meeting-scheduled,.status-meeting-completed{background:var(--purple-soft);color:var(--purple)}
.followup-upcoming{background:var(--info-soft);color:var(--info)}
.invoice-status-sent{background:var(--info-soft);color:var(--info)}
.invoice-status-sent:before{content:'';width:5px;height:5px;border-radius:50%;background:currentColor;margin-right:1px}
.invoice-status-partial{background:var(--warning-soft);color:var(--warning)}
.invoice-status-partial:before{content:'';width:5px;height:5px;border-radius:50%;background:currentColor;margin-right:1px}
.invoice-status-paid:before{content:'';width:5px;height:5px;border-radius:50%;background:currentColor;margin-right:1px}
.invoice-status-cancelled:before,.invoice-status-overdue:before{content:'';width:5px;height:5px;border-radius:50%;background:currentColor;margin-right:1px}
.invoice-status-draft:before{content:'';width:5px;height:5px;border-radius:50%;background:currentColor;margin-right:1px}
[class*=invoice-status-]{display:inline-flex;align-items:center;gap:5px}

/* ---- Tables: clearer header, softer row hover, transitions ---- */
th{border-bottom:1px solid var(--line)}
tbody tr{transition:background var(--ease)}
[data-theme=dark] tbody tr:hover{background:var(--surface-elevated)}

/* ---- Empty states: icon container gets a soft brand-tinted ring instead
   of a flat circle, per "empty states should look intentional" ---- */
.empty-icon{background:color-mix(in srgb,var(--green) 12%,var(--surface-soft));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--green) 18%,transparent)}
.dashboard-empty span{background:color-mix(in srgb,var(--green) 12%,var(--surface-soft));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--green) 18%,transparent)}

/* ---- Sidebar / navigation: left accent bar on the active item (via
   box-shadow so no layout shift), warmer hover, icon treatment ---- */
[data-theme=dark] .sidebar{background:var(--canvas);border-right-color:var(--line)}
.nav-item{border-radius:var(--radius-sm);transition:background var(--ease),color var(--ease),box-shadow var(--ease)}
.nav-item:hover{background:color-mix(in srgb,var(--green) 8%,transparent)}
.nav-item.is-active{background:color-mix(in srgb,var(--green) 14%,transparent);box-shadow:inset 3px 0 0 var(--green);font-weight:800}
.nav-icon{opacity:.85}
.nav-item.is-active .nav-icon{opacity:1}
.account-avatar{border-radius:var(--radius-sm)}
.logout-button{border-radius:var(--radius-sm)}
[data-theme=dark] .mobile-header,[data-theme=dark] .mobile-nav{background:var(--canvas)}
.mobile-nav .nav-item.is-active{box-shadow:inset 0 -3px 0 var(--green)}

/* ---- Auth page: elevated card on dark (gradient text was tried on
   .eyebrow here and deliberately reverted — that class is reused as a
   small uppercase kicker in a dozen places app-wide including tiny .82rem
   modal headers, where a gradient clip reads muddy rather than premium;
   plain var(--green) is legible everywhere and stays as-is). ---- */
.auth-panel{border-radius:var(--radius-lg)}
[data-theme=dark] .auth-panel{background:var(--surface-elevated)}

/* ---- Page heading: stronger hierarchy ---- */
.page-heading h1{font-weight:800;letter-spacing:-.04em}
.eyebrow{font-weight:800}

/* ---- Pipeline / conversion visualisation: thicker tracks, subtle glow on
   the active fill, dot markers reused from the badge language above ---- */
.progress-track{height:9px;background:color-mix(in srgb,var(--line) 55%,transparent)}
[data-theme=dark] .progress-track{background:var(--surface-soft)}
.progress-fill{transition:width var(--ease-slow)}
.pipeline-contacted{background:var(--info)}
.pipeline-meeting-scheduled,.pipeline-meeting-completed{background:var(--purple)}
.pipeline-proposal-sent{background:var(--warning)}
.pipeline-converted{background:var(--green)}
.conversion-value strong{background:var(--gradient-brand);background-clip:text;-webkit-background-clip:text;color:transparent}
.conversion-track{height:11px}
.meeting-item time{border-radius:var(--radius-sm)}
.activity-avatar{border-radius:var(--radius-sm)}
.activity-item,.meeting-item,.followup-meeting-item{border-radius:var(--radius-sm);transition:background var(--ease)}
.followup-meeting-item:hover{background:color-mix(in srgb,var(--green) 6%,transparent)}

/* ---- Invoice editor: stronger card hierarchy + emphasised Grand Total,
   mirroring the same "strongest element on the page" principle already
   approved for the print document, applied here to the on-screen editor
   only (.invoice-totals-* below is screen-only markup — the print
   document's own totals block is .print-doc-totals, a different class,
   inside @media print, untouched). ---- */
.invoice-editor-card{border-radius:var(--radius);transition:border-color var(--ease)}
.invoice-editor-card h2 span{border-radius:var(--radius-sm)}
.invoice-totals-grand{border-top:2px solid var(--green);margin-top:4px}
.invoice-totals-grand strong{color:var(--green);font-size:1.3rem}
.invoice-editor-footer{backdrop-filter:blur(6px)}

/* ---- Modal: elevated surface distinct from page cards ---- */
.modal{background:var(--surface-elevated);border-radius:var(--radius-lg)}
.modal-close{border-radius:var(--radius-sm);transition:background var(--ease),color var(--ease)}
.modal-close:hover{background:var(--surface-soft);color:var(--ink)}

/* ---- Toast: elevated surface + accent left edge ---- */
.toast{background:var(--surface-elevated);border-radius:var(--radius-sm);box-shadow:var(--shadow-elevated);box-shadow:var(--shadow-elevated),inset 3px 0 0 var(--green)}
.toast.error{box-shadow:var(--shadow-elevated),inset 3px 0 0 var(--danger)}

/* ---- Filter chips / active-filter-chips: pill polish ---- */
.filter-chip{transition:background var(--ease),border-color var(--ease)}

/* ---- Skeleton loading: theme-aware shimmer already uses tokens, just
   round it to match the new radius language ---- */
.skeleton-row td>span{border-radius:999px}

/* =====================================================================
   PHASE 10.1 — LOADING SCREEN REDESIGN (manual-test brief, Aug 2026).
   Additive, screen-only. .loading-screen is a new class added alongside
   the existing .page-loader on the two loading-state <section> elements
   (index.html's static pre-JS markup, and js/app.js's renderAuthLoading()
   template) — the base .page-loader,.fatal-error{...} rule and .spinner
   above are UNCHANGED, so .fatal-error (the real error state) is entirely
   unaffected, and nothing else in the app that might reference
   .page-loader loses anything.

   Design decision — always dark, regardless of the user's chosen theme:
   the one approved reference is a dark forest-green splash design, and the
   brief describes "the same visual language as the newly deployed Phase 10
   CRM" as a single fixed identity, the way most apps show one fixed-brand
   splash regardless of theme preference. Reading the live tokens here
   would mean light-theme users see a pale, washed-out screen that doesn't
   match the reference at all for the one moment brand-consistency matters
   most (first paint). Rather than invent new colors, --ld-* below are set
   to the exact same values as [data-theme=dark]'s real tokens above — this
   is the established dark palette, just always-on for this one screen. ===================================================================== */
.loading-screen{
  --ld-canvas:#0a1712;--ld-forest:#0a2b1c;--ld-green:#35d68c;--ld-green-dim:#1c8f5c;
  --ld-surface-elevated:#17291f;--ld-line:#213a2e;--ld-ink:#eaf3ee;--ld-muted:#8fa89c;
  position:relative;overflow:hidden;isolation:isolate;gap:0;justify-items:center;padding:32px 20px;
  color:var(--ld-muted);
  background:
    radial-gradient(ellipse 60% 45% at 50% 34%,color-mix(in srgb,var(--ld-green) 14%,transparent),transparent 70%),
    linear-gradient(175deg,var(--ld-canvas) 0%,var(--ld-forest) 130%);
}
.loading-screen>*{position:relative;z-index:1}
.loading-scene{position:relative;width:clamp(150px,30vw,220px);height:clamp(150px,30vw,220px);display:grid;place-items:center;margin-bottom:clamp(14px,3vw,22px);pointer-events:none}
.loading-ring{position:absolute;inset:0;border-radius:50%;border:1px solid color-mix(in srgb,var(--ld-green) 22%,transparent);animation:loading-spin 40s linear infinite}
.loading-ring-inner{inset:15%;border-color:color-mix(in srgb,var(--ld-green) 16%,transparent);animation-duration:30s;animation-direction:reverse}
.loading-dot{position:absolute;width:6px;height:6px;border-radius:50%;background:var(--ld-green);box-shadow:0 0 10px 2px color-mix(in srgb,var(--ld-green) 70%,transparent);animation:loading-spin 40s linear infinite}
.loading-dot-1{top:-3px;left:50%;margin-left:-3px}
.loading-dot-2{top:50%;right:-3px;margin-top:-3px;animation-duration:30s;animation-direction:reverse;opacity:.7}
.loading-dot-3{bottom:8%;left:8%;opacity:.55;animation-duration:55s}
.loading-dot-4{top:12%;right:14%;opacity:.5;animation-duration:48s;animation-direction:reverse}
@keyframes loading-spin{to{transform:rotate(360deg)}}
.loading-mark{width:clamp(64px,14vw,86px);height:clamp(64px,14vw,86px);border-radius:50%;display:grid;place-items:center;background:color-mix(in srgb,var(--ld-surface-elevated) 88%,transparent);border:1px solid var(--ld-line);box-shadow:0 0 34px color-mix(in srgb,var(--ld-green) 30%,transparent)}
.loading-leaf{position:relative;width:34%;height:34%}
.loading-leaf span{position:absolute;inset:0;background:linear-gradient(135deg,var(--ld-green),var(--ld-green-dim));border-radius:0 100% 100% 100%}
.loading-leaf span:first-child{transform:rotate(-38deg)}
.loading-leaf span:last-child{transform:rotate(52deg) scale(.82);opacity:.85}
.loading-brand{margin:0;font-family:var(--font-display);font-weight:800;font-size:clamp(1.35rem,4.4vw,1.9rem);color:var(--ld-ink);letter-spacing:-.02em}
.loading-brand span{color:var(--ld-green)}
.loading-tagline{margin:6px 0 0;font-size:.9rem;color:var(--ld-muted)}
.loading-bar{margin-top:clamp(22px,5vw,32px);width:min(280px,70vw);height:5px;border-radius:999px;background:color-mix(in srgb,var(--ld-line) 70%,transparent);overflow:hidden;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ld-green) 10%,transparent)}
.loading-bar-fill{display:block;width:38%;height:100%;border-radius:inherit;background:linear-gradient(90deg,transparent,var(--ld-green),transparent);box-shadow:0 0 12px color-mix(in srgb,var(--ld-green) 60%,transparent);animation:loading-bar-sweep 1.6s ease-in-out infinite}
@keyframes loading-bar-sweep{0%{transform:translateX(-110%)}100%{transform:translateX(340%)}}
.loading-status{margin:12px 0 0;font-size:.82rem;color:var(--ld-muted);animation:loading-pulse 2.4s ease-in-out infinite}
@keyframes loading-pulse{0%,100%{opacity:.7}50%{opacity:1}}
/* gap-based spacing rather than per-item borders — a border-left divider
   would leave a stray, disconnected line fragment above any item that
   wraps to its own row at narrow widths (verified at 320px), so each item
   is its own small pill instead; this reads cleanly whether the row stays
   in one line or wraps. */
.loading-features{list-style:none;display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:6px;margin:clamp(26px,6vw,36px) 0 0;padding:8px;border:1px solid var(--ld-line);border-radius:var(--radius-lg);background:color-mix(in srgb,var(--ld-surface-elevated) 55%,transparent);max-width:min(360px,88vw)}
.loading-features li{display:flex;align-items:center;gap:6px;padding:6px 14px;font-size:.78rem;font-weight:700;color:var(--ld-ink);border-radius:999px;background:color-mix(in srgb,var(--ld-canvas) 35%,transparent)}
.loading-feature-icon{font-size:.95rem;color:var(--ld-green)}
.loading-footer{margin:clamp(18px,4vw,26px) 0 0;font-size:.72rem;color:color-mix(in srgb,var(--ld-muted) 75%,transparent)}
@media (prefers-reduced-motion:reduce){
  .loading-ring,.loading-dot,.loading-bar-fill,.loading-status{animation:none!important}
  .loading-bar-fill{width:60%;transform:none;opacity:.8}
}

/* Print / PDF document (§14, redesigned — manual-test fix, Aug 2026).
   #invoice-print-view is hidden on screen at all times; the @media print
   block below is the ONLY place it's ever shown, and it hides everything
   else in the document while doing so. window.print() targeting this node
   is the entire "PDF" mechanism (every modern browser's print dialog
   offers "Save as PDF") — zero new dependency, per decision 8.

   THE FIX: previously #invoice-print-view lived nested inside #app's own
   innerHTML, so `body>*:not(.invoice-print-view){display:none!important}`
   hid #app (a real direct <body> child) — and a `display:none` ancestor
   always wins over a descendant's own display value, so the print view
   stayed invisible no matter what display it set on itself. index.html now
   places #invoice-print-view as a genuine top-level <body> sibling, so this
   same selector actually reaches it. The selector itself didn't need to
   change; the DOM did. See index.html's comment on that node for the full
   root-cause writeup.

   .print-doc's own custom properties are declared directly on .print-doc
   (not on :root/[data-theme]) so the printed page is always brand-green-
   on-white regardless of whichever screen theme (light/dark) happens to be
   active in the CRM at the moment of printing — [data-theme=dark] swaps
   --green/--ink to lighter values meant for a dark screen background, which
   would be low-contrast and wrong on white paper. */
.invoice-print-view{display:none}
@media print{
  body>*:not(.invoice-print-view){display:none!important}
  html,body{background:#fff!important;height:auto}
  .invoice-print-view{display:block!important;position:static;margin:0;padding:0;background:#fff}
  @page{size:A4;margin:0}

  /* Visual-strength pass (manual-test fix, Aug 2026): the structural fix
     above (relocating #invoice-print-view) made the document print at all;
     this pass is about how it reads once printed — reported as "too dim,
     small, compressed... looks like a web form printed onto A4" next to the
     BioSanjeevani reference. None of the HTML structure below changed for
     this pass, and neither did the logo <img> itself — only its printed
     size, which stays proportional (object-fit:contain), same as before.
     Every number below was deliberately raised together — type scale,
     section spacing, panel padding, row height, border weight — rather
     than just bumping font-size, per the explicit "don't just increase
     font sizes everywhere" direction: a bigger font in a cramped box still
     reads as cramped. --pd-muted itself was also darkened (#5b6b62 ->
     #45564d) so secondary text stops contributing to the "dim" feel
     wherever it's used, without a dozen individual overrides. */
  /* Page side margins (Phase 19 correction): 300dpi pixel measurement of
     the reference PDF found its printed content box sitting ~10.5mm in
     from each page edge (125px and 124px of a 2479px-wide page at 300dpi),
     not the 14mm this template had been using. Every internal proportion
     fixed in Phase 18 (the .ti-* grid ratios, item-table columns, etc.) is
     a percentage OF this content box, so getting the box's own width wrong
     silently shifts the entire document's visual rhythm even when every
     internal percentage is individually correct — this is very likely why
     the layout still read as "off" against the reference after Phase 18.
     Shared by both the Tax Invoice (.ti-doc) and the Quotation
     (.print-doc-* below), since both extend this same base class. */
  .print-doc{
    --pd-green:#176b42;--pd-green-dark:#103d29;--pd-green-soft:#e3f0e6;
    --pd-ink:#16241d;--pd-muted:#45564d;--pd-line:#c3d4c9;
    --pd-amber-bg:#fdf6e0;--pd-amber-border:#dcb951;--pd-amber-text:#6b4e0f;
    width:100%;min-height:100%;padding:0 10.5mm 12mm;
    font-family:var(--font-body);font-size:11.5pt;line-height:1.4;
    color:var(--pd-ink);background:#fff;
  }
  .print-doc *{box-sizing:border-box}
  .print-doc address,.print-doc p{overflow-wrap:anywhere}

  .print-doc-topbar{height:7mm;margin:0 -10.5mm 10mm;background:var(--pd-green-dark)}

  .print-doc-header{display:flex;align-items:flex-start;justify-content:space-between;gap:24px;padding-top:1mm;padding-bottom:16px;border-bottom:3px solid var(--pd-green)}
  .print-doc-brand{display:flex;gap:16px;align-items:flex-start;min-width:0}
  .print-doc-logo{flex:0 0 auto;width:76px;height:76px;object-fit:contain}
  .print-doc-company{min-width:0}
  .print-doc-company h2{margin:0 0 4px;font-family:var(--font-display);font-size:20pt;font-weight:800;color:var(--pd-green-dark);letter-spacing:-.015em;line-height:1.12}
  .print-doc-company address,.print-doc-company p{margin:0 0 2px;font-style:normal;font-size:9.7pt;line-height:1.32;color:var(--pd-muted);white-space:pre-line}
  .print-doc-company .print-doc-gstin{font-weight:800;color:var(--pd-ink);font-size:10pt;margin-top:3px}
  .print-doc-meta{flex:0 0 auto;text-align:right;min-width:220px}
  .print-doc-meta h1{margin:0;font-family:var(--font-display);font-size:28pt;font-weight:800;letter-spacing:.025em;color:var(--pd-ink);line-height:1.05}
  .print-doc-subtitle{margin:4px 0 0;color:var(--pd-green);font-size:9.5pt;font-weight:800;letter-spacing:.16em;text-transform:uppercase}
  .print-doc-meta-list{margin:10px 0 0;display:grid;gap:5px;font-size:10.3pt}
  .print-doc-meta-row{display:flex;justify-content:flex-end;gap:12px}
  .print-doc-meta-row span:first-child{color:var(--pd-muted);font-weight:600}
  .print-doc-meta-row span:last-child{font-weight:800;color:var(--pd-ink);font-size:10.8pt}

  .print-doc-parties{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin:16px 0;padding:16px 22px;border:1px solid color-mix(in srgb,var(--pd-green) 30%,transparent);border-radius:8px;background:var(--pd-green-soft)}
  .print-doc-label{margin:0 0 4px;color:var(--pd-green-dark);font-size:8.6pt;font-weight:800;letter-spacing:.12em;text-transform:uppercase}
  .print-doc-party strong{display:block;margin-bottom:2px;font-size:12.3pt;font-weight:800;color:var(--pd-ink)}
  .print-doc-party address,.print-doc-party p{margin:0 0 1px;font-style:normal;font-size:10pt;line-height:1.28;color:var(--pd-ink);white-space:pre-line}

  .print-doc-terms{margin:0 0 14px;padding:12px 20px;line-height:1.35;border:1.5px solid var(--pd-amber-border);border-radius:8px;background:var(--pd-amber-bg);font-size:10.2pt;color:var(--pd-amber-text);break-inside:avoid}
  .print-doc-terms strong{color:var(--pd-amber-text);font-size:10.4pt}

  /* THE actual right-side overflow bug (Phase 19 correction): every prior
     round's percentage math was correct but was being silently overridden.
     The base stylesheet's generic `table{width:100%;min-width:840px;...}`
     rule (for the CRM's own on-screen data tables, e.g. the Leads grid,
     which needs a floor so it doesn't get too cramped before its
     .table-wrap's horizontal scroll kicks in) has no print-media guard and
     no exclusion for .print-doc-items — since it sets width AND min-width
     while this class only ever set `width:100%`, the class selector's
     higher specificity won the `width` property but left the element
     selector's `min-width:840px` completely unopposed. 840px is WIDER than
     this whole printed page (about 794px of usable A4 width at 96dpi), so
     every quotation and invoice item table (.ti-items had the identical
     problem — see below) has been silently rendering past the right
     margin, edge-to-edge, this whole time — confirmed by measuring the
     table's actual getBoundingClientRect(), not just reading the CSS.
     `min-width:0` here cancels that floor for this one class only; the
     CRM's own on-screen tables are untouched. This is also why the
     previous round's column-percentage tuning never visibly "took" no
     matter how the numbers were adjusted — the outer table was overflowing
     regardless of its internal column math. Column widths are now an
     explicit <colgroup> (see the matching HTML in quotations-ui.js) sized
     for the actual widest realistic values (formatMinor always prints
     paise as "₹32,000.00"-style decimals), with tighter 8px horizontal
     cell padding so the four money columns have enough room without
     shrinking the font. */
  .print-doc-items{width:100%;min-width:0;table-layout:fixed;border-collapse:collapse;margin:4px 0 14px;font-size:9.9pt}
  .print-doc-items thead{display:table-header-group}
  .print-doc-items thead th{background:var(--pd-green);color:#fff;padding:8px 8px;text-align:left;font-size:8.4pt;font-weight:800;letter-spacing:.06em;text-transform:uppercase}
  .print-doc-items td{padding:8px 8px;border-bottom:1px solid var(--pd-line);vertical-align:top}
  .print-doc-items .num,.print-doc-items th.num{text-align:right}
  .print-doc-items tbody tr{break-inside:avoid}
  .print-doc-items tbody tr:nth-child(even){background:color-mix(in srgb,var(--pd-green-soft) 55%,transparent)}
  .print-doc-items tbody tr:last-child td{border-bottom:2px solid var(--pd-green)}
  .print-doc-pct{display:block;color:var(--pd-muted);font-size:8.3pt;margin-top:1px}
  .print-doc-empty-items td{padding:22px;text-align:center;color:var(--pd-muted);font-style:italic}

  .print-doc-summary{display:flex;justify-content:flex-end;margin-bottom:16px;break-inside:avoid}
  .print-doc-totals{display:grid;gap:6px;width:min(100%,380px);padding:12px 24px;border:1.5px solid var(--pd-green);border-radius:8px;font-size:10.6pt;line-height:1.3}
  .print-doc-totals>div{display:flex;justify-content:space-between;gap:18px}
  .print-doc-totals>div span:first-child{color:var(--pd-muted)}
  .print-doc-totals>div span:last-child{font-weight:700;color:var(--pd-ink)}
  .print-doc-grand{margin:6px -24px -12px;padding:12px 24px;border-top:2.5px solid var(--pd-green);border-radius:0 0 6px 6px;background:var(--pd-green-soft);font-size:17pt;font-weight:800;color:var(--pd-green-dark)}
  .print-doc-grand span:first-child{white-space:nowrap}
  .print-doc-grand span:last-child{color:var(--pd-green-dark);font-weight:800;white-space:nowrap}

  .print-doc-signature{display:flex;justify-content:flex-end;margin:10px 0 6px;break-inside:avoid}
  .print-doc-signature-block{text-align:center;min-width:230px}
  .print-doc-signature-block p{margin:0;font-size:10.1pt}
  .print-doc-sig-line{height:26px}
  .print-doc-stamp-label{margin-top:4px;color:var(--pd-muted);font-size:8.8pt}

  .print-doc-footer{margin-top:8px;padding-top:7px;border-top:1.5px solid var(--pd-line);text-align:center;break-inside:avoid}
  .print-doc-thanks{margin:0 0 3px;color:var(--pd-green-dark);font-weight:800;font-size:12.3pt}
  .print-doc-legal{margin:0;color:var(--pd-muted);font-size:8.3pt;letter-spacing:.02em}
}

/* ==========================================================================
   PHASE 17 — Finance → Invoice → Download PDF. A dedicated "TAX INVOICE"
   layout for ISSUED invoices only, matching the Biosanjeevani reference PDF
   (bordered-grid style) field-for-field. Deliberately NOT built by editing
   any .print-doc-* selector above — those are still exactly what renders
   the draft-invoice "quotation preview" branch of renderInvoicePrintView
   AND quotations-ui.js's entirely separate renderQuotationPrintView, and
   neither of those may change for this feature. Every selector below lives
   under the new .ti- namespace, so nothing here can ever collide with or
   affect the rules above. The wrapping element carries both `print-doc`
   (for its --pd-* color custom properties, base page padding/font, and the
   shared .print-doc-topbar strip already styled above) and the new
   `ti-doc` class — see invoices-ui.js's renderInvoicePrintView for the
   markup this styles.

   FIELDS SHOWN AS "—": HSN (per item and in the tax summary), PAN Number,
   and Place of Supply. None of these have a backing database column
   (confirmed against the exhaustive Phase 9/11 migration SQL — no live
   query available), and adding one is out of scope for this feature per
   the brief's own "no schema changes" stop condition — see the doc comment
   on renderInvoicePrintView in invoices-ui.js for the full breakdown. */
@media print{
  .ti-badge-row{display:flex;align-items:center;gap:10px;margin:2mm 0 5px}
  .ti-badge-title{font-family:var(--font-display);font-size:12.5pt;font-weight:800;letter-spacing:.04em;color:var(--pd-ink)}
  .ti-badge-chip{padding:3px 10px;border:1.3px solid var(--pd-muted);border-radius:3px;font-size:8pt;font-weight:700;letter-spacing:.05em;color:var(--pd-muted);text-transform:uppercase}

  /* ONE CSS grid for the whole top block (company/meta/bill/ship), not two
     independently-positioned boxes — this is the fix for the top-right
     alignment bug reported after the first pass: the previous
     .ti-header-box (1fr/200px) and .ti-parties-box (1fr/1fr) computed two
     DIFFERENT column widths, so their vertical dividers never lined up.
     A single grid has exactly one set of column tracks, shared by every
     row, so the divider between "company info | Invoice No./Date" and the
     one between "Bill To | Ship To" are now geometrically the same line —
     alignment is guaranteed by construction, not by coincidentally-matching
     numbers in two places. Borders are drawn with the "gap as border"
     technique (colored background showing through a small grid gap) so
     every internal division is exactly the same 1.3px thickness with zero
     risk of the 1-3px double-border rounding gaps that bedeviled the
     previous two-separate-boxes version.
     Column ratio (Phase 18 correction): 300dpi pixel measurement of the
     reference PDF puts this divider at x=1242 of a 125–2355 print area,
     i.e. 50.0% — a true 1fr/1fr split, not the previous round's guessed
     1fr/220px. That guessed narrow right column was *also* the root cause
     of the "Ship To looks compressed" complaint: Bill To and Ship To sit in
     the same two grid tracks as the header row above them, so a too-narrow
     right column squeezed Ship To into it exactly as much as it squeezed
     Invoice No./Date — fixing the ratio here fixes both symptoms from one
     shared cause. */
  .ti-top-grid{display:grid;grid-template-columns:1fr 1fr;gap:1.3px;background:var(--pd-green-dark);border:1.3px solid var(--pd-green-dark);break-inside:avoid}
  .ti-cell{background:#fff}
  .ti-cell-company,.ti-cell-meta{padding:0}
  .ti-header-company{display:flex;gap:10px;padding:9px 12px;height:100%}
  .ti-header-logo{flex:0 0 auto;width:50px;height:50px;object-fit:contain}
  .ti-header-company h2{margin:0 0 3px;font-family:var(--font-display);font-size:12.5pt;font-weight:800;color:var(--pd-green-dark);line-height:1.16}
  .ti-header-company address{margin:0 0 3px;font-style:normal;font-size:8.3pt;line-height:1.3;color:var(--pd-ink);white-space:pre-line}
  .ti-header-company-row{display:flex;flex-wrap:wrap;gap:4px 16px;margin-top:2px}
  .ti-header-company-row span{font-size:8.3pt;color:var(--pd-ink)}
  .ti-header-company-row b,.ti-header-pan b{font-weight:800}
  .ti-header-pan{margin:2px 0 0;font-size:8.3pt;color:var(--pd-ink)}
  /* Invoice No. / Invoice Date (Phase 18 correction): the reference shows
     these SIDE BY SIDE as two centered halves of the meta column, not
     stacked as the previous round had them — a cropped view of the
     reference's own meta cell confirmed this directly. A narrow stacked
     column reads as "cramped" even once the outer column width is fixed,
     so this row/column change is what actually resolves the right-side
     spacing complaint, more than the ratio change above does on its own. */
  .ti-header-meta{padding:9px 12px;height:100%;box-sizing:border-box;display:flex;flex-direction:row;align-items:center}
  .ti-header-meta div{flex:1 1 50%;text-align:center}
  .ti-header-meta div span:first-child{display:block;font-size:7.6pt;font-weight:700;color:var(--pd-muted)}
  .ti-header-meta div span:last-child{display:block;margin-top:4px;font-size:10.2pt;font-weight:800;color:var(--pd-ink)}

  .ti-cell-bill,.ti-cell-ship{padding:8px 12px;font-size:8.6pt;color:var(--pd-ink)}
  .ti-party-label{margin:0 0 4px;font-size:7.6pt;font-weight:800;letter-spacing:.06em;color:var(--pd-green-dark)}
  .ti-cell-bill strong,.ti-cell-ship strong{display:block;margin-bottom:3px;font-size:9.8pt;font-weight:800}
  .ti-cell-bill p,.ti-cell-ship p{margin:0}

  /* Item table column widths (Phase 18 correction): replaced with the
     percentages measured directly from the reference's own item-table
     grid lines (S.NO. 9.4 / Items 35.5 / HSN 13.7 / Qty. 11.1 / Rate 9.1 /
     Tax 7.7 / Amount 13.5 — sums to 100.0), rather than the previous
     round's approximate 6/32/11/13/12/12/14 split.
     Phase 19 correction: `min-width:0` added — this table (and
     .ti-tax-summary below) was being silently forced to the generic
     `table{min-width:840px}` rule meant for the CRM's own on-screen data
     tables (840px is wider than this whole printed page), which overrode
     the correct percentage math above with a hard floor regardless of it.
     See the long comment on .print-doc-items above for the full
     root-cause writeup — the same bug, same fix, in both templates. */
  .ti-items{width:100%;min-width:0;border-collapse:collapse;table-layout:fixed;margin-top:0;font-size:8.7pt}
  .ti-items thead{display:table-header-group}
  .ti-items th{border:1.3px solid var(--pd-green-dark);background:var(--pd-green-soft);padding:6px 8px;font-size:7.4pt;font-weight:800;letter-spacing:.03em;text-transform:uppercase;text-align:left;color:var(--pd-green-dark)}
  .ti-items td{border:1.3px solid var(--pd-green-dark);padding:6px 8px;vertical-align:top}
  .ti-items .ti-num,.ti-items th.ti-num{text-align:right}
  .ti-items tbody tr{break-inside:avoid}
  .ti-items th:nth-child(1),.ti-items td:nth-child(1){width:9.4%}
  .ti-items th:nth-child(2),.ti-items td:nth-child(2){width:35.5%;text-align:left}
  .ti-items th:nth-child(3),.ti-items td:nth-child(3){width:13.7%}
  .ti-items th:nth-child(4),.ti-items td:nth-child(4){width:11.1%}
  .ti-items th:nth-child(5),.ti-items td:nth-child(5){width:9.1%}
  .ti-items th:nth-child(6),.ti-items td:nth-child(6){width:7.7%}
  .ti-items th:nth-child(7),.ti-items td:nth-child(7){width:13.5%}
  .ti-pct{display:block;color:var(--pd-muted);font-size:7.3pt}
  .ti-empty-items td{padding:16px;text-align:center;color:var(--pd-muted);font-style:italic}
  /* TOTAL / RECEIVED AMOUNT / BALANCE AMOUNT rows (Phase 18 correction):
     pixel-measuring these three rows in the reference found vertical
     divider lines at the EXACT SAME 7 column boundaries as the item rows
     above them — i.e. these are full 7-cell rows sharing the item table's
     own column grid (see the <colgroup>-free nth-child widths above,
     which apply automatically since it's the same table), not a
     colspan-merged 2-cell layout as the previous round had. Only TOTAL is
     shaded; RECEIVED AMOUNT / BALANCE AMOUNT are plain white rows with the
     same bold weight. */
  .ti-total-row{font-weight:800;background:var(--pd-green-soft)}
  .ti-total-row td,.ti-summary-row td{text-align:right}
  .ti-summary-row{font-weight:800}

  /* Tax summary column widths (Phase 18 correction): a <colgroup> is used
     instead of nth-child widths because the header's first row (HSN/SAC,
     Taxable Value, IGST[colspan=2], Total Tax Amount — 4 cells) and second
     row (Rate, Amount — 2 cells) don't share a cell count with the 5-cell
     body rows, so nth-child can't express one consistent set of column
     widths across all three rows the way it can for the item table above.
     Percentages are the reference's own measured column boundaries: HSN/SAC
     23.1 / Taxable Value 24.4 / Rate 6.7 / Amount 15.5 / Total Tax Amount
     30.3 (sums to 100.0). The reference also right-aligns every column,
     including HSN/SAC and Taxable Value — the previous round's left-align
     on the first column didn't match it. */
  .ti-tax-summary{width:100%;min-width:0;border-collapse:collapse;table-layout:fixed;margin:10px 0 12px;font-size:8.4pt;break-inside:avoid}
  .ti-tax-summary th,.ti-tax-summary td{border:1.3px solid var(--pd-green-dark);padding:6px 8px}
  .ti-tax-summary thead th{background:var(--pd-green-soft);font-size:7.4pt;font-weight:800;text-transform:uppercase;text-align:center;color:var(--pd-green-dark)}
  .ti-tax-summary td{text-align:right}
  .ti-tax-summary tbody .ti-total-row td{font-weight:800}

  .ti-words-box{border:1.3px solid var(--pd-green-dark);padding:8px 12px;font-size:8.8pt;break-inside:avoid}
  .ti-words-box p:first-child{margin:0 0 3px;font-size:7.6pt;font-weight:800;color:var(--pd-muted);text-transform:uppercase;letter-spacing:.04em}
  .ti-words-box p:last-child{margin:0;font-weight:700;color:var(--pd-ink)}

  /* Terms / Signatory footer (Phase 18 correction): the reference's own
     divider between these two sections sits at exactly 50% of the page's
     print width, not the previous round's fixed 200px signature column —
     that fixed width is also what caused the "excessive empty space /
     unbalanced" complaint, since 200px is much narrower than the
     reference's true half-width signatory column. */
  .ti-footer-box{display:flex;border:1.3px solid var(--pd-green-dark);border-top:none;break-inside:avoid}
  .ti-terms{flex:0 0 50%;padding:9px 12px;font-size:8.2pt;line-height:1.5;color:var(--pd-ink);box-sizing:border-box}
  .ti-terms p:first-child{margin:0 0 4px;font-size:7.6pt;font-weight:800;color:var(--pd-muted);text-transform:uppercase;letter-spacing:.04em}
  .ti-terms p{margin:0 0 2px}
  .ti-signature{flex:0 0 50%;padding:9px 12px;border-left:1.3px solid var(--pd-green-dark);display:flex;flex-direction:column;justify-content:flex-end;align-items:center;text-align:center;font-size:8.2pt;box-sizing:border-box}
  .ti-signature-space{height:30px}
  .ti-signature p{margin:0}
}

/* ==========================================================================
   PHASE 11 — QUOTATION MANAGEMENT SYSTEM (manual-test brief, Aug 2026).

   Additive only, per this project's phase-additive CSS convention (see the
   PHASE 10 section comment above) — nothing below redefines an existing
   selector. Everything the quotation module's screen UI needs beyond this
   section is already covered by classes shared with Leads/Invoices
   (.card/.leads-card/.metrics/.metric-card/.toolbar/.table-wrap/
   .empty-state/.invoice-editor-page/.invoice-editor-shell/
   .invoice-editor-card/.invoice-editor-footer/.invoice-totals-summary/
   .form-error/.icon-button/.btn/.badge, etc. — see js/quotations-ui.js's
   own file-level comment for why those are reused rather than reinvented).
   The quotation PDF needs NO new print CSS at all: it renders through the
   exact same #invoice-print-view mount point and .print-doc-* classes
   styled in the @media print block directly above, unchanged.

   What's genuinely new here: (1) seven quotation status badge colors —
   a distinct domain from invoice-status-*, so a separate class family
   (.quotation-status-*) rather than extending [class*=invoice-status-]'s
   selector; and (2) two small informational banners for the quotation
   editor (past-validity hint, converted-to-invoice notice) plus a "past
   validity" list-row hint and the Lead Integration action button — all
   built from the same var(--*)-driven color approach the PHASE 10 pass
   established just above (color-mix against semantic tokens, so every
   swatch already adapts correctly to [data-theme=dark] with no separate
   dark override needed — same reasoning as .invoice-status-sent/-partial
   right above). Only .quotation-status-converted needs one dark override,
   for the same reason .btn-primary already has one: white text on a solid
   var(--green) fill needs to flip to a dark ink in the dark theme, where
   --green is a bright, light color rather than a deep one. */

.quotation-status-draft{background:color-mix(in srgb,var(--muted) 12%,transparent);color:var(--muted)}
.quotation-status-sent{background:var(--info-soft);color:var(--info)}
.quotation-status-viewed{background:var(--purple-soft);color:var(--purple)}
.quotation-status-accepted{background:color-mix(in srgb,var(--green) 14%,transparent);color:var(--green)}
.quotation-status-rejected{background:var(--danger-soft);color:var(--danger)}
.quotation-status-expired{background:var(--warning-soft);color:var(--warning)}
.quotation-status-converted{background:var(--green);color:#fff}
[data-theme=dark] .quotation-status-converted{color:#0e1f18}
.quotation-status-badge:before{content:'';width:5px;height:5px;border-radius:50%;background:currentColor;margin-right:1px}
[class*=quotation-status-]{display:inline-flex;align-items:center;gap:5px;border-radius:999px;transition:background var(--ease)}

/* List-row "past validity" hint (sent/viewed quotations whose valid_until
   has passed) — informational only, never changes the stored/displayed
   status (see quotation-status.js's isQuotationPastValidity doc comment).
   Reuses the warning token, same as .quotation-status-expired above, since
   both communicate "this needs a look." */
.quotation-past-validity{color:var(--warning);font-weight:700}

/* Quotation editor banners. .quotation-validity-note mirrors
   .lead-readonly-note's exact shape (neutral informational strip) with the
   warning tint swapped in. .quotation-converted-note is the stronger,
   positive "this is done" banner shown once a quotation has been converted
   — greener/more prominent since it marks a completed, non-reversible
   workflow step, with its own "View Invoice" action inline. */
.quotation-validity-note{margin:0 0 18px;padding:10px 12px;border:1px solid var(--warning);border-radius:9px;background:var(--warning-soft);color:var(--warning);font-size:.82rem;font-weight:600;line-height:1.5}
.quotation-converted-note{display:flex;flex-wrap:wrap;align-items:center;gap:10px 14px;margin:0 0 18px;padding:12px 14px;border:1px solid var(--green);border-radius:9px;background:color-mix(in srgb,var(--green) 12%,transparent);color:var(--ink);font-size:.85rem;line-height:1.5}
.quotation-converted-note strong{color:var(--green)}
.quotation-converted-note .btn{flex:0 0 auto;min-height:32px;padding:6px 12px;font-size:.78rem}

/* Lead Integration — "Create Quotation" row action (see renderLeads in
   js/ui.js). Plain .icon-button already covers its base look; this just
   tints it distinctly from Edit (green/default) and Delete (danger) so all
   three read as separate actions at a glance. */
.create-quotation-from-lead{color:var(--info)}
.create-quotation-from-lead:hover{background:var(--info-soft)}

/* Mobile nav overflow fix (found during Phase 11 responsive verification,
   real Playwright pass at 320/375/390px — not a hypothetical). Adding the
   5th item ("Quotations") to .mobile-nav pushed its five single-word,
   unbreakable-text buttons (Dashboard/Leads/Quotations/Invoices/Team) to a
   combined ~401px minimum content width — wider than the 320px viewport
   Phase 10 was already verified against, with the previous 4 items.
   Two contributing causes, both fixed:
   1. ROOT CAUSE: the mobile single-column grid track at max-width:899px
      (`.app-shell{grid-template-columns:1fr}`, a few lines above this
      section) was missing the `minmax(0,...)` guard the desktop two-column
      rule already has one line above IT. A bare `1fr` track's automatic
      minimum is its content's min-content size, not 0 — so with 401px of
      unbreakable nav text inside it, the whole single-column layout refused
      to shrink below 401px regardless of viewport width, dragging the
      entire page into horizontal overflow (confirmed by measuring
      .mobile-nav's own clientWidth reported as 401px even at a 320px
      viewport, before this fix). Changed that rule to
      `grid-template-columns:minmax(0,1fr)`, mirroring the desktop rule's
      already-correct pattern — this is the standard fix for CSS Grid's
      well-known "blowout" behavior, and only ever constrains size that was
      already trying to overflow; it changes nothing when content fits.
   2. BELT AND SUSPENDERS: even with the grid fixed, flexbox still can't
      shrink a single-word button's text below its own min-content width —
      it would just move the same 401px-vs-320px mismatch from the page
      level to the nav row itself. Rather than shrinking/wrapping the
      labels, the row now scrolls horizontally within itself (a standard,
      expected mobile tab-bar pattern) via overflow-x:auto + hidden
      scrollbar; flex:0 0 auto keeps every button at its natural, readable
      width instead of flexbox fighting text that can't wrap.
   Both scoped to the same max-width:899px range this layout already only
   applies in — zero effect on desktop, which was never affected
   (.side-nav's items stack vertically, one per row, not in a single row). */
@media(max-width:899px){
  .mobile-nav{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}
  .mobile-nav::-webkit-scrollbar{display:none}
  .mobile-nav .nav-item{flex:0 0 auto}
}

/* ==========================================================================
   PHASE 12 — UI SPACING & MOBILE UX POLISH (manual-test brief, Aug 2026).
   Visual/spacing/responsive refinement ONLY — per this project's established
   phase-additive CSS convention (see the PHASE 10/11 section banners
   above), nothing below touches business logic, and every existing
   selector's color/content/structural behavior from Phase 6-11 is left
   exactly as-is except the two small in-place value edits noted where they
   happened (search "PHASE 12 in-place edit" elsewhere in this file):
   `.filter-row{grid-template-columns:1fr}` at the existing 719px breakpoint
   became `1fr 1fr` (Requirement 5), and `#team-management{margin-top:20px}`
   became `margin-top:var(--space-section)` (Requirement 1 — replacing an
   old one-off invented pixel value with the same token every other major
   section boundary already uses, exactly the "spacing bug fix" precedent
   already set by the #invoices-workspace comment above). Nothing else in
   this file was edited in place; everything else needed is additive, below.

   Every gap value below reuses an EXISTING token from :root's spacing
   system (--space-section/--space-section-mobile/--space-control-gap/
   --space-control-gap-tight) — no new spacing variable was invented. Real
   Playwright/Chromium measurements (not visual guesswork) at all 9 required
   widths (320/375/390/430/768/820/900/1024/1440) drove every decision
   below; see the Phase 12 report for the full before/after pixel tables.
   Two requirements turned out to already be satisfied by the EXISTING
   Phase 10/11 CSS with zero change needed — documented here rather than
   changed for the sake of changing something:
     - Req 2 (metric-card horizontal spacing): `.quotation-summary-metrics`/
       `.invoice-summary-metrics` already carry the base `.metrics` class,
       which already sets `gap:var(--space-card-gap)` (and the existing
       719px/1199px breakpoints already scale it to 10px/12px/16px). Measured
       10px at 320-430px, 12px at 768-1199px (an intentional tighter Phase 10
       tablet value, not a bug), 16px at 1440px — never 0, never touching, at
       any of the 9 widths. No change made.
     - The general `.filter-row` gap (`--space-control-gap-tight`, 8px) was
       already present at every width for the visible-controls case (the
       "[All statuses][Clear filters]" example) — the ACTUAL bug there was
       the single-column stacking at ≤719px fixed by the in-place edit above
       (Req 5), not a missing gap.
   ========================================================================= */

/* ---- Requirement 1: major section spacing — #quotations-workspace never
   had its own margin-top at all (unlike #invoices-workspace right above,
   which already gets one), so Leads sat flush against Quotations at every
   width (measured margin: 0px, confirmed via Chromium getBoundingClientRect
   before this fix). Exact same two tokens, exact same breakpoint, and the
   exact same "own media block placed after this declaration" cascade
   discipline as the #invoices-workspace fix's own comment above explains
   (a later, unconditional-within-its-media-query declaration is required
   to reliably beat the earlier one at the SAME specificity — verified with
   a real 375px measurement after this change, not assumed). Quotations→
   Invoices already measured a correct 28px/21px gap beforehand, because
   #invoices-workspace's margin-top is unconditional on which section
   precedes it — so only this one declaration was missing. #team-management
   already had SOME margin (just an inconsistent invented 20px, now fixed
   in place above); its own 719px companion below completes that token
   switch the same way every other section boundary already works. ---- */
#quotations-workspace{margin-top:var(--space-section)}
@media(max-width:719px){
  #quotations-workspace{margin-top:var(--space-section-mobile)}
  #team-management{margin-top:var(--space-section-mobile)}
}

/* ---- Requirement 3: global button spacing — two REAL, measured "buttons
   touching" bugs found (not the `.filter-row`/`.metrics` cases, which
   already had gaps — see the banner comment above):

   1. `.table-wrap td.actions` (the row-level View/Print/Duplicate/Edit/
      Delete icon-button group used identically by Leads/Quotations/
      Invoices) only ever got `display:flex;gap:5px` inside the EXISTING
      719px mobile card-row block below — outside that block, on desktop
      and tablet, the cell was plain inline flow with no flex/gap at all,
      so the icon-buttons rendered with a measured 0px gap between them
      (confirmed at 768/820/900/1024/1440px).

      PHASE 12.1 CORRECTION: the original Phase 12 fix here set
      `display:flex` directly on the `<td>` itself (guarded to
      `min-width:720px` so it could never fire alongside the existing
      719px mobile block, which already blockifies the whole table and
      makes flex safe there). That part was fine in isolation, but real
      Playwright verification during the 12.1 pass found it broke the
      Leads table's horizontal containment at tablet widths (measured at
      768/820px): giving a `<td>` an explicit `display:flex` while its
      `<table>/<tr>` siblings are still real table-layout boxes made
      Chromium paint that cell's content outside `.table-wrap`'s own
      `overflow-x:auto` clip — the actions column visibly spilled past
      the rounded white card's right edge onto the page background
      instead of scrolling inside it (confirmed by toggling the rule off
      in isolation: the moment `<td class="actions">` stops being
      `display:flex` and goes back to its native `table-cell` display,
      `.table-wrap`'s existing overflow-x:auto clip contains it exactly
      as it always did for every other column). Desktop (1440px) never
      revealed this because its table happens to fit without needing
      the scroll clip at all — the bug was there regardless, just
      invisible until something needed clipping.

      Fixed by dropping `display:flex` entirely and using an adjacent-
      sibling margin instead (`.icon-button + .icon-button{margin-left:
      ...}`) — same visual gap between the buttons, zero risk to the
      cell's table-layout participation since the `<td>`'s `display`
      is never touched. Still scoped to `min-width:720px` for the same
      cascade-safety reason as before.
   2. `.invoice-editor-footer-left` (shared by both the invoice editor and
      the quotation editor footers) had NO rule at all, at any width. The
      invoice editor only ever puts a single "Cancel Invoice" button there,
      so this was invisible in that case — but the quotation editor's
      transition buttons (Mark Sent/Viewed/Accepted/Rejected/Expired) are
      rendered with `.join('')` (no separator) into this same container, so
      a quotation with several legal transitions (e.g. a "sent" quotation
      offering Viewed/Accepted/Rejected/Expired = 4 buttons) rendered them
      LITERALLY adjacent with a measured 0px gap, confirmed via Chromium at
      every width tested. Fixed by giving it the exact same
      `display:flex;gap:var(--space-control-gap);flex-wrap:wrap` treatment
      `.invoice-editor-footer-right` (its sibling) already had, plus the
      same 719px full-width-button stacking behavior `.invoice-editor-
      footer-right` already gets in the existing 719px block above — so the
      two sides of the footer stay visually consistent at every width. ---- */
@media(min-width:720px){
  .table-wrap td.actions .icon-button+.icon-button{margin-left:var(--space-control-gap-tight)}
}
.invoice-editor-footer-left{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-control-gap)}
@media(max-width:719px){
  .invoice-editor-footer-left{flex-direction:column;align-items:stretch}
  .invoice-editor-footer-left .btn{width:100%}
}

/* ---- Requirement 5: mobile Leads filter layout — the in-place edit above
   changed the EXISTING 719px `.filter-row` rule from a single `1fr` column
   to `1fr 1fr`, which (combined with the unconditional `.filter-row{gap:
   var(--space-control-gap-tight)}` base rule already in this file) turns
   the previous 6-row vertical stack (search / status / domain / assignee /
   follow-up / clear, each its own full-width row) into the brief's
   requested compact grid: search stays full width (it lives in `.toolbar`,
   outside `.filter-row`, untouched), then status/domain/assignee/follow-up
   pair up two-per-row, with Clear filters as its own final row — EXCEPT
   Clear filters would otherwise land alone in the grid's first column
   (an odd 5th item in a 2-column grid), which is the one place the plain
   `1fr 1fr` change doesn't yet match the brief's target layout (Clear
   filters spanning full width). Scoped to `#clear-filters` specifically
   (Leads' own unique id — Quotations/Invoices use their own differently-
   named ids, `#quotation-clear-filters`/`#invoice-clear-filters`, and are
   deliberately NOT touched here: their filter-row only ever has 2 items,
   status + clear, so the plain 2-column grid already lays them out exactly
   like the brief's own "[All statuses] [Clear filters]" example with zero
   extra rule needed) so only Leads' 5-item row gets the extra full-width
   span; Quotations/Invoices keep their already-correct 2-up pairing. ---- */
@media(max-width:719px){
  #leads-workspace .filter-row #clear-filters{grid-column:1/-1}
}

/* ---- Requirement 4: mobile Dashboard Leads sticky header. Scoped to
   `#leads-workspace .leads-card-header` specifically — NOT the bare
   `.leads-card-header` class, which Quotations/Invoices/Team also use for
   their own headers and must NOT receive this behavior (only Leads was
   asked for). Compact by construction: only the header row itself (title +
   result count + Add lead button) becomes sticky, not the toolbar, filters,
   or table beneath it — "Do NOT make the entire Leads content permanently
   fixed" is satisfied by scoping the sticky rule to this one child element,
   not by any extra show/hide logic.

   THE STRUCTURAL FIX the brief explicitly warned might be needed ("position:
   sticky can fail when an ancestor has certain overflow behavior. Fix the
   actual structural cause if required") turned out to be real, not
   hypothetical: `.leads-card{overflow:hidden}` (used purely to clip child
   content to the card's own rounded corners — see its own original Phase 6
   rule far above) sits directly between this header and the page's actual
   scrolling context. A plain `position:sticky` declaration still computes
   (`getComputedStyle` reports "sticky") but never visibly STUCK — confirmed
   with a real Chromium test that jumped the scroll position 400px past the
   section's natural top and measured the header still moving right along
   with it instead of pinning at the top of the viewport. Removing
   `overflow:hidden` in an isolated test (same scroll test, same jump)
   immediately fixed it — the header pinned at the top exactly as expected —
   confirming the ancestor's overflow was the actual cause, not the sticky
   declaration itself. The fix below is a targeted, ID-scoped override
   (`#leads-workspace.leads-card{overflow:visible}`, mobile-only) rather
   than touching the shared `.leads-card{overflow:hidden}` rule itself:
   Quotations/Invoices/Team all reuse that same class for their own cards
   and were never asked for sticky behavior, so their rounded-corner
   clipping stays exactly as before. Verified this is visually safe for
   Leads specifically: every element that touches the card's own top/bottom
   rounded edges (the header, the empty state) has no background of its
   own outside mobile — and once sticky is active, the header's own
   `background:var(--surface)` below is the IDENTICAL colour to the card's
   own `background:var(--surface)` (see `.metric-card,.card` far above), so
   there is no contrasting square corner to reveal now that the rounded
   clip is gone — only the table header row (`th{background:var(--surface-
   soft)}`) has a different fill, and it never touches a rounded corner
   (the toolbar and header always sit above it). No visual difference was
   observed in the before/after screenshots at any of the 6 mobile widths.

   top:0, not a positive offset, because — verified directly, not assumed —
   `.mobile-header`/`.mobile-nav` (the "existing mobile header/navigation
   that occupies space at the top" the brief warns about) are NOT
   themselves position:sticky/fixed anywhere in this file (grep confirms
   neither rule sets a `position` at all); they are ordinary in-flow content
   that sits ABOVE `#leads-workspace` in the DOM (see index.html/js/ui.js's
   renderApp: mobile-header and mobile-nav render once, before
   `.main-content`, and #leads-workspace is deep inside `.main-content`,
   after the dashboard metrics and the full analytics grid). By the time a
   real scroll — or the existing `data-nav-target` click handler's
   `scrollIntoView({block:'start'})` — brings `#leads-workspace`'s own top
   anywhere near the viewport's top edge, the combined ~118px of mobile-
   header + mobile-nav has already scrolled far off-screen above it (their
   own height is trivial next to the dashboard metrics + full analytics
   grid that precedes Leads on this single continuously-scrolling page).
   Confirmed empirically with a real Chromium scroll-and-measure pass at
   320/375/390/430/768/820px, AFTER the overflow fix above: the sticky
   header actually pins at the viewport's top edge, never overlaps
   `.mobile-header`/`.mobile-nav`, and the table rows that scroll underneath
   it are the intended effect of a sticky header (see the Phase 12 report's
   sticky-behavior table for the exact before/after pixel values). z-index:5
   is comfortably below the invoice/quotation editor overlay (25) and the
   modal layer (30), so it can never wrongly sit above either. Scoped to
   899px (not 719px) to match this app's OWN existing mobile-shell
   breakpoint exactly — the same width at which .sidebar hides and
   .mobile-header/.mobile-nav appear (see the existing 899px block above) —
   so "desktop and tablet should NOT receive this" is satisfied by reusing
   the app's own already-established mobile/desktop boundary rather than
   inventing a new one; 768px/820px do get it (they already render the
   mobile shell at this breakpoint), 900px+ do not (they render the desktop
   sidebar shell and were never asked for this). An opaque `background`
   (not a translucent one) plus a `border-bottom` — the same divider
   treatment `.toolbar` already uses elsewhere in this exact card — keeps
   scrolling table content from visibly showing through underneath it. ---- */
@media(max-width:899px){
  #leads-workspace.leads-card{overflow:visible}
  #leads-workspace .leads-card-header{
    position:sticky;top:0;z-index:5;
    background:var(--surface);
    border-bottom:1px solid var(--line);
    border-radius:var(--radius) var(--radius) 0 0;
  }
  [data-theme=dark] #leads-workspace .leads-card-header{background:var(--surface)}
}

/* ==========================================================================
   PHASE 12.1 — UI CORRECTION PASS (manual-test brief, Aug 2026).
   Three corrections to the Phase 12 pass above, found by real-browser
   visual inspection after that phase shipped. Additive, per this project's
   established phase-additive convention — the ONE in-place edit is
   documented and searchable at "PHASE 12.1 CORRECTION" a little further up
   in the Phase 12 Requirement-3 comment block (the `.table-wrap
   td.actions{display:flex}` rule was replaced with a sibling-margin
   approach; see that comment for the full root-cause writeup). Everything
   else needed is additive, below.

   ---- Issue 2: Quotations/Invoices metric cards touching the outer edges.
   `.quotation-summary-metrics`/`.invoice-summary-metrics` are `.metrics`
   grids nested DIRECTLY inside `#quotations-workspace`/`#invoices-workspace`
   (both `.card.leads-card`) — unlike `.leads-card-header` and `.toolbar`,
   which both already carry their own `22px` (`16px` at ≤719px) left/right
   padding, the `.metrics` grid itself has never had any horizontal padding
   at any width (confirmed: `.metrics{gap:...;margin:...}` — margin is
   vertical-only, `0` left/right). So the metric cards' outer edges sat
   flush against the card's own edges while the gap BETWEEN cards was
   already correct (`--space-card-gap`, unchanged from Phase 12 — see that
   phase's own banner comment: 10px at ≤719px, 12px at 720-1199px, 16px at
   1440px, confirmed still correct here). Fixed at the grid-container level,
   exactly as the brief's own example prescribes: `padding-left`/
   `padding-right` on the two card-nested `.metrics` variants specifically
   (NOT on the bare `.metrics` class, which the Dashboard's own top-level
   metrics row also uses — that row lives directly in `.main-content`,
   which already supplies its own outer padding, and must NOT get a second,
   doubled-up inset). The exact same `22px`/`16px` values `.leads-card-
   header`/`.toolbar` already use for their own horizontal inset in this
   same card are reused here (not a new invented number, and no dedicated
   `--space-*` token exists for this literal in this file — `22px`/`16px`
   are the existing convention for "this card's own horizontal inset",
   reused verbatim) so all three — header, toolbar, and now the metrics
   grid — line up on the same left/right edge. Measured via Chromium
   getBoundingClientRect before/after: left inset went from 0px to 22px
   (768-1440px) / 16px (320-430px); gap between cards unchanged; card
   count/content/calculations untouched. ---- */
.quotation-summary-metrics,.invoice-summary-metrics{padding:0 22px}
@media(max-width:719px){
  .quotation-summary-metrics,.invoice-summary-metrics{padding:0 16px}
}

/* ---- Issue 3: mobile Leads sticky header verification. The brief asked
   to inspect body/main-content/dashboard-wrapper/mobile-nav/Leads-section/
   overflow/height to find what's actually blocking sticky. That inspection
   (full ancestor-chain dump via getComputedStyle, from `.leads-card-header`
   up through `.leads-card` → `.main-content` → `.workspace` → `.app-shell`
   → `#app` → `body` → `html`) found NO additional blocking ancestor beyond
   the one Phase 12 already found and fixed (`.leads-card{overflow:hidden}`,
   already overridden to `visible` above). Every ancestor in the chain
   computes `overflow:visible` except `body` itself, which carries this
   file's pre-existing `overflow-x:hidden` (see the `body{...}` rule near
   the top of this file) — CSS's overflow-pairing rule means `overflow-y`
   on `body` computes to `auto` as a result, but per the CSS Overflow spec's
   standard "propagation" behaviour (a `<body>` overflow value propagates to
   the viewport when `<html>` doesn't set its own, which is the case here),
   this does NOT make `<body>` itself a scrolling ancestor box that could
   trap `position:sticky` — the viewport remains the actual scrolling
   context, exactly as if `body` had no overflow rule at all.

   Re-verified with real Chromium/Playwright at all 4 required widths
   (320/375/390/430px), using the ACTUAL user flow (clicking the mobile
   nav's "Leads" link, then real mouse-wheel scroll events in 150px steps —
   not a single scroll jump) rather than a synthetic scroll-position
   assignment: the header's `getBoundingClientRect().top` clamps to `0` and
   STAYS at `0` through 12 consecutive wheel-scroll steps at every one of
   the 4 widths, while the table rows underneath keep scrolling normally —
   i.e. the sticky mechanism itself is confirmed working correctly with
   the current CSS, unchanged from Phase 12, no further rule needed here.

   If sticky still isn't visible in your own testing: this phase (like
   Phase 12) made NO git commit/push/deploy per the brief's explicit "NO
   GIT OPERATIONS" instruction, so crm.biosanjeevani.com's LIVE site still
   only has the pre-Phase-12 code — only the local project files (now
   updated to `?v=20260826j`, see the version bump below) have this fix.
   If you're testing against the live site rather than these local files,
   that fully explains "claims sticky but isn't" without any code defect —
   worth double-checking which one you're viewing before assuming another
   code-level cause. ---- */

/* =========================================================================
   Phase 14 — Finance-only additions. Everything above this line is an
   UNMODIFIED copy of the CRM's styles.css (verified via diff against
   ../styles.css before this block was appended) — every token, component,
   and print rule Finance reuses (cards, buttons, tables, badges, the
   invoice/quotation editor shell, .print-doc-*, forms, modals, toasts,
   sidebar/nav, auth page) is untouched, per condition 15 ("reuse the
   existing CRM visual language/design tokens, but Finance gets its own
   stylesheet"). Only genuinely NEW Finance-only surfaces are added below:
   the dashboard's hand-rolled SVG charts, and the MFA enrollment/challenge
   screens the CRM has no equivalent of.
   ========================================================================= */

/* ---- Finance dashboard charts ---- */
.finance-dashboard-charts{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:var(--space-card-gap,18px)}
.finance-chart-card{display:flex;flex-direction:column;gap:12px;min-width:0}
.finance-chart-card h3{margin:0;font-family:var(--font-display);font-size:.95rem;letter-spacing:.01em}
.finance-chart-card-wide{grid-column:1/-1}
.finance-chart-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:32px 12px;text-align:center;color:var(--muted)}
.finance-chart-empty .empty-icon{font-size:1.6rem;opacity:.6}
.finance-chart-empty p{margin:0;font-size:.85rem;max-width:32ch}

.finance-donut-wrap{display:flex;align-items:center;gap:20px;flex-wrap:wrap}
.finance-donut{width:150px;height:150px;flex:0 0 auto}
.finance-donut-value{font-size:15px;font-weight:700;fill:var(--ink)}
.finance-donut-caption{font-size:9px;fill:var(--muted);text-transform:uppercase;letter-spacing:.06em}
.finance-legend{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px;font-size:.82rem}
.finance-legend li{display:flex;align-items:center;gap:8px;color:var(--muted)}
.finance-legend li strong{color:var(--ink);margin-left:auto;font-weight:600}
.finance-legend-swatch{width:10px;height:10px;border-radius:3px;flex:0 0 auto}

.finance-bars{display:flex;flex-direction:column;gap:12px}
.finance-bar-row{display:grid;grid-template-columns:110px 1fr 34px;align-items:center;gap:10px;font-size:.82rem}
.finance-bar-label{color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.finance-bar-track{height:9px;border-radius:999px;background:var(--surface-soft);overflow:hidden}
.finance-bar-fill{display:block;height:100%;border-radius:999px;transition:width var(--ease)}
.finance-bar-value{text-align:right;font-weight:600}

.finance-line-chart{width:100%;height:auto;overflow:visible}
.finance-line-path{fill:none;stroke:var(--green);stroke-width:2.5;stroke-linejoin:round;stroke-linecap:round}
.finance-line-dot{fill:var(--green);stroke:var(--surface);stroke-width:1.5}
.finance-line-axis-label{font-size:9px;fill:var(--muted)}

/* ---- MFA enrollment / challenge screens (reuses .auth-page/.auth-panel) ---- */
.mfa-enroll-qr{display:flex;justify-content:center;margin:6px 0}
.mfa-enroll-qr img{width:180px;height:180px;border-radius:var(--radius-sm);background:#fff;padding:10px;border:1px solid var(--line)}
.mfa-enroll-secret{font-size:.82rem;color:var(--muted);text-align:center;word-break:break-all}
.mfa-enroll-secret code{background:var(--surface-soft);padding:2px 6px;border-radius:4px;font-size:.8rem}

/* MFA challenge screen ("STEP 2 OF 2 - Enter your authenticator code") —
   bug report: the primary "Verify & continue" button (last child of
   #mfa-challenge-form.form-stack) and the secondary "Cancel & sign out"
   button (a plain sibling of that form inside .auth-panel, not part of the
   form's own .form-stack grid/gap) had no margin between them at all,
   since .auth-panel itself is a plain block container with no gap of its
   own. Fix is a single scoped margin-top on the cancel button, targeted
   only at the one that directly follows #mfa-challenge-form — the
   structurally identical enroll screen (#mfa-enroll-form) is intentionally
   left untouched, since only the challenge screen was reported. Nothing
   about either button's size/color/type/text/shadow changes, and this is a
   fixed px value so it's already correct at every viewport width,
   including mobile (~390px). */
#mfa-challenge-form + [data-mfa-cancel]{margin-top:12px}

/* =========================================================================
   Finance dashboard redesign (this phase) — additive only. Every selector
   below is either a NEW class (finance-kpi, finance-overview-grid,
   finance-recent, finance-monthly, finance-pipeline, quotation-pipeline)
   or a more specific override of an existing Finance-only dashboard rule
   (.finance-dashboard-charts's own grid-template-columns, redeclared below —
   this is scoped to that one Finance-only class, not the shared/CRM-derived
   .analytics-dashboard base rule far above this section, which is untouched).
   Nothing here touches CRM: this whole file is finance/styles.css, an
   entirely separate stylesheet from the CRM's own root styles.css.
   ========================================================================= */

/* chartsRoot is now a simple single-column stack — each of its three direct
   children (the overview/pipeline pair, the monthly card, the recent pair)
   manages its own internal layout below, rather than all cards competing
   for slots in one auto-fit grid (which is what let a lone wide monthly
   card stretch to fill unrelated leftover space). */
.finance-dashboard-charts{display:grid;grid-template-columns:1fr;gap:var(--space-section)}

/* ---- KPI row (§1) — 5 cards on desktop, wrapping down at narrower widths
   so nothing overflows and every card stays comfortably readable. ---- */
.finance-summary-metrics{grid-template-columns:repeat(5,minmax(0,1fr))}
.finance-kpi-card .finance-kpi-context{display:block;margin-top:4px;color:var(--muted);font-size:.72rem;font-weight:650;letter-spacing:.01em}
@media(max-width:1199px){.finance-summary-metrics{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media(max-width:719px){.finance-summary-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* ---- Shared card heading (title + small note, e.g. "6 invoices") reused
   by Invoice Overview / Quotation Pipeline / Monthly Invoiced Amount /
   Recent Invoices / Recent Quotations, so every dashboard card's header
   reads consistently. ---- */
.finance-card-heading{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
.finance-card-heading h3{margin:0}

/* ---- Row: Invoice Overview | Quotation Pipeline (§2/§3) ---- */
.finance-overview-grid{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);gap:var(--space-card-gap)}
@media(max-width:899px){.finance-overview-grid{grid-template-columns:1fr}}

/* Invoice Overview's detailed legend — swatch + label/meta (count · %) on
   the left, amount on the right, matching the app's existing "label ...
   value" list rhythm (e.g. .breakdown-label above) rather than a new shape. */
.finance-legend-detailed li{align-items:flex-start;gap:10px}
.finance-legend-detailed .finance-legend-swatch{margin-top:3px}
.finance-legend-detailed .finance-legend-text{display:flex;flex-direction:column;gap:2px;min-width:0}
.finance-legend-detailed .finance-legend-label{color:var(--ink);font-weight:700}
.finance-legend-detailed .finance-legend-meta{color:var(--muted);font-size:.74rem}
.finance-legend-detailed li strong{white-space:nowrap}

/* Quotation Pipeline — reuses .pipeline-row/.pipeline-status/.progress-track/
   .progress-fill verbatim (already defined above, already proven on the
   CRM's own dashboard for an identical "status label + bar + count" shape);
   only the count cell gets a small percentage alongside it, and each status
   gets its own fill color mirroring the exact same tokens already used by
   the quotation-status-* badges elsewhere in this file, so the pipeline bar
   colors and the status badges shown in Recent Quotations/the Quotations
   workspace always agree. */
.finance-pipeline-row strong{display:flex;align-items:baseline;gap:5px}
.finance-pipeline-row strong small{color:var(--muted);font-weight:650;font-size:.7rem}
.progress-fill.quotation-pipeline-draft{background:var(--muted)}
.progress-fill.quotation-pipeline-sent{background:var(--info)}
.progress-fill.quotation-pipeline-viewed{background:var(--purple)}
.progress-fill.quotation-pipeline-accepted{background:var(--green)}
.progress-fill.quotation-pipeline-rejected{background:var(--danger)}
.progress-fill.quotation-pipeline-expired{background:var(--warning)}
.progress-fill.quotation-pipeline-converted{background:var(--green)}
.finance-pipeline-total{margin:2px 0 0;padding-top:12px;border-top:1px solid var(--line);color:var(--muted);font-size:.8rem}
.finance-pipeline-total strong{color:var(--ink)}

/* ---- Monthly Invoiced Amount (§4) — sizing now follows the data instead
   of a fixed giant chart. .finance-monthly-inner lays the summary number
   and the chart side by side so a compact 1-2/2-3-month state fills its
   full-width row usefully (big number + small chart + breathing room)
   rather than as a small chart adrift in a mostly-empty card. ---- */
.finance-monthly-inner{display:flex;align-items:center;gap:32px;flex-wrap:wrap}
.finance-monthly-inner-compact{gap:40px}
.finance-monthly-summary{display:grid;gap:4px;flex:0 0 auto}
.finance-monthly-summary strong{font-family:var(--font-display);font-size:1.9rem;letter-spacing:-.03em}
.finance-monthly-summary span{color:var(--muted);font-size:.82rem}
.finance-monthly-hint{margin:2px 0 0;color:var(--muted);font-size:.78rem}
.finance-monthly-card .finance-line-chart{flex:1 1 320px;max-width:640px}

/* Single-month state: a short, deliberately non-quantitative indicator bar
   (there is nothing to compare a single point against) rather than an
   empty chart shape. */
.finance-monthly-single-indicator{width:180px;height:10px;border-radius:999px;background:var(--surface-soft);overflow:hidden;flex:0 0 auto}
.finance-monthly-single-indicator span{display:block;width:100%;height:100%;background:var(--green)}

/* 2-3-month compact bar chart — fixed-width columns (not stretched to fill
   the row), so a couple of data points read as a small, intentional widget. */
.finance-monthly-bars{display:flex;gap:22px;align-items:flex-end;height:104px;flex:0 0 auto}
.finance-monthly-bar-col{display:flex;flex-direction:column;align-items:center;gap:6px;width:60px}
.finance-monthly-bar-track{width:34px;height:72px;display:flex;align-items:flex-end;background:var(--surface-soft);border-radius:6px;overflow:hidden}
.finance-monthly-bar-fill{display:block;width:100%;background:var(--green);border-radius:6px 6px 0 0;transition:height var(--ease)}
.finance-monthly-bar-value{font-size:.7rem;font-weight:700;color:var(--ink);white-space:nowrap}
.finance-monthly-bar-label{font-size:.66rem;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}

/* ---- Row: Recent Invoices | Recent Quotations (§5/§6) ---- */
.finance-recent-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--space-card-gap)}
@media(max-width:899px){.finance-recent-grid{grid-template-columns:1fr}}
.finance-recent-card{padding:0;overflow:hidden}
.finance-recent-card .finance-card-heading{padding:21px 21px 0}
/* FIX 5 (dashboard Recent Invoices/Quotations table overflow at 1280/1024):
   every ancestor here was already correctly sized (.finance-recent-grid's
   minmax(0,1fr) tracks, .finance-recent-card's min-width:0 + overflow:hidden,
   this wrap's own inherited .table-wrap{overflow-x:auto}, .finance-recent-
   table{min-width:0}) — confirmed by measuring each one directly, none of
   them render wider than their own grid column. The actual leak is a known
   browser quirk specific to a <table> nested inside an overflow:auto
   container inside CSS Grid: the table's own intrinsic content width can
   still inflate document.documentElement's scrollable-overflow region (and
   therefore let the WHOLE PAGE scroll horizontally) even though every
   element's own getBoundingClientRect/computed width is individually
   correct and the table is already fully reachable via this wrap's own
   internal scrollbar. contain:paint is the standard, minimal fix: it makes
   this element an explicit paint-containment boundary, so nothing inside it
   — however wide — can affect layout/scroll outside it. Verified: with this
   one property, document.documentElement.scrollWidth === window.innerWidth
   and window.scrollX no longer moves at 1440/1280/1024/768/390, while the
   wrap's own internal horizontal scrollbar (all columns still reachable,
   nothing removed/hidden) is completely unaffected. */
.finance-recent-table-wrap{margin-top:14px;contain:paint}
.finance-recent-table{min-width:0}
.finance-recent-table th,.finance-recent-table td{padding:12px 21px;font-size:.81rem}
.finance-recent-table td.actions{white-space:nowrap}
@media(max-width:719px){.finance-recent-card .finance-card-heading{padding:17px 16px 0}.finance-recent-table th,.finance-recent-table td{padding:10px 16px}}

@media(max-width:719px){.finance-monthly-inner{gap:20px}.finance-monthly-inner-compact{gap:24px}.finance-monthly-summary strong{font-size:1.55rem}.finance-monthly-bars{gap:16px}.finance-monthly-bar-col{width:50px}}

/* Export Feature (task #187) — "Export PDF"/"Export Excel" button styling.
   Reuses .btn/.btn-secondary exactly as-is for color/border/hover/disabled
   states; only matches sizing to the existing .clear-filters button each
   sits beside in the Quotations/Invoices toolbars' .filter-row (same
   min-height/padding/font-size). .export-buttons groups the PDF+Excel pair
   so they stay visually paired (small gap, no wrap between the two) while
   still being free to wrap onto their own line below the status filter and
   Clear filters button at narrow widths, exactly like every other
   .filter-row child already does via the existing @media(max-width:899px)
   grid rule above — no new breakpoint logic needed. */
.export-button{min-height:34px;padding:6px 10px;font-size:.77rem}
.export-buttons{display:inline-flex;gap:8px}

/* =====================================================================
   PHASE 1 — VISUAL REDESIGN: DESIGN TOKENS, GLOBAL SHELL, THEME SYSTEM
   =====================================================================
   Appended at the end of the file on purpose. Every block below either
   introduces a new `--bs-*` token / `.bs-*` class (no collision possible
   with anything above), or re-declares an EXISTING token at the same
   specificity as its original declaration (`:root` at the top of this file,
   `[data-theme=dark]` further down) so the later declaration simply wins.
   Nothing above this line was edited, so the pre-existing page bodies
   (dashboard, quotations, invoices, editors, print stylesheet) keep every
   rule they had and just inherit the refreshed palette.

   ACCENT COLOUR: --green is deliberately NOT changed. #176b42 (light) and
   #35d68c (dark) are the accent values this codebase already shipped with,
   and the brief is explicit that the existing Biosanjeevani green is the
   accent. Everything green below is either var(--green) itself or a
   color-mix derived from it — no new green is invented anywhere.

   DARK IS THE PRIMARY DIRECTION: the dark palette is tuned first (deep
   charcoal-forest ground, recessed sidebar, elevated cards, hairline green
   borders). The light palette is its own design rather than an inversion:
   it uses a light neutral ground with true-white cards, cool grey-green
   hairlines and a deeper, higher-contrast green — the opposite relationship
   between "ground" and "card" that the dark theme uses.
   ===================================================================== */

/* ---- 1. Refreshed neutral scale + shared shell tokens (LIGHT) ------- */
:root {
  /* Light ground is a soft neutral; cards sit ABOVE it in pure white. */
  --canvas: #f3f6f4;
  --surface: #ffffff;
  --surface-soft: #f7f9f8;
  --surface-elevated: #ffffff;
  --line: #e2e8e4;
  --ink: #16211d;
  --muted: #5f7069;

  /* Restrained corners — the brief rules out pill-shaped / bubbly. */
  --radius: 12px;
  --radius-lg: 14px;
  --radius-sm: 8px;

  --shadow: 0 1px 2px rgba(18, 40, 32, .05), 0 8px 24px rgba(18, 40, 32, .06);
  --shadow-elevated: 0 2px 4px rgba(18, 40, 32, .06), 0 18px 40px rgba(18, 40, 32, .10);

  /* Shell geometry (shared by both themes). */
  --bs-sidebar-w: 268px;
  --bs-rail-w: 74px;
  --bs-topbar-h: 62px;
  --bs-nav-radius: 9px;

  /* Shell colour roles. Named separately from --surface so the sidebar and
     topbar can sit at a different elevation than a content card does —
     which is exactly what makes light mode read as its own design rather
     than an inverted dark mode. */
  --bs-sidebar-bg: #ffffff;
  --bs-sidebar-border: #e2e8e4;
  --bs-topbar-bg: #ffffff;
  --bs-nav-ink: #55665f;
  --bs-nav-ink-hover: var(--ink);
  --bs-nav-hover-bg: #eef2f0;
  --bs-nav-active-bg: color-mix(in srgb, var(--green) 10%, transparent);
  --bs-nav-active-ink: var(--green);
  --bs-scrim: rgba(22, 33, 29, .44);
  --bs-drawer-shadow: 0 24px 64px rgba(18, 40, 32, .24);
  --bs-popover-shadow: 0 18px 44px rgba(18, 40, 32, .16);
  --bs-track: #eef2f0;
}

/* ---- 2. Dark theme — the primary direction ------------------------- */
[data-theme=dark] {
  /* Deep charcoal with a forest cast. The sidebar is RECESSED (darker than
     the content ground) and cards are ELEVATED above it — the inverse of
     the light theme's relationship, on purpose. */
  --canvas: #0b1310;
  --surface: #111b17;
  --surface-soft: #0e1714;
  --surface-elevated: #17221d;
  --line: #1f2c27;
  --ink: #e8f1ec;
  --muted: #8b9d95;

  --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 12px 32px rgba(0, 0, 0, .34);
  --shadow-elevated: 0 2px 6px rgba(0, 0, 0, .45), 0 24px 56px rgba(0, 0, 0, .46);

  --bs-sidebar-bg: #070e0c;
  --bs-sidebar-border: #1a2621;
  --bs-topbar-bg: #0b1310;
  --bs-nav-ink: #90a29a;
  --bs-nav-ink-hover: var(--ink);
  --bs-nav-hover-bg: #15201b;
  --bs-nav-active-bg: color-mix(in srgb, var(--green) 13%, transparent);
  --bs-nav-active-ink: var(--green);
  --bs-scrim: rgba(3, 8, 6, .62);
  --bs-drawer-shadow: 0 24px 64px rgba(0, 0, 0, .55);
  --bs-popover-shadow: 0 20px 48px rgba(0, 0, 0, .55);
  --bs-track: #16211c;
}

/* ---- 3. Icons ------------------------------------------------------ */
/* Every icon comes from js/icons.js: 24x24, stroke="currentColor". Sizing
   lives here so a single class controls the whole set and no icon ever
   needs a per-theme variant. */
.bs-icon { display: block; flex: 0 0 auto; width: 18px; height: 18px; }
.bs-icon-lg { width: 22px; height: 22px; }

/* ---- 4. Shell frame ------------------------------------------------ */
/* <html data-sidebar> is set by js/app.js and is always exactly one of
   expanded | collapsed | drawer. See the state-machine comment there. */
.bs-shell {
  display: grid;
  grid-template-columns: var(--bs-sidebar-w) minmax(0, 1fr);
  min-height: 100vh;
  background: var(--canvas);
}
:root[data-sidebar=collapsed] .bs-shell { grid-template-columns: var(--bs-rail-w) minmax(0, 1fr); }
:root[data-sidebar=drawer] .bs-shell { grid-template-columns: minmax(0, 1fr); }

.bs-frame { display: flex; flex-direction: column; min-width: 0; }

.bs-main { flex: 1; min-width: 0; }
/* Tighter than the old shell's padding — the brief calls out excessive
   empty space. Still roomy enough that dense tables do not touch the edge. */
.bs-main .main-content {
  width: min(100%, 1480px);
  margin-inline: auto;
  padding: 26px clamp(18px, 2.4vw, 34px) calc(52px + env(safe-area-inset-bottom, 0px));
}

.bs-view { display: block; }
/* .bs-view can receive display:block above, which would beat the [hidden]
   UA rule — this keeps the hidden attribute authoritative for view switching. */
.bs-view[hidden] { display: none !important; }

/* ---- 5. Sidebar ---------------------------------------------------- */
.bs-sidebar {
  position: sticky;
  top: 0;
  align-self: start;
  z-index: 70;
  display: flex;
  flex-direction: column;
  gap: 14px;
  height: 100vh;
  padding: 16px 12px 14px;
  background: var(--bs-sidebar-bg);
  border-right: 1px solid var(--bs-sidebar-border);
}

.bs-sidebar-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 4px 14px;
  border-bottom: 1px solid var(--bs-sidebar-border);
}
.bs-brand { display: flex; align-items: center; gap: 10px; min-width: 0; }
.bs-brand-logo {
  flex: 0 0 auto;
  width: 34px; height: 34px;
  object-fit: contain;
  border-radius: 8px;
}
[data-theme=dark] .bs-brand-logo { mix-blend-mode: screen; }
.bs-brand-text { display: grid; min-width: 0; line-height: 1.15; }
.bs-brand-text strong {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--font-display);
  font-size: .95rem; font-weight: 800; letter-spacing: -.02em;
  color: var(--ink);
}
.bs-brand-text small {
  margin-top: 2px;
  color: var(--green);
  font-size: .62rem; font-weight: 800; letter-spacing: .14em; text-transform: uppercase;
}

.bs-nav { display: grid; align-content: start; gap: 2px; min-height: 0; overflow-y: auto; }
.bs-nav-secondary { padding-top: 10px; border-top: 1px solid var(--bs-sidebar-border); }

.bs-nav-item {
  position: relative;
  display: flex; align-items: center; gap: 11px;
  width: 100%;
  min-height: 38px;
  padding: 8px 10px;
  border: 0; border-radius: var(--bs-nav-radius);
  background: transparent;
  color: var(--bs-nav-ink);
  font-size: .845rem; font-weight: 650; letter-spacing: -.005em;
  text-align: left;
  transition: background var(--ease), color var(--ease);
}
.bs-nav-item:hover { background: var(--bs-nav-hover-bg); color: var(--bs-nav-ink-hover); }
.bs-nav-item.is-active {
  background: var(--bs-nav-active-bg);
  color: var(--bs-nav-active-ink);
  font-weight: 750;
}
/* A 3px inset marker rather than a full pill — restrained, and it survives
   the icon-only rail unchanged. */
.bs-nav-item.is-active::before {
  content: '';
  position: absolute; left: 0; top: 50%;
  width: 3px; height: 18px;
  margin-top: -9px;
  border-radius: 0 3px 3px 0;
  background: var(--green);
}
.bs-nav-icon { display: flex; flex: 0 0 auto; }
.bs-nav-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.bs-sidebar-foot { display: grid; gap: 8px; margin-top: auto; }

.bs-account { display: flex; align-items: center; gap: 9px; min-width: 0; padding: 6px 4px; }
.bs-avatar {
  display: grid; place-items: center;
  flex: 0 0 auto;
  width: 30px; height: 30px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--green) 14%, transparent);
  color: var(--green);
  font-size: .78rem; font-weight: 800;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--green) 22%, transparent);
}
.bs-account-text { display: grid; min-width: 0; line-height: 1.2; }
.bs-account-text strong,
.bs-account-text small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bs-account-text strong { font-size: .755rem; font-weight: 700; color: var(--ink); }
.bs-account-text small { margin-top: 2px; color: var(--muted); font-size: .68rem; }

.bs-logout { color: var(--muted); font-weight: 650; }
.bs-logout:hover,
.bs-logout:focus-visible { background: var(--danger-soft); color: var(--danger); }

/* ---- 6. Icon buttons (topbar + sidebar chrome) --------------------- */
.bs-icon-button {
  display: grid; place-items: center;
  flex: 0 0 auto;
  width: 34px; height: 34px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  transition: background var(--ease), color var(--ease), border-color var(--ease);
}
.bs-icon-button:hover { background: var(--bs-nav-hover-bg); color: var(--ink); }
/* display:grid above would beat the UA [hidden] rule — js/app.js hides the
   collapse chevron with the hidden attribute in the forced-rail range. */
.bs-icon-button[hidden] { display: none !important; }
.bs-collapse-button { margin-left: auto; }

/* ---- 7. Topbar ----------------------------------------------------- */
.bs-topbar {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: 10px;
  min-height: var(--bs-topbar-h);
  padding: 9px clamp(14px, 2.2vw, 28px);
  background: var(--bs-topbar-bg);
  border-bottom: 1px solid var(--line);
}
.bs-topbar-title {
  min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: .95rem; font-weight: 800; letter-spacing: -.02em;
}
.bs-topbar-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }

.bs-search { position: relative; flex: 1 1 auto; min-width: 0; max-width: 340px; margin-left: 6px; }
.bs-search-icon {
  position: absolute; top: 50%; left: 10px;
  display: flex; margin-top: -9px;
  color: var(--muted);
  pointer-events: none;
}
.bs-search input {
  width: 100%;
  min-height: 36px;
  padding: 7px 12px 7px 34px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-soft);
  color: var(--ink);
  font-size: .82rem;
}
[data-theme=dark] .bs-search input { background: #0e1714; }
.bs-search input::placeholder { color: var(--muted); }
.bs-search input:disabled { opacity: .55; cursor: not-allowed; }

/* Notifications: a real slot in the layout, but explicitly NOT a fake
   notification feed — no badge, no counts, no invented items. The popover
   states plainly that nothing is connected yet. */
.bs-notifications { position: relative; }
.bs-popover {
  position: absolute; top: calc(100% + 9px); right: 0; z-index: 50;
  width: min(280px, calc(100vw - 32px));
  padding: 14px 15px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-elevated);
  box-shadow: var(--bs-popover-shadow);
}
.bs-popover[hidden] { display: none !important; }
.bs-popover h2 {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-size: .88rem; font-weight: 800; letter-spacing: -.02em;
  color: var(--ink);
}
.bs-popover p { margin: 0; color: var(--muted); font-size: .78rem; line-height: 1.5; }

/* Three-way Light / Dark / System control. */
.bs-theme-switch {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-soft);
}
[data-theme=dark] .bs-theme-switch { background: #0e1714; }
.bs-theme-option {
  display: grid; place-items: center;
  width: 30px; height: 28px;
  padding: 0;
  border: 0; border-radius: 6px;
  background: transparent;
  color: var(--muted);
  transition: background var(--ease), color var(--ease);
}
.bs-theme-option:hover { color: var(--ink); }
.bs-theme-option[aria-checked=true] {
  background: var(--surface);
  color: var(--green);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .12);
}
[data-theme=dark] .bs-theme-option[aria-checked=true] { background: var(--surface-elevated); }

.bs-topbar-user { display: flex; align-items: center; gap: 9px; min-width: 0; max-width: 220px; padding-left: 4px; }
.bs-topbar-logout { color: var(--muted); }
.bs-topbar-logout:hover { background: var(--danger-soft); color: var(--danger); }

/* The legacy single cycling theme button (auth screens) now holds an SVG. */
.theme-toggle-icon { display: flex; align-items: center; }

/* ---- 8. Page heads + placeholder views ----------------------------- */
.bs-page-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 18px;
  margin-bottom: 20px;
}
.bs-page-head h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.2vw, 1.95rem);
  font-weight: 800; letter-spacing: -.035em;
  color: var(--ink);
}
.bs-page-head .muted { max-width: 62ch; }

.bs-placeholder {
  display: grid; justify-items: start; gap: 8px;
  max-width: min(100%, 720px);
  padding: 34px clamp(20px, 3vw, 34px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}
.bs-placeholder-icon {
  display: grid; place-items: center;
  width: 38px; height: 38px;
  margin-bottom: 4px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--green) 11%, transparent);
  color: var(--green);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--green) 20%, transparent);
}
.bs-placeholder-icon .bs-icon { width: 20px; height: 20px; }
.bs-placeholder h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.05rem; font-weight: 800; letter-spacing: -.02em;
  color: var(--ink);
}
.bs-placeholder p { margin: 0; max-width: 64ch; color: var(--muted); font-size: .86rem; line-height: 1.6; }
.bs-placeholder-hint {
  margin-top: 6px !important;
  padding: 10px 12px;
  border-left: 2px solid color-mix(in srgb, var(--green) 45%, transparent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--surface-soft);
  font-size: .82rem !important;
}

/* ---- 9. Collapsed icon rail ---------------------------------------- */
/* Labels are clipped rather than display:none so they stay in the
   accessibility tree — the buttons keep their accessible names, and
   js/app.js adds a title attribute for sighted hover. */
:root[data-sidebar=collapsed] .bs-sidebar { padding-inline: 10px; }
:root[data-sidebar=collapsed] .bs-nav-label,
:root[data-sidebar=collapsed] .bs-brand-text,
:root[data-sidebar=collapsed] .bs-account-text {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
:root[data-sidebar=collapsed] .bs-nav-item,
:root[data-sidebar=collapsed] .bs-account { justify-content: center; padding-inline: 0; }
:root[data-sidebar=collapsed] .bs-sidebar-head { justify-content: center; }
:root[data-sidebar=collapsed] .bs-collapse-button { margin-left: 0; }
:root[data-sidebar=collapsed] .bs-brand { justify-content: center; }
/* In the rail the item is centred and full-width, so the inset marker has
   nowhere sensible to sit — the tinted background plus the green icon is
   already an unambiguous active state. */
:root[data-sidebar=collapsed] .bs-nav-item.is-active::before { display: none; }

/* ---- 10. Mobile / tablet drawer ------------------------------------ */
/* A true overlay: the content column never shrinks to make room for it. */
.bs-scrim { display: none; }
:root[data-sidebar=drawer] .bs-scrim {
  display: block;
  position: fixed; inset: 0; z-index: 60;
  background: var(--bs-scrim);
  animation: bs-scrim-in .18s ease;
}
.bs-scrim[hidden] { display: none !important; }
@keyframes bs-scrim-in { from { opacity: 0; } to { opacity: 1; } }

:root[data-sidebar=drawer] .bs-sidebar {
  position: fixed; top: 0; left: 0;
  width: min(284px, 84vw);
  height: 100vh; height: 100dvh;
  transform: translateX(-100%);
  visibility: hidden;
  transition: transform .24s ease, visibility .24s ease;
}
:root[data-sidebar=drawer][data-drawer=open] .bs-sidebar {
  transform: none;
  visibility: visible;
  box-shadow: var(--bs-drawer-shadow);
}

/* The collapse chevron is desktop-only; the hamburger is drawer-only; the
   drawer's own close button only exists in drawer mode. */
.bs-menu-button,
.bs-drawer-close { display: none; }
:root[data-sidebar=drawer] .bs-menu-button { display: grid; }
:root[data-sidebar=drawer] .bs-drawer-close { display: grid; margin-left: auto; }
:root[data-sidebar=drawer] .bs-collapse-button { display: none; }
:root[data-sidebar=collapsed] .bs-collapse-button,
:root[data-sidebar=expanded] .bs-collapse-button { display: grid; }

@media (prefers-reduced-motion: reduce) {
  :root[data-sidebar=drawer] .bs-sidebar { transition: none; }
  :root[data-sidebar=drawer] .bs-scrim { animation: none; }
}

/* ---- 11. Responsive trimming --------------------------------------- */
/* 1024-1199px is handled by js/app.js forcing the icon rail, so the only
   things left here are topbar density adjustments. */
@media (max-width: 1099px) {
  .bs-search { max-width: 260px; }
  .bs-topbar-user .bs-account-text { display: none; }
  .bs-topbar-user { max-width: none; }
}
@media (max-width: 899px) {
  .bs-main .main-content { padding: 22px clamp(16px, 3vw, 24px) calc(44px + env(safe-area-inset-bottom, 0px)); }
  .bs-page-head { align-items: flex-start; flex-direction: column; gap: 12px; margin-bottom: 16px; }
}
@media (max-width: 767px) {
  /* Below tablet the topbar keeps: hamburger, section name, bell, theme,
     logout. The search box moves out of the topbar rather than squeezing
     everything else — each workspace still has its own search field. */
  .bs-search { display: none; }
  .bs-topbar { gap: 8px; padding-inline: 14px; }
  .bs-topbar-actions { gap: 4px; }
  .bs-topbar-user { display: none; }
  .bs-main .main-content { padding: 18px 14px calc(40px + env(safe-area-inset-bottom, 0px)); }
}
@media (max-width: 419px) {
  .bs-theme-option { width: 27px; height: 26px; }
  .bs-icon-button { width: 32px; height: 32px; }
  .bs-topbar-title { font-size: .88rem; }
}

/* ---- 12. Safe-area padding (notched phones) ------------------------- */
/* The content column's bottom inset is folded into its own padding rules in
   sections 4 and 11 instead of being set here, so a narrow-width @media
   rule can never drop it. */
.bs-topbar { padding-top: max(9px, env(safe-area-inset-top, 0px)); }
.bs-sidebar { padding-top: max(16px, env(safe-area-inset-top, 0px)); }

/* ---- 13. Shell-wide polish on existing components ------------------- */
/* Tightens the pre-existing cards/tables to match the new radii and border
   treatment. Purely visual, and all token-driven — no per-component
   colour is hard-coded, so both themes follow automatically. */
.bs-main .metric-card,
.bs-main .analytics-card,
.bs-main .leads-card,
.bs-main .card { border-radius: var(--radius); }
.bs-main .metric-card { min-height: 112px; padding: 17px 18px; }
.bs-main .metric-card > div { min-width: 0; flex: 1 1 auto; }
.bs-main .metric-card strong { margin-top: 6px; font-size: 1.6rem; }
.bs-main th { letter-spacing: .07em; }

/* The KPI strip's existing 3-column rule was written for the OLD shell,
   which reserved more horizontal space than the new sidebar/rail does. With
   the rail in play at 1024-1199px the fourth card was being orphaned onto a
   second row, so the four-up strip is restored from 900px upwards. Below
   900px the pre-existing 2-up rules still apply, untouched. */
@media (min-width: 900px) {
  .bs-main .finance-summary-metrics { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* Below 900px the same strip was still on 3 columns (its own rule outranked
   the generic 2-column .metrics rule by source order), which orphaned the
   fourth card on tablet too. Two even columns instead. */
@media (max-width: 899px) {
  .bs-main .finance-summary-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* GUARDRAIL FIX — "no neon colors". The pre-existing .btn-primary carried a
   large coloured glow (box-shadow tinted with var(--green) mixed toward
   #4CAF50) plus a hover lift. Against the new deep charcoal-forest ground
   that glow reads as neon, which the brief rules out. Replaced with a flat
   shadow and no lift; the gradient fill itself is unchanged, so the button
   keeps its identity. */
.btn-primary {
  box-shadow: 0 1px 2px rgba(10, 30, 22, .18);
}
.btn-primary:hover:not(:disabled) {
  transform: none;
  filter: brightness(1.06);
  box-shadow: 0 2px 6px rgba(10, 30, 22, .22);
}

/* ==========================================================================
   PHASE 1 FIX 3 — dashboard-only card border definition.

   SCOPING (the whole point of this block): .metric-card, .card and
   .analytics-card are SHARED base classes — the Leads workspace, the invoice
   list summary strip and the quotation list summary strip all use them, and
   their borders must not change. .finance-kpi-card and .finance-chart-card
   are emitted ONLY by js/dashboard.js (renderFinanceDashboard's KPI tiles
   and chart/analytics cards respectively), so every rule below is keyed on
   those two classes and cannot reach any other screen. js/dashboard.js is
   untouched — this is a pure CSS addition, no markup/data/layout change.

   The shared base leaves these cards on border-color:color-mix(--line 80%,
   transparent), which against the redesigned card backgrounds reads as
   almost no edge at all. These give the dashboard's cards a defined edge
   using the brand green, mixed INTO --line (not into transparent) so the
   result stays a fully opaque hairline in the same family as the rest of
   the UI. Deliberately within the client's guardrails: border-color only —
   no glow/coloured box-shadow, no width change (still the inherited 1px),
   no gradient.

   The two :hover rules are not new behaviour: .metric-card:hover and
   .analytics-card:hover already raise the border toward green, and the
   dark-mode resting rules below match their specificity (0,2,0) and come
   later in source order, which would have silently cancelled that hover
   affordance on the dashboard. These restate it one step above each
   theme's resting tint so hover still reads as a change in both themes.
   ========================================================================== */
.finance-kpi-card,
.finance-chart-card { border-color: color-mix(in srgb, var(--green) 26%, var(--line)); }

[data-theme=dark] .finance-kpi-card,
[data-theme=dark] .finance-chart-card { border-color: color-mix(in srgb, var(--green) 32%, var(--line)); }

.finance-kpi-card:hover,
.finance-chart-card:hover { border-color: color-mix(in srgb, var(--green) 40%, var(--line)); }

[data-theme=dark] .finance-kpi-card:hover,
[data-theme=dark] .finance-chart-card:hover { border-color: color-mix(in srgb, var(--green) 46%, var(--line)); }

/* ==========================================================================
   PHASE 2 — FINANCE DASHBOARD ("Executive Command Center"). ADDITIVE ONLY.

   Everything below is presentation. No rule here changes a number, a filter
   or a formula — js/dashboard.js computes exactly what it computed before
   (see its own header comment). Scope discipline, same as Phase 1 FIX 3:
   every selector is either a brand-new .finance-* class emitted ONLY by
   js/dashboard.js / the dashboard page head in js/ui.js, or a more specific
   restatement of an existing Finance-only dashboard rule. Nothing here can
   reach the Quotations/Invoices workspaces, the editors, or the print
   stylesheet.

   The new Finance Insights card deliberately reuses the EXISTING
   .finance-chart-card class rather than introducing a third card type, so
   it inherits the Phase 1 FIX 3 green border treatment automatically and
   those rules stay the single source of truth for the dashboard's borders.
   The FIX 3 block above is untouched.

   Both themes are driven entirely by the existing tokens (--green, --info,
   --warning, --danger, --line, --surface*, --muted, --ink). No new colour
   is introduced anywhere, so light mode keeps its own Phase 1 palette
   rather than becoming an inverted dark mode.
   ========================================================================== */

/* ---- 1. Dashboard greeting header ---------------------------------- */
/* Extends .bs-page-head (Phase 1) rather than replacing it, so the h1 and
   .muted typography stay identical to every other page head in the app.
   Restrained on purpose: a hairline rule, a date chip and the signed-in
   email — no oversized greeting, no decoration. */
.finance-greeting {
  align-items: flex-end;
  gap: 20px;
  margin-bottom: 22px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--line);
}
.finance-greeting-main { min-width: 0; }
.finance-greeting-main .muted { margin-top: 7px; font-size: .88rem; }
.finance-greeting-meta {
  display: grid; gap: 7px; justify-items: end;
  flex: 0 0 auto; min-width: 0; text-align: right;
}
.finance-greeting-date {
  display: inline-flex; align-items: center; gap: 8px;
  margin: 0;
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font-size: .78rem; font-weight: 700;
  white-space: nowrap;
}
.finance-greeting-date > span { display: flex; color: var(--green); }
.finance-greeting-date .bs-icon { width: 15px; height: 15px; }
.finance-greeting-identity {
  margin: 0;
  max-width: 34ch;
  color: var(--muted);
  font-size: .76rem;
  overflow-wrap: anywhere;
}
.finance-greeting-identity strong { color: var(--ink); font-weight: 700; }
@media (max-width: 899px) {
  /* .bs-page-head already switches to flex-direction:column here; this block
     comes later in the file than that rule, so it must restate the start
     alignment or the flex-end above would win and push the whole greeting to
     the right edge. */
  .finance-greeting { align-items: flex-start; gap: 14px; padding-bottom: 15px; margin-bottom: 18px; }
  .finance-greeting-meta { justify-items: start; text-align: left; }
}

/* ---- 2. KPI row ------------------------------------------------------ */
/* Same five cards, same values. Density up, decoration down: an uppercase
   micro-label paired with a tinted icon chip on one line, then the figure,
   then the context line. The 2px top accent is not a new pattern — the
   Leads summary strip in this same stylesheet already tints .metric-card's
   ::before per card; this just applies it to Finance's own KPIs using the
   same status tokens the donut and pipeline already use. */
.finance-summary-metrics { margin: 0 0 26px; }
.bs-main .finance-kpi-card { min-height: 116px; padding: 15px 16px 16px; }
.finance-kpi-card .finance-kpi-body { display: flex; flex-direction: column; min-width: 0; width: 100%; }
.finance-kpi-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.finance-kpi-card .finance-kpi-head > span:first-child {
  min-width: 0;
  color: var(--muted);
  font-size: .715rem; font-weight: 800;
  letter-spacing: .07em; text-transform: uppercase;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bs-main .finance-kpi-card strong {
  margin-top: 11px;
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 1.35vw, 1.5rem);
  font-weight: 800; letter-spacing: -.035em; line-height: 1.12;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.finance-kpi-card .finance-kpi-context { margin-top: 6px; font-size: .715rem; line-height: 1.35; }
.finance-kpi-card .metric-icon { width: 30px; height: 30px; }
.finance-kpi-card .metric-icon .bs-icon { width: 16px; height: 16px; }

/* Tone: icon chip tint + the 2px top accent. Tokens only. */
.finance-kpi-card.finance-kpi-green .metric-icon { background: color-mix(in srgb, var(--green) 13%, transparent); color: var(--green); }
.finance-kpi-card.finance-kpi-amber .metric-icon { background: color-mix(in srgb, var(--warning) 16%, transparent); color: var(--warning); }
.finance-kpi-card.finance-kpi-info .metric-icon { background: color-mix(in srgb, var(--info) 15%, transparent); color: var(--info); }
.finance-kpi-card::before { height: 2px; opacity: 1; }
.finance-kpi-card.finance-kpi-green::before { background: color-mix(in srgb, var(--green) 62%, transparent); }
.finance-kpi-card.finance-kpi-amber::before { background: color-mix(in srgb, var(--warning) 62%, transparent); }
.finance-kpi-card.finance-kpi-info::before { background: color-mix(in srgb, var(--info) 62%, transparent); }

/* Column ladder. Restated at the same specificity as the Phase 1 rules
   above and later in source order, so it simply wins. Five across only
   where five cards genuinely fit a currency figure without clipping;
   three, then two, below that. */
@media (min-width: 1280px) { .bs-main .finance-summary-metrics { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
@media (min-width: 600px) and (max-width: 1279px) { .bs-main .finance-summary-metrics { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 599px) { .bs-main .finance-summary-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } }
@media (max-width: 719px) { .bs-main .finance-kpi-card { min-height: 104px; padding: 13px 14px 14px; } }

/* ---- 3. Section bands ------------------------------------------------ */
/* Three labelled bands (performance / analytics / records) instead of one
   flat stack. .section-kicker is the app's existing kicker style, reused. */
.finance-section { display: grid; gap: 13px; }
.finance-section-head { display: grid; gap: 3px; }
.finance-section-head h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.02rem; font-weight: 800; letter-spacing: -.025em;
  color: var(--ink);
}

/* ---- 4. Analytics row: Monthly Invoiced Amount | Finance Insights ---- */
/* Pairing these two is what removes the dead space the monthly card used
   to leave to its right in its compact (1-month / 2-3-month) tiers. */
.finance-analytics-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: var(--space-card-gap);
}
@media (max-width: 1023px) { .finance-analytics-grid { grid-template-columns: 1fr; } }
.finance-monthly-card .finance-line-chart { flex: 1 1 260px; }

/* ---- 5. Finance Insights card --------------------------------------- */
/* NOT an AI feature and never labelled as one — every row is arithmetic
   over the same two arrays the rest of this page reads (see
   buildFinanceInsights in js/dashboard.js). A row with no supporting data
   renders its label and an honest sentence, with no value element at all —
   which is why .finance-insight-value is optional in the markup. */
.finance-insights-list { list-style: none; margin: 0; padding: 0; display: grid; }
.finance-insight {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 13px 0;
  border-top: 1px solid var(--line);
}
.finance-insight:first-child { border-top: 0; padding-top: 1px; }
.finance-insight:last-child { padding-bottom: 1px; }
.finance-insight-icon {
  display: grid; place-items: center;
  flex: 0 0 auto; width: 30px; height: 30px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--muted) 13%, transparent);
  color: var(--muted);
}
.finance-insight-icon .bs-icon { width: 16px; height: 16px; }
.finance-insight-green .finance-insight-icon { background: color-mix(in srgb, var(--green) 13%, transparent); color: var(--green); }
.finance-insight-amber .finance-insight-icon { background: color-mix(in srgb, var(--warning) 16%, transparent); color: var(--warning); }
.finance-insight-danger .finance-insight-icon { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); }
.finance-insight-info .finance-insight-icon { background: color-mix(in srgb, var(--info) 15%, transparent); color: var(--info); }
.finance-insight-text { display: grid; gap: 2px; min-width: 0; }
.finance-insight-label {
  color: var(--muted);
  font-size: .705rem; font-weight: 800;
  letter-spacing: .06em; text-transform: uppercase;
  overflow-wrap: anywhere;
}
.finance-insight-value {
  font-family: var(--font-display);
  font-size: 1.1rem; font-weight: 800; letter-spacing: -.03em; line-height: 1.2;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.finance-insight-meta { color: var(--muted); font-size: .755rem; line-height: 1.45; overflow-wrap: anywhere; }

/* ---- 6. Chart-card polish ------------------------------------------- */
.finance-chart-card h3 { font-size: 1rem; font-weight: 800; letter-spacing: -.02em; }
.finance-card-heading { align-items: center; }
.finance-card-heading .analytics-note { flex: 0 0 auto; white-space: nowrap; }
.bs-main .finance-chart-card { padding: 19px 20px; }
.bs-main .finance-recent-card { padding: 0; }
.finance-recent-card .finance-card-heading { padding: 19px 20px 0; }
.finance-recent-table th, .finance-recent-table td { padding: 11px 20px; }
@media (max-width: 719px) {
  .bs-main .finance-chart-card { padding: 16px; }
  .finance-recent-card .finance-card-heading { padding: 16px 16px 0; }
  .finance-recent-table th, .finance-recent-table td { padding: 10px 16px; }
}

/* Empty states now carry a real icon from js/icons.js instead of a glyph. */
.finance-chart-empty { padding: 26px 12px; }
.finance-chart-empty .empty-icon { width: 40px; height: 40px; background: color-mix(in srgb, var(--green) 10%, transparent); opacity: 1; }
.finance-chart-empty .empty-icon .bs-icon { width: 19px; height: 19px; }

/* Donut legend: hairline-separated rows read as a table of figures rather
   than a loose list. Values/colours are untouched. */
.finance-donut-wrap { gap: 22px; }
.finance-donut { width: 156px; height: 156px; }
.finance-legend-detailed { gap: 0; flex: 1 1 210px; min-width: 0; }
.finance-legend-detailed li { padding: 8px 0; border-top: 1px solid var(--line); }
.finance-legend-detailed li:first-child { border-top: 0; padding-top: 0; }
.finance-legend-detailed li:last-child { padding-bottom: 0; }
.finance-legend-detailed li strong { font-variant-numeric: tabular-nums; }

/* Pipeline rows: a touch more air, same components, same counts. */
.finance-pipeline-row { grid-template-columns: 116px minmax(0, 1fr) auto; }
.pipeline-list { gap: 13px; }
@media (max-width: 719px) { .finance-pipeline-row { grid-template-columns: 96px minmax(0, 1fr) auto; } }

/* Monthly summary figure sits in a narrower column now, so it steps down a
   little to stay on one line beside the chart. */
.finance-monthly-summary strong { font-size: 1.65rem; font-variant-numeric: tabular-nums; }
.finance-monthly-inner { gap: 26px; }

/* ---- 7. Records band: one full-width table per row -------------------- */
/* Side by side, each Recent card only got ~540px — narrower than the six
   columns' own min-content width (measured: 660px), so the Status and
   actions columns sat permanently inside the wrap's internal scroller and
   the Customer/Date cells wrapped onto three lines. Giving each table the
   full content column shows all six columns with no truncation and no
   internal scrolling at desktop widths. .finance-recent-table-wrap keeps
   its `contain:paint` (Phase 1 FIX 5) untouched, so the narrow-width
   fallback still cannot leak into the page's scroll width. */
.finance-recent-grid { grid-template-columns: minmax(0, 1fr); }

/* ---- 8. Trend chart: baseline + area fill ---------------------------- */
/* Decorative layers over the SAME coordinates the polyline already uses
   (see lineChart() in js/dashboard.js). currentColor-free, token-driven,
   and deliberately faint — no glow, no neon. */
.finance-line-base { stroke: var(--line); stroke-width: 1; }
.finance-line-area { fill: color-mix(in srgb, var(--green) 14%, transparent); stroke: none; }
[data-theme=dark] .finance-line-area { fill: color-mix(in srgb, var(--green) 12%, transparent); }
.finance-monthly-card .finance-line-chart { max-width: none; }
.finance-monthly-inner { align-items: flex-end; }
.finance-monthly-inner-compact { align-items: center; }
/* The card is stretched to the Finance Insights card's height by the grid.
   Letting the inner block grow and centre its wrapped lines spreads the
   leftover height above and below the chart instead of dumping all of it
   under the month labels. */
.finance-monthly-card .finance-monthly-inner { flex: 1 1 auto; align-content: center; }

/* ---- 9. Narrow-width KPI labels ------------------------------------- */
/* At two columns a KPI label has ~150px beside its icon chip, which
   ellipsised "Total Invoiced" to "TOTAL INVOIC…" — a KPI you cannot name is
   worse than a taller card. Below tablet the label wraps instead, and the
   head aligns to the top so the icon chip stays put on a two-line label. */
@media (max-width: 899px) {
  .finance-kpi-head { align-items: flex-start; }
  .finance-kpi-card .finance-kpi-head > span:first-child {
    white-space: normal; overflow: visible; text-overflow: clip;
    line-height: 1.3; letter-spacing: .05em;
  }
}

/* ==========================================================================
   PHASE 3 — INVOICE / QUOTATION LISTS + EDITORS ("Executive Command
   Center" redesign, continued). ADDITIVE ONLY — a pure visual/UX pass over
   js/invoices-ui.js's renderInvoicesWorkspaceShell/renderInvoiceSummaryCards/
   renderInvoiceRows/renderInvoiceEditor (+ its private row/tax-panel/
   payment-history helpers) and js/quotations-ui.js's equivalent five
   functions. No rule here changes a number, a filter, a calculation or an
   event listener — those files' own header comments describe what actually
   computes each value; this block only restyles the markup they already
   emit (plus a small number of new combo classes those files now also
   emit, called out below). renderInvoicePrintView/renderQuotationPrintView
   and every print-doc- or ti- prefixed rule under @media print are untouched —
   nothing in this phase can reach the PDF output.

   SCOPING DISCIPLINE — the whole point of the trap this comment exists to
   document: three shared base classes are ALSO used by the already-approved,
   LOCKED Finance Dashboard (js/dashboard.js), so their base rules are never
   touched here:
     - .metric-card (dashboard KPI tiles use `metric-card finance-kpi-card
       finance-kpi-*`; these two files' list summary cards now use
       `metric-card finance-list-metric-card finance-metric-tone-*` instead
       — a sibling combo-class family, never `finance-kpi-*`).
     - .table-wrap (dashboard's Recent Invoices/Quotations tables use
       `table-wrap finance-recent-table-wrap`; the list table and the
       editor's line-items/payment-history tables now use
       `table-wrap finance-list-table-wrap` / `table-wrap
       finance-editor-table-wrap` instead — again, never
       `finance-recent-table-wrap`).
     - .badge / .invoice-status-* / .quotation-status-* — completely
       unmodified in this phase, per explicit instruction; they already read
       correctly against the new dark cards because they already use the
       same design tokens the dashboard badges use.
   Also left untouched for the same reason (verified by grep against every
   js/*.js file, not assumed): base .icon-button and the `view-invoice` /
   `view-quotation` action classes, because js/dashboard.js's Recent
   Invoices/Quotations "View" button reuses those exact two class names —
   restyling them here would silently restyle the locked dashboard's Recent
   tables too. The list rows below therefore carry an additional
   `finance-list-icon-btn` class (dashboard's buttons never get it) and every
   rule in this phase that touches action-button appearance is keyed to that
   new class, never to `.icon-button` itself. Base `.modal` is left alone for
   the identical reason — js/confirm.js's confirm dialog and (elsewhere)
   the lead form both extend the same base `.modal` rule — so the payment
   modal is only ever restyled through its own already-unique `.payment-modal`
   class.

   Classes confirmed EXCLUSIVE to these two files by grep across every
   js/*.js in the project (safe to restyle at their own base selector, no
   combo class needed): .leads-card, .leads-card-header, .toolbar, .search,
   .filter-row, .select-field, .clear-search, .clear-filters,
   .export-buttons, .export-button, .result-count, .invoice-editor-shell,
   .invoice-editor-header, .invoice-editor-card, .invoice-editor-footer,
   .invoice-items-table, .item-tax-row, .item-tax-panel,
   .item-tax-toggle-line, .tax-toggle-button, .invoice-totals-summary,
   .invoice-totals-grand, .field-pair, .form-grid, .required-indicator,
   .payment-modal.
   ========================================================================== */

/* ---- 1. List card shell + in-card header ----------------------------- */
.leads-card {
  border-radius: var(--radius-lg);
  border-color: color-mix(in srgb, var(--green) 20%, var(--line));
}
[data-theme=dark] .leads-card { border-color: color-mix(in srgb, var(--green) 28%, var(--line)); }
.leads-card-header { padding: 24px 26px 18px; gap: 18px; }
.leads-card-header h2 {
  font-size: 1.22rem; letter-spacing: -.03em; font-weight: 800;
}
.leads-card-header .section-kicker { margin: 0 0 5px; }
.leads-card-header .result-count { margin-top: 6px; font-size: .8rem; }
@media (max-width: 719px) { .leads-card-header { padding: 18px 18px 13px; } }

/* ---- 2. Summary metric strip ------------------------------------------ */
/* New combo classes only — the base .metric-card / .metrics rules (shared
   with the dashboard's KPI tiles) are never edited. Tone accents mirror the
   dashboard's own finance-kpi-green/amber/info recipe (2px top accent +
   tinted icon chip) using a parallel, non-colliding class family. */
.invoice-summary-metrics,
.quotation-summary-metrics { margin: 0; padding: 20px 26px 2px; gap: var(--space-card-gap); }
.finance-list-metric-card {
  min-height: 104px; padding: 15px 16px 16px;
  border-radius: var(--radius);
  border-color: color-mix(in srgb, var(--green) 18%, var(--line));
}
[data-theme=dark] .finance-list-metric-card { border-color: color-mix(in srgb, var(--green) 26%, var(--line)); }
.finance-list-metric-card:hover { border-color: color-mix(in srgb, var(--green) 38%, var(--line)); }
.finance-list-metric-card span:not(.metric-icon) {
  font-size: .715rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
}
.finance-list-metric-card strong {
  margin-top: 10px;
  font-size: clamp(1.1rem, 1.5vw, 1.45rem);
  font-variant-numeric: tabular-nums;
}
.finance-list-metric-card:before { height: 2px; opacity: 1; }
.finance-list-metric-card .metric-icon { width: 30px; height: 30px; border-radius: var(--radius-sm); font-size: .95rem; }
.finance-metric-tone-green .metric-icon { background: color-mix(in srgb, var(--green) 13%, transparent); color: var(--green); }
.finance-metric-tone-green:before { background: color-mix(in srgb, var(--green) 62%, transparent); }
.finance-metric-tone-amber .metric-icon { background: color-mix(in srgb, var(--warning) 16%, transparent); color: var(--warning); }
.finance-metric-tone-amber:before { background: color-mix(in srgb, var(--warning) 62%, transparent); }
.finance-metric-tone-info .metric-icon { background: color-mix(in srgb, var(--info) 15%, transparent); color: var(--info); }
.finance-metric-tone-info:before { background: color-mix(in srgb, var(--info) 62%, transparent); }
.finance-metric-tone-danger .metric-icon { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); }
.finance-metric-tone-danger:before { background: color-mix(in srgb, var(--danger) 62%, transparent); }
@media (max-width: 899px) {
  .finance-list-metric-card span:not(.metric-icon) { white-space: normal; line-height: 1.3; }
}

/* ---- 3. Toolbar: search + status filter + export ---------------------- */
/* No new filter dimensions are added here — only the existing search box
   and status filter are restyled, per explicit instruction. */
.toolbar { padding: 14px 26px 18px; gap: 14px 18px; background: transparent; }
.search { max-width: 420px; }
.search input {
  min-height: 38px; border-radius: var(--radius-sm);
  background: var(--surface-soft);
}
.search input:focus { background: var(--surface); }
.filter-row { gap: 10px; }
.filter-row select {
  min-height: 38px; border-radius: var(--radius-sm);
  background: var(--surface-soft); font-weight: 700;
}
.clear-filters, .clear-search { border-radius: var(--radius-sm); }
.export-buttons { gap: 8px; }
.export-button {
  border-radius: var(--radius-sm);
  border-color: color-mix(in srgb, var(--green) 22%, var(--line));
}
.export-button:hover:not(:disabled) { border-color: color-mix(in srgb, var(--green) 42%, var(--line)); }
@media (max-width: 719px) { .toolbar { padding: 13px 16px 15px; } }

/* ---- 4. List table ----------------------------------------------------- */
/* New combo classes (finance-list-table-wrap / finance-list-table) keep
   every rule below off the shared base .table-wrap selector — see the
   scoping-discipline note at the top of this phase. */
/* contain:paint — same Chromium table-inside-overflow:auto scroll-width
   leak already diagnosed and fixed on the dashboard's own
   .finance-recent-table-wrap (see that rule's comment for the full
   root-cause writeup and the window.scrollTo/scrollX proof method).
   Reproduced independently here: the Quotations list table (8 columns +
   up to 5 action buttons) exceeds .table-wrap's own internal scroll width
   at 768px and otherwise leaks into the page's own horizontal scrollable
   region. Scoped to this combo class only — base .table-wrap and the
   dashboard's own .finance-recent-table-wrap are untouched. */
.finance-list-table-wrap { border-top: 1px solid var(--line); contain: paint; }
.finance-list-table th {
  background: var(--surface-soft);
  font-size: .665rem; letter-spacing: .07em;
}
.finance-list-table td { font-size: .83rem; }
.finance-list-table tbody tr { transition: background var(--ease); }
.finance-list-table tbody tr:hover { background: color-mix(in srgb, var(--green) 5%, transparent); }
.finance-list-table td[data-label=Amount] small,
.finance-list-table td[data-label="Valid Until"] small {
  display: block; margin-top: 3px; color: var(--warning); font-weight: 700; font-size: .71rem;
}
.finance-list-table td[data-label=Status] small { display: block; margin-top: 4px; color: var(--muted); font-size: .71rem; }
.finance-list-actions { display: flex; flex-wrap: wrap; gap: 2px; }
.finance-list-icon-btn {
  border-radius: var(--radius-sm);
  font-size: .74rem; font-weight: 750;
}
.finance-list-icon-btn-danger { color: var(--danger); }
.finance-list-icon-btn-danger:hover { background: var(--danger-soft); }

/* ---- 5. Empty states ---------------------------------------------------- */
.finance-list-empty { padding: 54px 24px; }
.finance-list-empty .empty-icon {
  width: 44px; height: 44px;
  background: color-mix(in srgb, var(--green) 10%, transparent);
}

/* ---- 6. Full-screen editor shell (shared by both editors) ------------- */
.invoice-editor-shell { gap: 18px; }
.invoice-editor-header { padding-top: 4px; }
.invoice-editor-header h1 { font-size: clamp(1.35rem, 2vw, 1.7rem); }
.invoice-editor-card {
  border-radius: var(--radius-lg);
  border-color: color-mix(in srgb, var(--green) 18%, var(--line));
  padding: 24px;
}
[data-theme=dark] .invoice-editor-card { border-color: color-mix(in srgb, var(--green) 26%, var(--line)); }
.invoice-editor-card h2 { margin-bottom: 18px; font-size: .82rem; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.invoice-editor-card h2 span {
  background: color-mix(in srgb, var(--green) 14%, transparent);
  color: var(--green);
}
.invoice-editor-footer { padding-top: 18px; }

/* ---- 6b. Form field containment (Billing Address overflow fix) --------- */
/* VISUAL-FIX ROUND (post-Phase-3): the Billing Address field (and Notes/
   Terms, same markup shape) is a `<label class="full">` — a CSS Grid item
   of `.form-grid` — wrapping a `<textarea>`. Two independent things let it
   escape its card:
   1. `.form-grid` is `display:grid` and its `<label>` children never had
      `min-width:0`. A grid item's default `min-width` is `auto`, which
      means "at least as wide as your content's own intrinsic minimum" —
      the exact same class of bug already fixed on `.invoice-editor-shell`
      (see its own comment above) and on the dashboard's KPI grid, just one
      level deeper here (the grid track is `.form-grid`'s column, not the
      page shell).
   2. Nothing in this app ever set `resize` on these textareas, so they
      kept the browser's own default of `resize: both` — which lets a
      person drag the native resize handle horizontally. A user-dragged
      width is an explicit inline size that plain `width:100%` cannot
      override, and it happily drags the textarea (and the grid track
      around it) wider than the card, independent of bug #1 above.
   Fixing both: grid items get `min-width:0` so they never stretch past
   their track on their own, and every input/select/textarea inside an
   editor card gets a hard `max-width:100%` so nothing — dragged, pasted,
   or overflowing on its own — can ever exceed its container regardless of
   cause. Resize is narrowed to `resize:vertical` (height-only), matching
   the CRM's own lead-modal textareas (`.lead-modal textarea{resize:
   vertical}`) elsewhere in this same stylesheet — so the field can still
   grow taller for a long address, just never wider than its column. */
.invoice-editor-card .form-grid > label { min-width: 0; }
.invoice-editor-card .form-grid input,
.invoice-editor-card .form-grid select,
.invoice-editor-card .form-grid textarea { max-width: 100%; box-sizing: border-box; }
.invoice-editor-card .form-grid textarea { resize: vertical; }

/* ---- 7. Line items table + Advanced Tax panel -------------------------- */
/* finance-editor-table-wrap is the new combo class (paired with the shared
   base .table-wrap) used by BOTH the line-items table and the payment-
   history table inside each editor.
   VISUAL-FIX ROUND (post-Phase-3): this rule originally set a plain
   `overflow: hidden` to clip the table's square corners to the wrapper's
   rounded border-radius. That also silently overrode the shared base
   `.table-wrap { overflow-x: auto }` (same specificity, later in the
   cascade, and `overflow` is a shorthand that sets both axes at once) —
   so once the Line Items & Tax table's real min-content width (HSN/Qty/
   Unit/Rate/Discount/Tax %/Total/Actions) exceeded the wrapper, there was
   no scrollbar to reach the clipped columns; they were just gone. Split
   into the two longhands instead: overflow-x stays scrollable (restoring
   the exact behavior `.table-wrap` already provides everywhere else in
   the app), overflow-y stays hidden so the rounded corners still clip
   correctly. */
.finance-editor-table-wrap { border: 1px solid var(--line); border-radius: var(--radius); overflow-x: auto; overflow-y: hidden; }
.finance-editor-table-wrap table { border-collapse: collapse; }
.invoice-items-table th { background: var(--surface-soft); font-size: .655rem; }
.invoice-items-table tbody tr:not(.item-tax-row):hover { background: color-mix(in srgb, var(--green) 4%, transparent); }
.finance-editor-item-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; }
.btn-catalog-placeholder { opacity: .6; }
.btn-catalog-placeholder:hover { transform: none; }
/* Phase 25 (Products & Services) — the "+ Add from Catalog" picker modal.
   Reuses .modal/.modal-backdrop and the shared .search input styling
   verbatim (see js/products-ui.js's renderCatalogPickerModal); only the
   scrollable product list itself is new here. */
.catalog-picker-modal { width: min(100%, 560px); }
.catalog-picker-list { display: flex; flex-direction: column; gap: 6px; max-height: 360px; overflow-y: auto; margin-top: 14px; }
.catalog-picker-item { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); text-align: left; transition: background var(--ease), border-color var(--ease); }
.catalog-picker-item:hover, .catalog-picker-item:focus-visible { border-color: color-mix(in srgb, var(--green) 40%, var(--line)); background: var(--surface-soft); }
.catalog-picker-item-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.catalog-picker-item-main strong { font-size: .86rem; }
.catalog-picker-item-main small { color: var(--muted); font-size: .72rem; }
.catalog-picker-item-meta { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; color: var(--muted); font-size: .72rem; white-space: nowrap; }
.catalog-picker-item-rate { font-weight: 750; font-size: .84rem; white-space: nowrap; }
.catalog-picker-empty { margin: 14px 0 0; padding: 20px; text-align: center; color: var(--muted); font-size: .83rem; }
.finance-editor-icon-btn { border-radius: var(--radius-sm); }
.item-tax-row { background: color-mix(in srgb, var(--surface-soft) 60%, transparent); }
.item-tax-panel { border-radius: var(--radius); background: var(--surface-soft); }
.item-tax-panel-title { color: var(--green); }
.tax-toggle-button { border-radius: 999px; }

/* ---- 8. Totals summary --------------------------------------------------- */
.finance-editor-totals-kicker {
  width: 100%; margin: 0 0 -2px;
  color: var(--muted); font-size: .68rem; font-weight: 800;
  letter-spacing: .08em; text-transform: uppercase; text-align: right;
}
.invoice-totals-summary { border-radius: 0; }
.invoice-totals-grand strong { font-variant-numeric: tabular-nums; }

/* ---- 9. Payment history ------------------------------------------------- */
.finance-editor-payment-wrap { margin-bottom: 6px; }
.finance-editor-payment-table th { background: var(--surface-soft); }
.invoice-outstanding-note strong { font-variant-numeric: tabular-nums; color: var(--green); }

/* ---- 10. Record Payment modal ------------------------------------------- */
/* Scoped entirely to .payment-modal — base .modal is shared with the confirm
   dialog (js/confirm.js) and is never touched here. */
.payment-modal { border-radius: var(--radius-lg); }
.payment-modal-outstanding { color: var(--green); font-variant-numeric: tabular-nums; }

/* ---- 11. Small-width tuning --------------------------------------------- */
@media (max-width: 719px) {
  .invoice-editor-card { padding: 18px; }
  .finance-editor-totals-kicker { text-align: left; }
}

/* ==========================================================================
   PHASE 5 UI REFINEMENT — Payments / Collections workspace polish.

   The workspace (finance/js/payments-ui.js) already reuses every shared
   class from the Phase 3 section above verbatim — .leads-card,
   .leads-card-header, .toolbar, .search, .filter-row, .select-field,
   .field-pair, .clear-filters, .export-buttons, .finance-list-table, etc.
   — so most of it never needed a single new rule. Two real problems only
   showed up once the page was actually run locally, both because Payments
   carries MORE controls than its siblings (a date range plus customer and
   method filters, versus Invoices'/Quotations' one status filter; three
   summary cards instead of four):

   1. The 3-card summary row (.payment-summary-metrics) was never added to
      the existing `.invoice-summary-metrics,.quotation-summary-metrics`
      combo rule above (Phase 3, section 2), so it fell back to the BARE
      `.metrics` rule instead — that base rule carries page-level spacing
      (`margin: 35px 0 28px`, meant for a top-of-page KPI strip like the
      Dashboard's) rather than the tight `margin:0;padding:20px 26px 2px`
      every OTHER in-card metrics row already gets. That mismatch was the
      actual cause of the reported "large empty vertical area" — not a
      missing min-height or a layout bug, just a missing class membership.

   2. `.toolbar` is `display:flex;justify-content:space-between` with no
      wrap anywhere in the cascade (Phase 1/3 above). With only two toolbar
      children — .search and .filter-row — that's fine when .filter-row is
      short (Invoices/Quotations: one status select + Clear filters +
      exports). Payments' .filter-row carries five more controls (date
      range, 2 selects, Clear filters, 2 export buttons), which squeezed
      .search down toward its flex-basis of 0 — the reported "search field
      is just a small icon box".

   3. A bare `<select>` has no width of its own — it renders as wide as its
      longest `<option>` text. `.filter-row select{min-width:145px}`
      (Phase 1) only ever set a floor, never a cap, so the customer filter
      (whose options are full "Name — Company" strings) grew to whatever
      its longest saved customer needed — the reported "customer selector
      becoming extremely wide".

   REVISION (single-row requirement): the first pass fixed #2 by letting
   the toolbar wrap onto two rows (search alone, then filter-row below).
   That is no longer wanted — every control must stay on ONE row at
   desktop widths, matching Invoices'/Quotations' own toolbars, with
   wrapping reserved for the existing ≤899px stack breakpoint only. Every
   rule below is a deliberately calculated flex-basis/min-width/max-width
   for each of the seven controls (see section 2's own comment for the
   exact numbers and reasoning) rather than a wrap-based escape hatch;
   section 4 below is the actual fix for #3. Every rule in this section is
   scoped to #payments-workspace (its own DOM id) or to the two Payments-
   only select ids, so none of it can reach Invoices/Quotations/Customers,
   which keep their existing toolbar behavior completely unchanged.

   Nothing below changes a class already used elsewhere in this file for
   anything other than adding #payments-workspace, .payment- class names,
   or the two Payments select ids to a selector list — no shared base rule's values
   were edited, no markup shape changed, no business logic touched (pure
   CSS, additive only, same discipline as every other Phase* section in
   this file).
   ========================================================================== */

/* ---- 1. Summary cards: join the existing in-card metrics spacing, 3-up
   instead of 4-up ------------------------------------------------------ */
.invoice-summary-metrics,
.quotation-summary-metrics,
.payment-summary-metrics { margin: 0; padding: 20px 26px 2px; gap: var(--space-card-gap); }
/* Column count only — the base `.metrics{grid-template-columns:repeat(4,
   minmax(0,1fr))}` rule (Phase 1, section "Content") already collapses to
   `repeat(2,...)` at the SAME 899px boundary used everywhere else in this
   file (see that rule's own `@media(max-width:899px)` companion) — this
   just supplies the correct 3-up count at desktop widths instead of
   inheriting the 4-up default meant for Invoices'/Quotations' own cards,
   which would otherwise leave an empty fourth column. */
@media (min-width: 900px) {
  .payment-summary-metrics { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ---- 2. Toolbar: ONE row at desktop widths, with search absorbing the
   row's spare width instead of leaving a dead gap.

   REVISION 3 (controls compressed to the right, a large empty area before
   them — actual cause, found by inspecting the cascade, not guessed): the
   base .toolbar rule (Phase 1, the "Content" section, unscoped) is
   display:flex; justify-content:space-between. With exactly two flex
   children here (.search and .filter-row), space-between pushes them to
   the two ends of the row and puts ALL leftover free space into the one
   gap between them — it does not hand that space to either child. The
   previous pass then capped .search at max-width:320px, well short of
   that leftover space, so the gap between search and the filter controls
   grew as wide as the screen allowed while the five filter-row controls
   stayed pinned together at the row's far right edge. That is exactly the
   "compressed to the right, dead space on the left" symptom reported.
   Fix, two parts: (a) override justify-content to flex-start for this
   toolbar only — Invoices and Quotations, with a single status filter,
   still want the base space-between layout, so this stays
   #payments-workspace-scoped and cannot reach them; (b) drop .search's
   max-width so its flex:1 1 0 actually consumes the row's spare width
   itself, instead of that space collecting in one artificial gap.
   min-width:240px is unchanged — still the floor that keeps the
   placeholder legible on a narrower desktop width.

   The rest of this section is unchanged from the previous pass:
     - .filter-row: flex:0 0 auto — every child below now has an explicit
       width, so its own total width is fixed; .search alone flexes
       around it.
     - .payment-date-range/.select-field/.clear-filters/.export-buttons:
       flex:0 0 auto (flex-shrink:0 is the load-bearing part — without it
       the browser can still compress a control below its own explicit
       width when the row is tight, which is exactly what section 4's
       select widths need to actually hold).
   All of this is released back to the existing mobile behavior at
   ≤899px — .search regains max-width:none (its own existing ≤899px rule,
   Phase 1) and the two selects regain width:100% (the existing
   .filter-row select{width:100%} ≤899px rule, Phase 1) — both need a
   same-breakpoint override here only because an ID selector's specificity
   would otherwise beat those existing class-only mobile rules; see
   section 4's own comment for the same reasoning applied to the selects. */
#payments-workspace .toolbar { flex-wrap: nowrap; justify-content: flex-start; }
#payments-workspace .search { flex: 1 1 0; min-width: 240px; max-width: none; }
#payments-workspace .filter-row { flex: 0 0 auto; flex-wrap: nowrap; }
#payments-workspace .payment-date-range,
#payments-workspace .select-field,
#payments-workspace .clear-filters,
#payments-workspace .export-buttons { flex: 0 0 auto; }
@media (max-width: 899px) {
  #payments-workspace .search { max-width: none; min-width: 0; }
}

/* ---- 3. Date range: .field-pair (Phase 22, shared with the invoice/
   quotation editors' Salutation+Name / Country-code+Phone pairs) uses a
   deliberately asymmetric `minmax(72px,88px) 1fr` column split — right for
   a short label beside a wide input, wrong for two equal-weight date
   fields, which is why From/To previously ran together. Overridden only
   for this element (id + class, so the shared base rule other screens use
   is untouched) with two equal columns instead.

   REVISION 2 (borders touching — actual cause, found by inspecting the
   cascade, not guessed): the previous pass narrowed these columns to
   `minmax(84px,96px)` but never told the grid ITEMS — the two <label>
   elements; the <input type=date> is their child, not a grid item itself
   — to respect that track width. A grid item's automatic minimum size
   defaults to its own min-content size, not 0, and min-content is
   inherited from descendants: a native `<input type=date>` can't render
   its day/month/year segments + calendar-picker icon below roughly
   130-150px in Chromium/Edge, no matter what width is requested. Since
   84-96px is narrower than that floor, the grid tracks were forced to
   blow out past their declared max-width to fit the input — silently
   consuming the 8px From/To gap and then the 10px .filter-row gap after
   it, so From, To and the customer select rendered edge-to-edge. This is
   the standard CSS Grid/Flexbox "min-width:auto blowout" (the same family
   of bug documented for flex items); it is confirmed here simply by the
   fact that 96px is smaller than a native date input can ever paint.
   Fix, two parts: (a) fixed columns — now 130px (top of this requirement's
   125-135px target, up from the first pass's 120px, so Chromium/Edge has
   the most room this spec allows to paint the full four-digit year
   instead of abbreviating it — that abbreviation is an internal browser
   rendering decision based on available layout width, not something CSS
   text can override, so giving the input more width is the only real
   fix) — replacing the minmax() that was being silently overridden
   anyway; (b) `min-width: 0` on the grid items and `width: 100%` on the
   input itself, so the input is sized FROM the column instead of sizing
   the column FROM the input. */
#payments-workspace .payment-date-range { grid-template-columns: 130px 130px; gap: 10px; }
#payments-workspace .payment-date-range label { min-width: 0; }
@media (max-width: 379px) {
  #payments-workspace .payment-date-range { grid-template-columns: 1fr; }
}
/* Matches the existing `.search input`/`.filter-row select` height, radius
   and fill (Phase 1, section 3) — plain `<input type=date>` wasn't covered
   by either of those selectors, so it was the only toolbar control left on
   the browser's own default sizing. width:100%/min-width:0 (see REVISION 2
   above) is the actual overflow fix; box-sizing:border-box already applies
   globally (`*{box-sizing:border-box}`, Phase 1 "Content" rule) so this
   100% width already includes the input's own border and can't reintroduce
   the blowout. */
#payments-workspace .filter-row input[type=date] {
  width: 100%; min-width: 0; box-sizing: border-box;
  min-height: 38px; border-radius: var(--radius-sm);
  background: var(--surface-soft);
}

/* ---- 4. Customer / Payment Method selects: the actual fix for "customer
   selector becoming extremely wide" (see section 1's point 3 above for the
   root cause — a bare <select> sizes itself from its longest <option>,
   and the existing base rule only ever set a min-width). Both selects get
   an explicit width comparable to the row's other controls (per
   requirement 3 — not dominant, not shrunk to illegibility), plus
   overflow:hidden/text-overflow:ellipsis/white-space:nowrap so a long
   selected name truncates instead of stretching the row.

   min-width:0 correction, found this pass by rendering in a real browser
   and reading the boxes back, not by re-reading the CSS text: the base
   `.filter-row select{min-width:145px}` rule (Phase 1) is unscoped, so it
   still applies here too, and min-width acts as a floor on the used width
   regardless of selector specificity — an explicit width below that floor
   is silently raised back up to it. That was invisible for the customer
   select (175px is already above the 145px floor) but it was quietly
   forcing the method select to 145px even though width/max-width both
   said 135px. min-width:0 on both selects removes that floor so the
   explicit width is the one actually used. Widths themselves: 175px/135px
   (top of this requirement's 165-180px / 130-140px targets, up from the
   previous pass's 165px/130px) now that the toolbar fix above (section 2)
   frees search from its old 320px cap and lets it absorb the row's spare
   width, leaving more of the row available for these two controls too.
   Everything else about them (height, radius, fill, font-weight) is still
   the existing `.filter-row select` rule (Phase 1, section 3) — only
   width is new here. IDs are used directly (not #payments-workspace-
   scoped) because both ids are already unique to this one page; the
   ≤899px override restores the existing `.filter-row select{width:100%}`
   mobile rule (Phase 1), which a bare ID selector would otherwise always
   outrank. */
#payment-customer-filter {
  width: 175px; max-width: 175px; min-width: 0;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
#payment-method-filter {
  width: 135px; max-width: 135px; min-width: 0;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
@media (max-width: 899px) {
  #payment-customer-filter,
  #payment-method-filter { width: 100%; max-width: none; }
}

/* ---- 5. Safety net for narrower desktop widths (found this pass by
   rendering at several real desktop widths and reading the actual boxes
   back, not by calculating from the CSS alone, per this requirement's own
   instruction to verify against real widths): search has a floor of
   240px and every filter-row control above is flex:0 0 auto (never
   shrinks), so the row has a fixed minimum content width. At a wide
   monitor (1920px, 1536px) that minimum is well inside the available
   card width and everything fits with room to spare. At a narrower but
   still common desktop width (checked at 1440px and 1366px, both well
   above the 899px stacking breakpoint) the row's fixed minimum width
   started to exceed the card's own width — and because the shared
   .leads-card rule (Phase 1) sets overflow:hidden, that excess was not a
   visible wrap or a scrollbar, it was the Export Excel button silently
   losing part of itself off the edge of the card with no visual sign
   anything was wrong. Rather than let any control shrink below the
   explicit widths this requirement asked for, or let the row wrap before
   the existing 899px breakpoint (both of which this requirement rules
   out), the row gets its own horizontal scroll as a last resort: it stays
   exactly one row and every control keeps its full requested width, and
   only on a desktop window too narrow to show all of it at once does a
   scrollbar appear on the toolbar itself, scoped so it never shows at
   widths where the row already fits (overflow-x:auto only scrolls when
   content actually overflows). A small bottom padding keeps that
   scrollbar from sitting flush against the controls when it does appear. */
#payments-workspace .toolbar { overflow-x: auto; padding-bottom: 6px; }
@media (max-width: 899px) {
  #payments-workspace .toolbar { overflow-x: visible; padding-bottom: 0; }
}
