/* AssetFlowPro — global stylesheet.
 * Extracted verbatim from the inline <style> in templates/base.html.
 * Append new design CSS at the END of this file (source-order-late wins),
 * matching the Design System v2 convention. Fix broken rules in place
 * rather than appending a second definition.
 */
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

    /* ═══════════════════════════════════════════
       DESIGN TOKENS
    ═══════════════════════════════════════════ */
    :root {
      /* Depths */
      --bg:            #070c15;
      --surface:       #0b1120;
      --surface2:      #111928;
      --surface3:      #172035;
      --surface4:      #1e2b40;

      /* Borders */
      --border:        #192438;
      --border-bright: #233148;

      /* Emerald — status / action accent */
      --emerald:       #4f8ef7;
      --emerald-dim:   #2563eb;
      --emerald-muted: #1e3560;
      --emerald-glow:  rgba(79,142,247,0.07);
      --emerald-glow2: rgba(79,142,247,0.14);
      /* Primary-button fill. --emerald is tuned as a *light* accent for dark
         surfaces, so white 12px/700 sits on it at 3.21:1. Same blue, cut dark
         enough to carry white text: fill-a 5.17:1, fill-b 6.81:1. Every
         [data-accent] preset redefines both; a preset that does not simply
         inherits this blue. */
      --btn-fill-a:    #2563eb;
      --btn-fill-b:    #1d4fd0;

      /* Gold — financial figures */
      --gold:          #c9a84c;
      /* Was #8a6e28 = 3.89:1 on --surface / 3.64:1 on --surface2. Fixed at the
         token rather than at .form-section-label.gold because the other three
         consumers are also small text: the money-chip in index.html, the P&L
         cell in intelligence/portfolio.html, a border in re/re_ts_calc.html.
         Same hue (43 deg) and saturation (~56%), lightness 35% -> 42%, so it
         stays clearly below --gold (8.24:1). */
      --gold-dim:      #a8862f;
      --gold-glow:     rgba(201,168,76,0.08);
      --gold-glow2:    rgba(201,168,76,0.16);

      /* Text */
      --text:          #e8f0f8;
      --text-mid:      #9ab8d0;
      /* --text-dim was #8eb5d0 — 1.05:1 from --text-mid, i.e. the same colour.
         The shell builds three levels of emphasis out of these two tokens and
         got one: .user-name vs .user-role, .nav-link vs .nav-pin-btn,
         .sidebar-context-text vs .sidebar-legal all read as one flat grey.
         Light mode always had the step (#2e4a6a vs #4a6888), so the flatness
         only ever showed in the default theme. 5.82:1 on --surface, and a
         1.56:1 step down from --text-mid. --dim is a twin literal consumed by
         inline styles in csv_import / re_probate_import / profile, so it moves
         with it or those labels keep the old value. */
      --text-dim:      #7596b4;
      --dim:           #7596b4;
      /* The scrollbar thumb used to ride --text-dim, and the comment at the
         ::-webkit-scrollbar rule records that its 3:1 non-text floor was a
         deliberate repair. Give it its own token so re-cutting the ink cannot
         quietly un-repair the thumb. 6.60:1 on the track. */
      --scroll-thumb:  #7ea3bf;

      /* Theme-aware surface tints. White-alpha here (dark theme); the
         [data-theme="light"] block flips them to dark-alpha so table dividers,
         zebra striping, header band, and card hover shadows survive light mode
         instead of vanishing / smudging. */
      --row-line:              rgba(255,255,255,0.04);
      --zebra:                 rgba(255,255,255,0.018);
      --head-band:             rgba(255,255,255,0.015);
      --card-hover-shadow:     0 10px 36px rgba(0,0,0,.55);
      --statcard-hover-shadow: 0 8px 28px rgba(0,0,0,.5);

      /* Neutral background washes — same contract as --row-line above. Rules
         that hardcoded rgba(255,255,255,.0X) as a fill lost the fill entirely
         on white; these flip to dark-alpha under [data-theme="light"]. */
      --wash-weak:             rgba(255,255,255,0.02);
      --wash:                  rgba(255,255,255,0.04);
      --wash-strong:           rgba(255,255,255,0.07);

      /* One left inset for the whole rail. Reading top to bottom the content
         edge used to land at 22 / 16 / 18 / 22 / 23 / 22 / 22 / 16 px, so the
         wordmark and the account block hung 6px left of the nav column and the
         search button hung 4px right of it — three staggered left edges on a
         240px rail with a hard right border. 22px is the value most of the
         rail already used. */
      --rail-pad:              22px;

      /* The lift under a pinned table header, and the data-table row hover.
         Both were hardcoded: the header shadow was 20% black at 14px blur in
         both themes (five times the blur and twice the darkness of the app's
         own --shadow-card on a white card), and the row hover was 3.5% of the
         DEFAULT accent blue, so under any other accent preset the hovered row
         washed blue while its first-cell rail turned rose/violet/amber. */
      --head-shadow:           0 4px 14px rgba(0,0,0,0.2);
      /* The edge shadow that says a pane scrolls sideways. Darker than
         --head-shadow on purpose: it has to read over table rows. */
      --shadow-edge:           rgba(0,0,0,0.38);
      --row-hover:             var(--emerald-glow);

      /* Ink that rides ON a filled accent. --emerald / --gold / --amber are
         light fills in dark theme and DARK fills in light theme, so text on
         top has to flip too. --on-gold covers --amber as well (both warm
         accents flip the same direction). --gold-grad2 is the .btn-gold
         gradient far stop, which has to darken with the gold. */
      --on-accent:             #000;
      --on-gold:               #070c15;
      --gold-grad2:            #a8841a;

      /* Status colors */
      --red:           #e05858;
      /* --amber is the warning colour and --gold #c9a84c is the money colour.
         At #e09e40 they were 1.006:1 apart — the same brightness, 9deg of hue
         between them — and they ship side by side (the rentals list footer
         renders Rent in gold and Paid in amber in one row). Lifted to raise
         both the step (1.14:1) and the separation (dE76 15.7 -> 17.4) while
         holding the hue at 35deg so it still reads as warning-orange. The two
         are close by nature; this is as far as lightness alone takes them,
         which is why --chart-* refuses to derive from either. */
      --amber:         #fa9f3e;
      /* --blue is the informational colour and --emerald is the accent. At
         #5b8de0 they were 1.03:1 apart at an identical 217deg hue, so
         .badge-blue and .alert-info were indistinguishable from the accent
         they exist to contrast with. Lighter and slightly cooler: dE76 31,
         1.54:1 from --emerald, and it lifts the badge from 4.29:1 to 6.83:1
         on --surface4 into the bargain. */
      --blue:          #86b7f2;
      --purple:        #9b72e0;
      /* True-green "success" semantic — distinct from --emerald which is the
         blue platform accent. Stays green across accent presets. */
      --success:       #34d399;
      --success-glow:  rgba(52,211,153,0.10);
      --success-glow2: rgba(52,211,153,0.18);

      /* Chart series ramp - ordinal, low risk to critical. Read at runtime by
         the Intelligence charts so a canvas picks up the theme like every
         other component. All six clear 3:1 on --surface (9.79 / 7.41 / 11.28
         / 6.72 / 5.05, neutral 3.96) and adjacent steps stay >=1.3:1 apart
         (1.32 / 1.52 / 1.68 / 1.33); the old chart literals put #c9a84c
         beside #e09e40 (1.01:1) and #4f8ef7 beside #5b8de0 (1.03:1). NOT
         --emerald-derived: an accent preset must not repaint a risk bucket. */
      --chart-1:       #34d399;   /* low risk / performing / gain */
      --chart-2:       #60a5fa;   /* moderate                     */
      --chart-3:       #fbbf24;   /* elevated                     */
      --chart-4:       #f97316;   /* high                         */
      --chart-5:       #e0574c;   /* critical / loss              */
      --chart-neutral: #64748b;   /* unknown / other              */
      /* Chart grid lines. Their own token rather than --border: the dark grid
         is deliberately a barely-there hairline and --border would make it a
         visible cage, while on white the white-alpha literal they used to
         carry was simply invisible. */
      --chart-grid:    rgba(255,255,255,0.04);

      /* Aliases */
      --card:          #0b1120;
      --mono:          Cambria, Georgia, 'Times New Roman', serif;
      --sans:          Cambria, Georgia, 'Times New Roman', serif;
      --display:       Cambria, Georgia, 'Times New Roman', serif;
      --radius-sm:     6px;
      --radius:        10px;
      --radius-lg:     14px;

      /* == Type scale ============================================
         Derived from what app.css actually uses, not a textbook ramp, so
         adopting a token is a 0px change at default density. Roles:
           --fs-1 micro / eyebrow      --fs-2 label
           --fs-3 control text         --fs-4 input, dense body
           --fs-5 table data           --fs-6 section head
           --fs-7 page title           --fs-8 KPI hero (declared but not yet
           adopted: the effective .stat-value size is the later 38px rule)
         Absorption map (old px -> token):
           8, 9, 10 -> fs-1 | 11 -> fs-2 | 12 -> fs-3 | 13 -> fs-4
           14, 14.5, 15 -> fs-5 | 16..20 -> fs-6 | 21..27 -> fs-7
           32, 34, 38 -> fs-8                                        */
      --fs-1:  10px;
      --fs-2:  11px;
      --fs-3:  12px;
      --fs-4:  13px;
      --fs-5:  14px;
      --fs-6:  18px;
      --fs-7:  26px;
      --fs-8:  34px;

      /* == Spacing scale =========================================
         The 4-16px band is where every component-level padding and gap in
         this file lives, so that is what the scale covers. Layout-level
         spacing (18 / 20 / 24 / 28 / 32 / 44 / 52) is deliberately still
         raw px and is a later sweep.
         Absorption map (old px -> token):
           3, 4, 5 -> sp-1 | 6, 7 -> sp-2 | 8, 9 -> sp-3 | 10, 11 -> sp-4
           12, 13 -> sp-5  | 14, 15 -> sp-6 | 16, 17 -> sp-7            */
      --sp-1:  4px;
      --sp-2:  6px;
      --sp-3:  8px;
      --sp-4:  10px;
      --sp-5:  12px;
      --sp-6:  14px;
      --sp-7:  16px;
      --transition:    0.18s cubic-bezier(0.16, 1, 0.3, 1);
      --spring:        cubic-bezier(0.16, 1, 0.3, 1);
      --snap:          cubic-bezier(0.4, 0, 0.2, 1);

      /* Shadows */
      --shadow-card:   0 1px 3px rgba(0,0,0,.55), 0 0 0 1px var(--border);
      /* Material tokens (theme-flipped): --inner-hi = 1px top bevel highlight;
         --shadow-hero = long-throw directional shadow that seats a panel. */
      --inner-hi:      rgba(255,255,255,0.05);
      --shadow-hero:   0 10px 30px -14px rgba(0,0,0,.75), 0 1px 2px rgba(0,0,0,.4);
      --shadow-raise:  0 4px 20px rgba(0,0,0,.65), 0 0 0 1px var(--border-bright);
      --shadow-glow:   0 0 24px rgba(79,142,247,0.06);

      /* ── Module color palette ───────────────────── */
      --vault-color:   #4f8ef7;   /* The Vault / All Files — blue  */
      --vault-glow:    rgba(79,142,247,0.12);
      --ams-color:     #14b8a6;   /* AMS / Loss Mitigation — teal  */
      --ams-glow:      rgba(20,184,166,0.10);
      --crm-color:     #34d399;   /* CRM — emerald green           */
      --crm-glow:      rgba(52,211,153,0.10);
      --intel-color:   #a78bfa;   /* Intelligence — violet         */
      --intel-glow:    rgba(167,139,250,0.10);
      --re-color:      #c9a84c;   /* Real Estate — gold            */
      --re-glow:       rgba(201,168,76,0.10);
      --hecm-color:    #f87171;   /* HECM Pools — rose             */
      --hecm-glow:     rgba(248,113,113,0.10);
      --agmt-color:    #fb923c;   /* Agreements — amber            */
      --agmt-glow:     rgba(251,146,60,0.10);
      --rental-color:  #22d3ee;   /* Rentals — cyan                */
      --rental-glow:   rgba(34,211,238,0.10);
      /* Border tint for Rentals buttons/chips. They hand-wrote
         rgba(34,211,238,.30/.35) inline, which kept the chrome cyan in light
         mode after --rental-color was re-cut to #0e7490. */
      --rental-line:   rgba(34,211,238,0.30);
    }

    /* ═══════════════════════════════════════════
       LIGHT MODE TOKENS
    ═══════════════════════════════════════════ */
    [data-theme="light"] {
      --bg:            #edf0f5;
      --surface:       #ffffff;
      --surface2:      #f2f5f9;
      --surface3:      #e8ecf3;
      --surface4:      #dce2ee;
      --border:        #d8deeb;
      --border-bright: #c4cde0;
      --emerald:       #2563eb;
      --emerald-dim:   #1e40af;
      --emerald-muted: #dbeafe;
      --emerald-glow:  rgba(37,99,235,0.09);
      --emerald-glow2: rgba(37,99,235,0.16);
      --gold:          #7a5a18;
      --gold-dim:      #5c4210;
      --gold-glow:     rgba(122,90,24,0.09);
      --gold-glow2:    rgba(122,90,24,0.17);
      --text:          #16202e;
      --text-mid:      #2e4a6a;
      /* 4.45:1 on --surface4 before; one step darker clears 4.73 and keeps
         the 1.48:1 step down from --text-mid. */
      --text-dim:      #476482;
      --dim:           #476482;
      --red:           #b82828;
      /* Was #9a6a18: 4.13:1 on --bg, 3.98 on --surface3, 3.63 on --surface4,
         and 3.90:1 on the .flash-warning fill it actually ships on — the least
         readable text in light mode, and the only accent token that had not
         been re-cut for white. Same hue and saturation, lightness 35% -> 30%:
         minimum 4.63:1 across every surface and every tinted fill it is paired
         with. It lands near light --gold #7a5a18, so where the two meet,
         separate them by hue, not by weight — which is what the value below
         does: it swings to 27deg, dE76 25 away from light --gold #7a5a18,
         while holding 5.12:1 as its worst case. */
      --amber:         #96450c;
      --blue:          #2856a8;
      --purple:        #5e30a8;
      /* Was #047857 — 4.22:1 on --surface4. */
      --success:       #04674b;
      --success-glow:  rgba(4,120,87,0.10);
      --success-glow2: rgba(4,120,87,0.20);
      /* Chart ramp re-cut for white. Every step clears 3:1 on --surface
         (3.39 / 6.70 / 3.19 / 5.18 / 8.31, neutral 7.58) and --chart-4 also
         clears 4.5:1, which matters because risk_engine hands it out as a
         14px text colour, not just a bar fill. */
      --chart-1:       #0e9f6e;
      --chart-2:       #1d4ed8;
      --chart-3:       #d97706;
      --chart-4:       #c2410c;
      --chart-5:       #991b1b;
      --chart-neutral: #475569;
      --chart-grid:    rgba(0,0,0,0.07);
      --card:          #ffffff;
      --shadow-card:   0 1px 3px rgba(0,0,0,.09), 0 0 0 1px var(--border);
      --inner-hi:      rgba(255,255,255,0.9);
      --shadow-hero:   0 10px 26px -16px rgba(0,0,0,.18), 0 1px 2px rgba(0,0,0,.06);
      --shadow-raise:  0 4px 18px rgba(0,0,0,.12), 0 0 0 1px var(--border-bright);
      --shadow-glow:   0 0 24px rgba(37,99,235,0.07);

      /* Module palette, re-cut for white. The dark values (#22d3ee, #34d399,
         #14b8a6 …) land at 1.8–3.2:1 on --surface in light mode, and they are
         not just chrome — Rentals renders a 38px .stat-value in
         var(--rental-color). Same hues, darkened to clear 4.5:1. */
      --vault-color:   #2563eb;
      --ams-color:     #0f766e;
      --crm-color:     #047857;
      --intel-color:   #6d28d9;
      --re-color:      #7a5a18;
      --hecm-color:    #b91c1c;
      --agmt-color:    #c2410c;
      --rental-color:  #0e7490;
      /* The module *-glow tints were never re-cut for white. --rental-glow is
         the one that actually ships on buttons, so it gets the teal it needs
         instead of leaving a cyan wash under a teal label. */
      --rental-glow:   rgba(14,116,144,0.10);
      --rental-line:   rgba(14,116,144,0.30);
    }

    /* ═══════════════════════════════════════════
       ACCENT COLOR PRESETS
       Override --emerald family so every component
       using --emerald picks up the new accent automatically.
    ═══════════════════════════════════════════ */
    [data-accent="sapphire"] {
      --emerald:       #60a5fa;
      --emerald-dim:   #3b82f6;
      --emerald-muted: #1e3a6e;
      --emerald-glow:  rgba(96,165,250,0.07);
      --emerald-glow2: rgba(96,165,250,0.15);
      --shadow-glow:   0 0 24px rgba(96,165,250,0.08);
      --btn-fill-a:    #1a5fd0;
      --btn-fill-b:    #1550bc;
    }
    [data-accent="violet"] {
      /* #8b5cf6 was 4.45:1 on --surface / 4.16:1 on --surface2, so under this
         preset every label repointed to var(--emerald) below (.nav-label,
         .form-section-label, .lc-label.done) would still have missed AA.
         Same violet, 4pp lighter in HSL: 5.35:1 and 5.00:1. The few places
         --emerald is a fill under #000 text gain too (4.96 -> 5.97). */
      --emerald:       #9670f8;
      --emerald-dim:   #6d28d9;
      --emerald-muted: #2e1d5e;
      --emerald-glow:  rgba(139,92,246,0.07);
      --emerald-glow2: rgba(139,92,246,0.15);
      --shadow-glow:   0 0 24px rgba(139,92,246,0.08);
      --btn-fill-a:    #7c3aed;
      --btn-fill-b:    #5b21b6;
    }
    [data-accent="rose"] {
      --emerald:       #f43f5e;
      --emerald-dim:   #be123c;
      --emerald-muted: #4c1028;
      --emerald-glow:  rgba(244,63,94,0.07);
      --emerald-glow2: rgba(244,63,94,0.15);
      --shadow-glow:   0 0 24px rgba(244,63,94,0.08);
      --btn-fill-a:    #be123c;
      --btn-fill-b:    #9f0f33;
    }
    [data-accent="amber"] {
      --emerald:       #f59e0b;
      --emerald-dim:   #b45309;
      --emerald-muted: #451a03;
      --emerald-glow:  rgba(245,158,11,0.07);
      --emerald-glow2: rgba(245,158,11,0.15);
      --shadow-glow:   0 0 24px rgba(245,158,11,0.08);
      /* No amber that still reads as amber carries white text (#f59e0b is
         2.15:1), so this stop is deliberately a burnt orange: hue 38 -> 24,
         white at 5.80:1 / 7.81:1. */
      --btn-fill-a:    #a8490a;
      --btn-fill-b:    #8a3a06;
    }

    /* ═══════════════════════════════════════════
       DENSITY MODES
    ═══════════════════════════════════════════ */
    [data-density="compact"] {
      --radius:    8px;
      --radius-lg: 11px;
      /* The density toggle only ever moved body font-size, --radius and a
         handful of chrome paddings, so the TABLES - the actual density
         problem - did not respond at all. Re-cutting the two scales here is
         what makes the toggle real: every rule that adopts an fs / sp token
         now scales with density, table cells first. */
      --fs-1:  9px; --fs-2: 10px; --fs-3: 11px; --fs-4: 12px;
      --fs-5: 13px; --fs-6: 16px; --fs-7: 23px; --fs-8: 30px;
      --sp-1:  3px; --sp-2:  5px; --sp-3:  6px; --sp-4:  8px;
      --sp-5:  9px; --sp-6: 11px; --sp-7: 12px;
    }
    [data-density="compact"] body { font-size: 15px; }
    [data-density="compact"] .card { padding: 14px 16px; }
    [data-density="compact"] .nav-link { padding: 6px 12px; font-size: 13px; }
    [data-density="compact"] .sidebar { width: 216px; }
    [data-density="compact"] .main { margin-left: 216px; }
    [data-density="compact"] .topbar { margin-bottom: 18px; }
    [data-density="compact"] .stats-bar { gap: 10px; }
    [data-density="compact"] .stat-card { padding: 14px 18px; }

    [data-density="spacious"] {
      --radius:    13px;
      --radius-lg: 18px;
      --fs-1: 11px; --fs-2: 12px; --fs-3: 13px; --fs-4: 14px;
      --fs-5: 16px; --fs-6: 20px; --fs-7: 29px; --fs-8: 38px;
      --sp-1:  5px; --sp-2:  8px; --sp-3: 10px; --sp-4: 13px;
      --sp-5: 15px; --sp-6: 18px; --sp-7: 21px;
    }
    [data-density="spacious"] body { font-size: 20px; }
    [data-density="spacious"] .card { padding: 28px 32px; }
    [data-density="spacious"] .nav-link { padding: 9px 14px; }
    [data-density="spacious"] .sidebar { width: 264px; }
    [data-density="spacious"] .main { margin-left: 264px; padding: 44px 52px; }
    [data-density="spacious"] .topbar { margin-bottom: 36px; }
    [data-density="spacious"] .stats-bar { gap: 20px; }
    [data-density="spacious"] .stat-card { padding: 24px 28px; }

    /* ═══════════════════════════════════════════
       TOAST NOTIFICATIONS
    ═══════════════════════════════════════════ */
    #toast-container {
      position: fixed; bottom: 24px; right: 24px;
      z-index: 10500; display: flex; flex-direction: column-reverse;
      gap: 10px; pointer-events: none;
    }
    .toast {
      display: flex; align-items: flex-start; gap: 12px;
      padding: 13px 16px; border-radius: var(--radius);
      background: var(--surface2);
      border: 1px solid var(--border-bright);
      box-shadow: var(--shadow-raise);
      pointer-events: all;
      min-width: 280px; max-width: 400px;
      animation: toast-in 0.38s cubic-bezier(0.16,1,0.3,1) both;
    }
    .toast.removing {
      animation: toast-out 0.22s ease forwards;
    }
    @keyframes toast-in {
      from { opacity:0; transform:translateX(24px) scale(0.96); }
      to   { opacity:1; transform:translateX(0) scale(1); }
    }
    @keyframes toast-out {
      to { opacity:0; transform:translateX(24px) scale(0.95); max-height:0; margin:0; padding:0; }
    }
    .toast-icon { font-size:15px; flex-shrink:0; margin-top:1px; line-height:1; }
    .toast-body { flex:1; font-size:13px; line-height:1.5; }
    .toast-close { background:none; border:none; color:var(--text-dim); cursor:pointer;
                   font-size:17px; line-height:1; padding:0; flex-shrink:0; margin-top:-1px; }
    .toast-close:hover { color:var(--text); }
    .toast.toast-success { border-left:3px solid var(--success); }
    .toast.toast-error   { border-left:3px solid var(--red); }
    .toast.toast-warning { border-left:3px solid var(--amber); }
    .toast.toast-info    { border-left:3px solid var(--blue); }

    /* ═══════════════════════════════════════════
       COMMAND PALETTE  (Cmd/Ctrl + K)
    ═══════════════════════════════════════════ */
    #cmd-overlay {
      display: none; position: fixed; inset: 0; z-index: 10200;
      background: rgba(0,0,0,0.55); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
      align-items: flex-start; justify-content: center;
      padding-top: 14vh;
    }
    #cmd-overlay.open { display: flex; }
    #cmd-box {
      background: var(--surface);
      border: 1px solid var(--border-bright);
      border-radius: var(--radius-lg);
      box-shadow: var(--shadow-raise), 0 0 40px rgba(0,0,0,0.5);
      width: 560px; max-width: 92vw;
      overflow: hidden;
      animation: cmd-in 0.25s cubic-bezier(0.16,1,0.3,1) both;
    }
    @keyframes cmd-in {
      from { opacity:0; transform:scale(0.96) translateY(-12px); }
      to   { opacity:1; transform:scale(1) translateY(0); }
    }
    #cmd-input-wrap {
      display: flex; align-items: center; gap: 10px;
      padding: 14px 16px; border-bottom: 1px solid var(--border);
    }
    #cmd-input-wrap svg { flex-shrink:0; opacity:0.4; }
    #cmd-input {
      flex:1; background:none; border:none; outline:none;
      font-size:15px; font-family:var(--sans); color:var(--text);
    }
    #cmd-input::placeholder { color:var(--text-dim); }
    #cmd-results { max-height: 340px; overflow-y: auto; padding: 6px 0; }
    .cmd-item {
      display:flex; align-items:center; gap:12px;
      padding:9px 16px; cursor:pointer; font-size:13px;
      transition:background var(--transition);
    }
    .cmd-item:hover, .cmd-item.focused {
      background:var(--emerald-glow2);
    }
    .cmd-item-icon { width:22px; text-align:center; font-size:13px; color:var(--text-dim); flex-shrink:0; }
    .cmd-item-label { flex:1; color:var(--text); }
    .cmd-item-section { font-size:10px; color:var(--text-dim); font-family:var(--mono);
                        letter-spacing:0.08em; padding:6px 16px 4px;
                        text-transform:uppercase; }
    #cmd-empty { padding:28px; text-align:center; font-size:13px; color:var(--text-dim); }
    #cmd-kbd-hint {
      padding:8px 16px; border-top:1px solid var(--border);
      display:flex; gap:16px; align-items:center;
    }
    .cmd-hint-item {
      display:flex; align-items:center; gap:5px;
      font-size:10px; color:var(--text-dim); font-family:var(--mono);
    }
    .cmd-hint-item kbd {
      background:var(--surface3); border:1px solid var(--border-bright);
      border-radius:4px; padding:1px 5px; font-size:9px; color:var(--text-mid);
    }

    /* ═══════════════════════════════════════════
       PASSWORD STRENGTH METER
    ═══════════════════════════════════════════ */
    .pw-meter { margin-top: 6px; }
    .pw-meter-bar {
      height: 3px; border-radius: 2px;
      background: var(--border); overflow: hidden;
    }
    .pw-meter-fill {
      height: 100%; border-radius: 2px;
      transition: width 0.3s ease, background 0.3s ease;
      width: 0%;
    }
    .pw-meter-label {
      font-size: 10px; font-family: var(--mono);
      margin-top: 4px; color: var(--text-dim);
    }

    html, body { height: 100%; }

    body {
      background: var(--bg);
      background-image:
        radial-gradient(circle at 18% 82%, rgba(79,142,247,0.022) 0%, transparent 48%),
        radial-gradient(circle at 82% 18%, rgba(91,141,224,0.016) 0%, transparent 48%),
        radial-gradient(circle, rgba(255,255,255,0.012) 1px, transparent 1px);
      background-size: auto, auto, 28px 28px;
      color: var(--text);
      font-family: var(--display);
      font-size: 18px;
      line-height: 1.45;
      min-height: 100vh;
    }

    /* ── View transition animations — iOS spring feel ── */
    @keyframes vt-fade-in {
      from { opacity: 0; transform: translateY(6px) scale(0.995); }
      to   { opacity: 1; transform: translateY(0) scale(1); }
    }
    @keyframes vt-fade-out {
      from { opacity: 1; transform: translateY(0) scale(1); }
      to   { opacity: 0; transform: translateY(-4px) scale(0.998); }
    }
    ::view-transition-old(root) {
      animation: 160ms cubic-bezier(0.4, 0, 1, 1) both vt-fade-out;
    }
    ::view-transition-new(root) {
      animation: 320ms cubic-bezier(0.16, 1, 0.3, 1) both vt-fade-in;
    }

    /* Fallback: page content fades in on every load */
    .main { animation: vt-fade-in 300ms cubic-bezier(0.16, 1, 0.3, 1) both; }

    /* ── Navigation progress bar ── */
    #nav-bar {
      position: fixed;
      top: 0; left: 0;
      height: 2px;
      width: 0%;
      background: linear-gradient(90deg, var(--emerald), #7aaef9, var(--emerald));
      background-size: 200% 100%;
      z-index: 9999;
      border-radius: 0 2px 2px 0;
      box-shadow: 0 0 10px rgba(79,142,247,0.6), 0 0 20px rgba(79,142,247,0.3);
      transition: width 0.22s cubic-bezier(0.4,0,0.2,1);
      pointer-events: none;
    }
    #nav-bar.nav-active {
      animation: nav-shimmer 1.4s linear infinite;
    }
    #nav-bar.nav-done {
      width: 100% !important;
      opacity: 0;
      transition: width 0.18s ease, opacity 0.4s ease 0.15s;
    }
    @keyframes nav-shimmer {
      0%   { background-position: 200% 0; }
      100% { background-position: -200% 0; }
    }

    /* ── Ambient background orbs ──
       Static radial gradients — no animation. The perpetual orb-drift
       animation used to pin the compositor and starve OS-native <select>
       popups of a stable frame (reported as multi-second dropdown lag on
       heavy pages). Reintroduce the animation ONLY behind
       prefers-reduced-motion: no-preference if you want the movement back
       on operator machines that can afford it. */
    body::before {
      content: '';
      position: fixed;
      pointer-events: none;
      z-index: 0;
      inset: 0;
      background:
        radial-gradient(ellipse 560px 380px at 12% 88%, rgba(79,142,247,0.028) 0%, transparent 70%),
        radial-gradient(ellipse 420px 300px at 88% 14%, rgba(91,141,224,0.022) 0%, transparent 70%),
        radial-gradient(ellipse 320px 200px at 50% 50%, rgba(201,168,76,0.009) 0%, transparent 70%);
    }
    [data-theme="light"] body::before {
      background:
        radial-gradient(ellipse 560px 380px at 12% 88%, rgba(37,99,235,0.04) 0%, transparent 70%),
        radial-gradient(ellipse 420px 300px at 88% 14%, rgba(40,86,168,0.03) 0%, transparent 70%),
        radial-gradient(ellipse 320px 200px at 50% 50%, rgba(122,90,24,0.015) 0%, transparent 70%);
    }

    /* Utility */
    .dim { color: var(--dim); }
    .em  { color: var(--emerald); }
    .gold { color: var(--gold); }

    /* ═══════════════════════════════════════════
       LAYOUT
    ═══════════════════════════════════════════ */
    .app-shell { display: flex; min-height: 100vh; }

    /* ═══════════════════════════════════════════
       SIDEBAR
    ═══════════════════════════════════════════ */
    .sidebar {
      width: 240px;
      /* No min-height: position:fixed with top:0/bottom:0 sizes it to the
         VISIBLE viewport. 100vh is the large viewport on Chrome Android /
         Samsung Internet, which pushed the footer (Sign out) off-screen on
         tablets whenever the browser bars were showing. */
      background: var(--surface);
      border-right: 1px solid var(--border);
      display: flex;
      flex-direction: column;
      position: fixed;
      top: 0; left: 0; bottom: 0;
      z-index: 100;
      box-shadow: 2px 0 20px rgba(0,0,0,0.4);
      transition: transform 240ms cubic-bezier(.16,1,.3,1);
    }

    /* ── Sidebar collapse ────────────────────────────────────────────── */
    body.sidebar-collapsed .sidebar {
      transform: translateX(-100%);
    }
    body.sidebar-collapsed .main {
      margin-left: 0 !important;
    }

    /* ── Onboarding tour ─────────────────────────────────────────────── */
    .tour-backdrop {
      position: fixed; inset: 0; z-index: 9000;
      /* Atmospheric blur + dim on the rest of the page. The spotlight
         area is cut out via a dynamic clip-path (set in JS) so the
         highlighted content stays sharp and bright while everything
         else stays softly blurred. */
      background: rgba(0,0,0,.45);
      backdrop-filter: blur(2px);
      -webkit-backdrop-filter: blur(2px);
      opacity: 0; pointer-events: none;
      transition: opacity 200ms ease,
                  clip-path 280ms cubic-bezier(.16,1,.3,1),
                  -webkit-clip-path 280ms cubic-bezier(.16,1,.3,1);
    }
    .tour-backdrop.is-open {
      opacity: 1; pointer-events: auto;
    }
    .tour-spotlight {
      position: fixed; z-index: 9001;
      border: 2px solid var(--emerald);
      border-radius: 10px;
      /* No dimming shadow here — the backdrop's clip-path cuts out this
         exact area, so the spotlight content is naturally bright and
         sharp. This is just the emerald glow that draws the eye. */
      box-shadow: 0 0 28px rgba(79,142,247,.55),
                  inset 0 0 0 1px rgba(255,255,255,.05);
      pointer-events: none;
      transition: top 280ms cubic-bezier(.16,1,.3,1),
                  left 280ms cubic-bezier(.16,1,.3,1),
                  width 280ms cubic-bezier(.16,1,.3,1),
                  height 280ms cubic-bezier(.16,1,.3,1);
      opacity: 0;
    }
    .tour-spotlight.is-on { opacity: 1; }
    .tour-tooltip {
      position: fixed; z-index: 9002;
      background: var(--surface);
      border: 1px solid var(--emerald);
      border-radius: 12px;
      padding: 20px 22px 16px;
      width: 360px;
      max-width: calc(100vw - 32px);
      box-shadow: 0 18px 50px rgba(0,0,0,.55),
                  0 0 0 1px rgba(79,142,247,.18);
      opacity: 0;
      transform: scale(.96);
      transition: opacity 200ms, transform 200ms;
    }
    .tour-tooltip.is-open { opacity: 1; transform: scale(1); }
    .tour-tooltip.is-centered {
      top: 50% !important; left: 50% !important;
      transform: translate(-50%, -50%) scale(.96) !important;
    }
    .tour-tooltip.is-centered.is-open {
      transform: translate(-50%, -50%) scale(1) !important;
    }
    .tour-tooltip-step {
      font-size: 10px;
      font-weight: 700;
      letter-spacing: .14em;
      text-transform: uppercase;
      color: var(--emerald);
      margin-bottom: 6px;
    }
    .tour-tooltip-title {
      font-size: 18px;
      font-weight: 700;
      letter-spacing: -.01em;
      color: var(--text);
      margin-bottom: 8px;
      line-height: 1.2;
    }
    .tour-tooltip-body {
      font-size: 13px;
      color: var(--text-mid);
      line-height: 1.55;
      margin-bottom: 16px;
    }
    .tour-tooltip-footer {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-top: 12px;
    }
    .tour-progress {
      display: flex; gap: 5px;
      flex: 1;
    }
    .tour-progress-dot {
      width: 6px; height: 6px; border-radius: 50%;
      background: var(--border);
    }
    .tour-progress-dot.is-current { background: var(--emerald); }
    .tour-progress-dot.is-past    { background: var(--emerald-dim); }
    .tour-btn {
      background: transparent;
      border: 1px solid var(--border);
      color: var(--text-mid);
      padding: 7px 14px;
      border-radius: 7px;
      font-size: 12px;
      cursor: pointer;
      font-family: var(--sans);
      transition: all 120ms;
    }
    .tour-btn:hover { color: var(--text); border-color: var(--text-dim); }
    .tour-btn--primary {
      background: var(--emerald);
      color: var(--on-accent);
      border-color: var(--emerald);
      font-weight: 600;
    }
    .tour-btn--primary:hover { background: var(--emerald); filter: brightness(1.1); }
    .tour-skip {
      position: absolute;
      top: 10px; right: 14px;
      background: none;
      border: none;
      color: var(--text-dim);
      font-size: 11px;
      cursor: pointer;
      padding: 4px;
    }
    .tour-skip:hover { color: var(--text); }

    /* "Restart tour" link inside help panel */
    .help-restart-tour {
      margin-top: 14px;
      padding-top: 12px;
      border-top: 1px solid var(--border);
      font-size: 11px;
      color: var(--text-dim);
    }
    .help-restart-tour a, .help-restart-tour .as-link {
      color: var(--emerald);
      text-decoration: none;
      cursor: pointer;
    }
    .help-restart-tour a:hover,
    .help-restart-tour .as-link:hover { text-decoration: underline; }

    /* ── Sidebar mini (icon-only) mode ───────────────────────────────────
       All rules below scoped with :not([data-nav="top"]) on <html> so they
       don't leak into the top-nav dropdown layout — sidebar-mini and
       top-nav are mutually exclusive presentation modes. */
    html:not([data-nav="top"]) body.sidebar-mini .sidebar { width: 60px !important; }
    html:not([data-nav="top"]) body.sidebar-mini .main    { margin-left: 60px !important; }
    html:not([data-nav="top"]) body.sidebar-mini .sidebar-context,
    html:not([data-nav="top"]) body.sidebar-mini .nav-label,
    html:not([data-nav="top"]) body.sidebar-mini .nav-sublabel,
    html:not([data-nav="top"]) body.sidebar-mini .nav-pinned-title,
    html:not([data-nav="top"]) body.sidebar-mini .sidebar-secure,
    html:not([data-nav="top"]) body.sidebar-mini .sidebar-legal { display: none !important; }
    /* .sidebar-footer and .nav-search-wrap used to be in that hide list, which
       took the whole account block with them — avatar, profile link, the nav
       and theme toggles and the Sign out form — plus the only visible search
       entry point. In compact mode the operator could not sign out, could not
       switch theme, could not reach their profile and had no search button
       without leaving compact mode first. All of it fits a 60px rail as icons,
       which the phone block already proves: it goes out of its way to un-hide
       this same footer, for this same reason. */
    html:not([data-nav="top"]) body.sidebar-mini .sidebar-footer {
      padding: 8px 0 10px;
    }
    html:not([data-nav="top"]) body.sidebar-mini .sidebar-user {
      flex-direction: column; align-items: center; gap: 8px;
    }
    html:not([data-nav="top"]) body.sidebar-mini .sidebar-user-info {
      justify-content: center;
    }
    /* Name, role and last-login have nowhere to go at 60px; the avatar stays
       as the profile link and carries the title. */
    html:not([data-nav="top"]) body.sidebar-mini .sidebar-user-info > div:not(.user-avatar) {
      display: none !important;
    }
    html:not([data-nav="top"]) body.sidebar-mini .sidebar-user > div {
      flex-direction: column !important; gap: 6px !important; align-items: center !important;
      justify-content: center !important;
    }
    html:not([data-nav="top"]) body.sidebar-mini .btn-logout {
      width: 38px; min-width: 0; padding: 0; font-size: 0;
      display: flex; align-items: center; justify-content: center; min-height: 34px;
    }
    html:not([data-nav="top"]) body.sidebar-mini .btn-logout::after {
      content: '\21aa'; font-size: 14px; line-height: 1;
    }
    /* The search trigger keeps its magnifier and drops the label and the
       keyboard hint. */
    html:not([data-nav="top"]) body.sidebar-mini .nav-search-wrap { padding: 6px 0 2px !important; }
    html:not([data-nav="top"]) body.sidebar-mini .nav-search-btn {
      justify-content: center; padding: 8px 0 !important;
    }
    html:not([data-nav="top"]) body.sidebar-mini .nav-search-txt,
    html:not([data-nav="top"]) body.sidebar-mini .nav-search-kbd { display: none !important; }
    /* .nav-active-dot was never in the hide list, so with space-between still
       in force it pinned a 5px glow to the right edge of the rail. */
    html:not([data-nav="top"]) body.sidebar-mini .nav-active-dot { display: none !important; }
    /* .sidebar-logo stays — see the mini rules on .brand-lockup — but it loses
       the 22px rail inset that would push a centred mark off to one side. */
    html:not([data-nav="top"]) body.sidebar-mini .sidebar-logo {
      padding: 12px 0 10px !important;
      /* .brand-lockup is inline-flex, so it shrink-wraps its mark and its own
         justify-content has nothing to distribute — the centring has to happen
         on the wrapper. */
      display: flex !important;
      justify-content: center;
    }
    html:not([data-nav="top"]) body.sidebar-mini .nav-link { padding: 8px 0 !important; justify-content: center; }
    html:not([data-nav="top"]) body.sidebar-mini .nav-link-text { display: none !important; }
    html:not([data-nav="top"]) body.sidebar-mini .nav-icon { font-size: 17px; margin: 0 !important; }
    html:not([data-nav="top"]) body.sidebar-mini .nav-pin-btn { display: none !important; }
    html:not([data-nav="top"]) body.sidebar-mini .nav-section-chevron { display: none !important; }
    html:not([data-nav="top"]) body.sidebar-mini .nav-section-header { padding: 8px 0 !important; cursor: default; }
    /* Header link (Intelligence/CRM/RE etc) should STILL navigate in
       mini mode even though the surrounding header is "decorative" */
    html:not([data-nav="top"]) body.sidebar-mini .nav-section-header a[data-stop-collapse] {
      pointer-events: auto !important; cursor: pointer;
    }
    html:not([data-nav="top"]) body.sidebar-mini .nav-pinned-wrap {
      padding: 4px 0 !important; border-bottom-color: var(--row-line) !important;
    }
    html:not([data-nav="top"]) body.sidebar-mini .div-switcher { padding: 4px 6px !important; }
    html:not([data-nav="top"]) body.sidebar-mini .div-switcher a { font-size: 0 !important; padding: 4px 0 !important; }
    html:not([data-nav="top"]) body.sidebar-mini .div-switcher a::first-letter { font-size: 11px !important; }

    /* ── Pinned tier ──────────────────────────────────────────────────── */
    .nav-pinned-wrap {
      padding: 10px 8px 4px;
      border-bottom: 1px solid var(--border);
      /* Use the accent-aware glow variable so this tracks light/dark
         theme and the user-selected accent color (rather than a
         hardcoded blue that disagrees with amber/rose/violet accents). */
      background: var(--emerald-glow);
    }
    .nav-pinned-title {
      font-size: 9px;
      letter-spacing: .14em;
      text-transform: uppercase;
      color: var(--text-dim);
      padding: 0 6px 6px;
      display: flex;
      align-items: center;
      gap: 6px;
    }
    .nav-pinned-title-icon { color: var(--gold); }

    /* ── Section header (collapsible) ────────────────────────────────── */
    .nav-section-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 10px 14px 4px;
      cursor: pointer;
      user-select: none;
      transition: background 120ms;
    }
    /* A module boundary: a hairline and real air above every section header
       that follows another section's items. Not on the first one. */
    .nav-section-items + .nav-section-header,
    .nav-pinned-wrap + .nav-section-header {
      border-top: 1px solid var(--border);
      margin-top: 14px;
    }
    .nav-section-header:hover { background: var(--emerald-glow); }
    /* The chevron is a real button now — it used to be a <div> with a click
       handler, so it was not focusable, not operable from a keyboard, and
       announced nothing about the section being open or shut. */
    .nav-section-toggle {
      appearance: none; background: transparent; border: 0;
      /* A 9px glyph in a 13px-wide button read as a full stop — you could not
         see which way it pointed, let alone aim at it. The whole header
         toggles, so this is mostly an indicator, but an indicator nobody can
         see is not one, and it is also the only part of the header a keyboard
         ever lands on. */
      min-width: 28px; min-height: 28px; padding: 0 4px; margin-right: -4px;
      cursor: pointer; color: inherit; font: inherit; line-height: 1;
      display: flex; align-items: center; justify-content: center; gap: 6px;
      border-radius: 6px;
      transition: background var(--transition), color var(--transition);
    }
    .nav-section-header:hover .nav-section-chevron { color: var(--text); }
    .nav-section-toggle:hover { background: var(--wash); }
    .nav-section-toggle:focus-visible {
      outline: 2px solid var(--focus); outline-offset: 1px;
    }

    /* Shared dismiss glyph. Every other × in the shell — the flash, the toast,
       the help panel — has a hover colour and an accessible name; the
       shortcuts sheet's was an unstyled inline <button> announced as
       "multiplication sign, button". */
    .icon-close {
      appearance: none; background: none; border: 0; padding: 2px 6px;
      color: var(--text-dim); font-size: 18px; line-height: 1; cursor: pointer;
      border-radius: 6px;
      transition: color var(--transition), background var(--transition);
    }
    .icon-close:hover { color: var(--text); background: var(--wash); }
    .icon-close:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }

    /* A button that has to read as a link: keyboard-operable, sitting inline
       in a sentence. Used where an <a> without an href was standing in for
       one — styled like a link, reachable by nobody but a mouse. */
    .as-link {
      appearance: none; background: none; border: 0; padding: 0;
      font: inherit; color: var(--emerald); cursor: pointer;
      text-decoration: none;
    }
    .as-link:hover { text-decoration: underline; }
    .as-link:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 3px; }
    .nav-section-header .nav-label {
      padding: 0 !important;
      margin: 0 !important;
    }
    /* Drawn, not typed. U+25BE inks about a quarter of its own em box in the
       font that actually resolves here, so font-size buys almost nothing:
       going 9px -> 15px took the rendered mark from 4x3 px to 6x4. A border
       triangle is the size you ask for. The ▾ in the markup stays as the
       accessible fallback and is zeroed out here. */
    .nav-section-chevron {
      width: 0; height: 0;
      font-size: 0; line-height: 0;
      border-left: 5px solid transparent;
      border-right: 5px solid transparent;
      border-top: 6px solid currentColor;
      color: var(--text-dim);
      transition: transform 180ms, color var(--transition);
    }
    .nav-section-header.is-collapsed .nav-section-chevron {
      transform: rotate(-90deg);
    }
    /* A 0x0 box with borders has no content box to centre, so the flex
       centring above lands it 3px low without this. */
    .nav-section-toggle .nav-section-chevron { margin-top: 3px; }
    .nav-section-items {
      max-height: 600px;
      overflow: hidden;
      transition: max-height 220ms ease-out;
    }
    .nav-section-items.is-collapsed {
      max-height: 0;
    }
    .nav-active-dot {
      width: 5px; height: 5px;
      border-radius: 50%;
      background: var(--emerald);
      box-shadow: 0 0 6px var(--emerald);
    }

    /* ── Pin button on each nav link ─────────────────────────────────── */
    .nav-pin-btn {
      position: absolute;
      right: 10px;
      top: 50%;
      transform: translateY(-50%);
      background: transparent;
      border: none;
      color: var(--text-dim);
      opacity: 0;
      cursor: pointer;
      font-size: 15px;
      line-height: 1;
      padding: 2px 4px;
      border-radius: 4px;
      transition: opacity 120ms, color 120ms, background 120ms;
    }
    .nav-link:hover .nav-pin-btn { opacity: 0.7; }
    /* opacity:0 composites the element AND its outline, so the global focus
       ring was painted and then made transparent: a keyboard user tabbing the
       rail crossed ~40 stops where the indicator vanished entirely and nothing
       on screen moved. Reveal on focus like every other hover-only control in
       the sheet does. */
    .nav-pin-btn:focus-visible {
      opacity: 1;
      outline: 2px solid var(--focus);
      outline-offset: 1px;
    }
    .nav-pin-btn:hover {
      color: var(--gold);
      background: rgba(218,165,32,.10);
      opacity: 1 !important;
    }
    .nav-link.is-pinned .nav-pin-btn {
      opacity: 1;
      color: var(--gold);
    }

    /* ── Pin drag-to-reorder ─────────────────────────────────────────
       Grip icon hides until hover to keep the sidebar clean. Cursor
       flips to grab/grabbing so the affordance is obvious the moment
       the pointer touches the item. The dragging item goes semi-
       transparent; the drop target gets a top border to show where
       the drop will land. */
    .nav-pin-grip {
      display: inline-block;
      color: var(--text-dim);
      font-size: 11px;
      letter-spacing: -1px;
      opacity: 0;
      margin-right: 4px;
      cursor: grab;
      transition: opacity 120ms;
      user-select: none;
    }
    .nav-link.is-pinned:hover .nav-pin-grip { opacity: 0.55; }
    .nav-link.is-pinned.dragging {
      opacity: 0.45;
      cursor: grabbing;
    }
    .nav-link.is-pinned.drop-target {
      box-shadow: inset 0 2px 0 0 var(--gold);
    }
    .nav-link.is-pinned.drop-target-below {
      box-shadow: inset 0 -2px 0 0 var(--gold);
    }
    /* Hide grip in top-nav mode + sidebar-mini (icons only, no drag) */
    [data-nav="top"] .nav-pin-grip { display: none !important; }
    html:not([data-nav="top"]) body.sidebar-mini .nav-pin-grip { display: none !important; }

    /* ── Mini-mode toggle (near sidebar-toggle button) ───────────────── */
    /* Both floating handles ride the sidebar's right border at a fixed
       `sidebar-width − 6px`, vertically centred on the viewport.
       Two things had them landing on top of page content:
       1. They were positioned PAST the border (left:250 against a 240px
          sidebar), i.e. 10px inside `.main` — so they covered the top-left
          breadcrumb chip / page title and ate clicks meant for them.
       2. They sat in the top band (top:18/52px), which is exactly where
          `.asset-sticky` lives — and that bar carries `margin:0 -28px` to
          bleed edge-to-edge, reclaiming the very gutter a sidebar-edge
          handle needs. No horizontal offset can win against a full-bleed
          element, so the handles leave the top band entirely.
       Mid-viewport, the only neighbours are ordinary cards, which respect
       `.main`'s padding — ≥6px of clear gutter in every density, and 2px
       inside the sidebar off the nav links' 232px right edge. */
    .sidebar-mini-toggle {
      position: fixed;
      top: calc(50vh + 21px);   /* stacked under .sidebar-toggle, mid-viewport */
      left: 234px;              /* 240 sidebar − 6 */
      width: 22px;
      height: 22px;
      border-radius: 50%;
      border: 1px solid var(--border);
      background: var(--surface);
      color: var(--text-dim);
      cursor: pointer;
      z-index: 9998;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      font-size: 11px;
      font-family: var(--mono);
      transition: left 240ms cubic-bezier(.16,1,.3,1), color 120ms, border-color 120ms;
    }
    .sidebar-mini-toggle:hover {
      color: var(--emerald);
      border-color: var(--emerald);
    }
    body.sidebar-mini .sidebar-mini-toggle { left: 54px; }
    body.sidebar-collapsed .sidebar-mini-toggle { display: none; }
    .sidebar-toggle {
      position: fixed;
      top: calc(50vh - 13px);   /* mid-viewport, clear of every page header */
      left: 234px;              /* 240 sidebar − 6 */
      width: 26px;
      height: 26px;
      border-radius: 50%;
      border: 1.5px solid var(--emerald);
      background: var(--emerald);
      color: var(--on-accent);
      cursor: pointer;
      z-index: 9999;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      font-size: 13px;
      font-weight: 700;
      font-family: var(--mono);
      line-height: 1;
      transition: left 240ms cubic-bezier(.16,1,.3,1),
                  transform 120ms;
      box-shadow: 0 2px 8px rgba(0,0,0,.4),
                  0 0 0 3px rgba(79,142,247,.10);
    }
    .sidebar-toggle:hover {
      transform: scale(1.1);
      box-shadow: 0 3px 12px rgba(0,0,0,.5),
                  0 0 0 4px rgba(79,142,247,.16);
    }
    body.sidebar-collapsed .sidebar-toggle {
      left: 12px;
    }
    [data-density="compact"] .sidebar-toggle { left: 210px; }   /* 216 − 6 */
    [data-density="spacious"] .sidebar-toggle { left: 258px; }  /* 264 − 6 */
    body.sidebar-collapsed[data-density="compact"] .sidebar-toggle,
    body.sidebar-collapsed[data-density="spacious"] .sidebar-toggle {
      left: 12px;
    }
    /* Mini-mode tracks the 60px sidebar edge for BOTH toggle buttons */
    body.sidebar-mini .sidebar-toggle { left: 54px; }           /* 60 − 6 */
    [data-density="compact"] .sidebar-mini-toggle { left: 210px; }
    [data-density="spacious"] .sidebar-mini-toggle { left: 258px; }
    body.sidebar-mini[data-density="compact"] .sidebar-toggle,
    body.sidebar-mini[data-density="compact"] .sidebar-mini-toggle,
    body.sidebar-mini[data-density="spacious"] .sidebar-toggle,
    body.sidebar-mini[data-density="spacious"] .sidebar-mini-toggle {
      left: 54px;
    }
    /* Collapsed: there's no sidebar edge left to ride, so the handle parks at
       the viewport edge — reserve a gutter for it instead of letting the page
       slide underneath. Costs 20px of the 240px collapsing just won back.
       Scoped away from top-nav mode, where the handle isn't rendered at all. */
    html:not([data-nav="top"]) body.sidebar-collapsed .main { padding-left: 48px; }
    /* NB: data-density lives on <html>, not <body> — the older
       `body.sidebar-collapsed[data-density=…]` compounds nearby are dead
       selectors that happen to be harmless (plain specificity already lands
       them on the right value). Don't copy that shape. */
    html:not([data-nav="top"])[data-density="spacious"] body.sidebar-collapsed .main { padding-left: 52px; }
    @media (max-width: 900px) {
      .sidebar-toggle { left: 12px; }
    }
    /* Sidebar top stripe — rebuilt as a compositor-safe transform.
       Previously animated `background-position`, which forces a paint
       every frame on the sidebar (never idle → OS-native <select> popups
       elsewhere on the page had to wait for a stable frame → dropdown
       lag). Now the stripe is drawn 2× wide and slid left/right via
       `translateX` — GPU compositor only, zero paint. Gated behind
       prefers-reduced-motion so operators who prefer stillness get it. */
    .sidebar::before {
      content: '';
      position: absolute;
      top: 0; left: 0;
      width: 200%;
      height: 2px;
      background: linear-gradient(90deg, transparent 0%, var(--emerald) 40%, #7aaef9 55%, var(--emerald) 70%, transparent 100%);
      transform: translateX(0);
      opacity: 0.95;
      will-change: transform;
    }
    @media (prefers-reduced-motion: no-preference) {
      .sidebar::before {
        animation: sidebar-stripe 4s ease-in-out infinite alternate;
      }
    }
    @keyframes sidebar-stripe {
      0%   { transform: translateX(0);     opacity: 0.9; }
      100% { transform: translateX(-50%);  opacity: 1;   }
    }
    /* Ambient glow behind sidebar — kept, but ~3× slower (18s vs 6s) and
       gated behind reduced-motion so it's not competing for compositor
       time on heavy pages. Still transform+opacity (compositor-safe). */
    .sidebar::after {
      content: '';
      position: absolute;
      top: 60px; left: -30px;
      width: 100px; height: 100px;
      border-radius: 50%;
      background: radial-gradient(circle, rgba(79,142,247,0.12) 0%, transparent 70%);
      pointer-events: none;
      opacity: 0.85;
    }
    @media (prefers-reduced-motion: no-preference) {
      .sidebar::after {
        animation: sidebar-orb 18s ease-in-out infinite alternate;
      }
    }
    @keyframes sidebar-orb {
      0%   { opacity: 0.7; transform: translateY(0) scale(1); }
      100% { opacity: 1;   transform: translateY(20px) scale(1.15); }
    }

    /* Context bar */
    .sidebar-context {
      padding: 9px 22px;
      background: rgba(79,142,247,0.04);
      border-bottom: 1px solid var(--border);
      display: flex;
      align-items: center;
      gap: 6px;
    }
    .sidebar-context-dot {
      width: 4px; height: 4px;
      border-radius: 50%;
      background: var(--emerald-dim);
      flex-shrink: 0;
    }
    .sidebar-context-text {
      font-size: 8px;
      font-family: var(--sans);
      letter-spacing: 0.18em;
      text-transform: uppercase;
      /* 8px tracked uppercase is body text, not an accent: --emerald-dim was
         3.49:1 on .sidebar-context's own tinted ground (rgba(79,142,247,.04)
         over --surface); --text-mid is 8.71:1 there. The --emerald-dim dot
         beside it still carries the accent. */
      color: var(--text-mid);
    }

    /* Logo */
    .sidebar-logo {
      padding: 14px var(--rail-pad) 12px;
      border-bottom: 1px solid var(--border);
    }
    .logo-img-wrap {
      background: #fff;
      border-radius: 8px;
      padding: 6px 10px;
      display: inline-block;
      line-height: 0;
      box-shadow: 0 1px 6px rgba(0,0,0,0.35);
    }
    .logo-img {
      width: 156px;
      height: auto;
      display: block;
    }
    .logo-gem {
      font-size: 20px;
      color: var(--emerald);
      margin-bottom: 9px;
      display: block;
      filter: drop-shadow(0 0 10px rgba(79,142,247,0.5));
    }
    .logo-wordmark {
      font-size: 16px;
      font-weight: 700;
      font-family: var(--display);
      letter-spacing: 0.1em;
      text-transform: uppercase;
      color: var(--text);
      line-height: 1;
    }
    .logo-wordmark .em { color: var(--emerald); }
    .logo-tagline {
      font-size: 9px;
      color: var(--text-dim);
      letter-spacing: 0.07em;
      margin-top: 8px;
      display: flex;
      align-items: center;
      gap: 3px;
      line-height: 1;
    }

    /* ═══════════════════════════════════════════
       BRAND LOCKUP — ASSETFLOW [mark] PRO
       Typographic wordmark that replaces the legacy JPG plate in
       the sidebar. Retires the white-rectangle band-aid: type inherits
       from the ink token so it lives on the dark chrome directly.
       Set in var(--display), like every other display-weight string in the
       app. It used to hardcode a Segoe UI Variable Display / system-ui stack,
       on the strength of a note saying --display was "currently set to Cambria
       (broken)" — which stopped being true when --display was re-declared as
       Geist later in this same file. So the app's most prominent piece of type
       was rendering in Segoe on Windows, San Francisco on a Mac and something
       else again on Linux, while everything around it was Geist.

       Between the two halves stands the real mark — templates/_afp_mark.html,
       styled by static/afp-logo.css, cropped to the artwork and sized off the
       lockup's own font-size so it tracks the type at every nav width. It stood
       in as a ◆ from the icon vocabulary for as long as the only logo file was a
       raster with a black plate baked into it.
    ═══════════════════════════════════════════ */
    .brand-lockup {
      display: inline-flex;
      /* centre, not baseline: the mark is a box with no baseline of its own, and
         both words are all-caps, so centring them reads identically. */
      align-items: center;
      gap: 0.44em;
      font-family: var(--display);
      font-weight: 600;
      font-size: 13px;
      letter-spacing: 0.14em;
      text-transform: uppercase;
      color: var(--text);
      line-height: 1;
      white-space: nowrap;
      text-decoration: none;
    }
    /* It is a link to the home dashboard now — every other product's logo is.
       Keep the ink; the mark does the acknowledging. */
    .brand-lockup:hover,
    .brand-lockup:focus-visible { color: var(--text); text-decoration: none; }
    .brand-mark {
      /* 1.62em against 13px caps: tall enough to read as the mark rather than as
         a bullet, short enough that it does not out-shout the wordmark. */
      --afp-mark-h: 1.62em;
      transition: transform var(--transition, .18s ease);
    }
    .brand-lockup:hover .brand-mark { transform: scale(1.06); }
    .brand-lockup:focus-visible {
      outline: 2px solid var(--emerald);
      outline-offset: 3px;
      border-radius: 4px;
    }
    @media (prefers-reduced-motion: reduce) {
      .brand-mark { transition: none; }
      .brand-lockup:hover .brand-mark { transform: none; }
    }

    /* Sidebar-mini + top-nav accommodations for the new lockup */
    /* MINI. The 60px rail used to hide .sidebar-logo outright, so the icon
       rail had no brand at all — nav links started flush against the top edge —
       and these rules, which claimed "words still fit at mini width", could
       never apply because their parent was display:none !important. They now
       do what mini actually wants: drop the two words and keep the mark, which
       is exactly the case _afp_mark.html's mark-only variant exists for. */
    body.sidebar-mini .brand-lockup { gap: 0; justify-content: center; }
    body.sidebar-mini .brand-lockup .rf-word,
    body.sidebar-mini .brand-lockup .v-word { display: none; }
    body.sidebar-mini .brand-mark { --afp-mark-h: 26px; }
    [data-nav="top"] .brand-lockup { font-size: 12px; letter-spacing: 0.12em; }
    .anim-wrap {
      display: inline-flex;
      align-items: center;
      overflow: hidden;
      height: 1em;
      position: relative;
      min-width: 52px;
    }
    .anim-word {
      color: var(--emerald);
      display: block;
      position: absolute;
      top: 0; left: 0;
      white-space: nowrap;
      line-height: 1;
    }
    @keyframes slideUpIn {
      from { transform: translateY(110%); opacity: 0; }
      to   { transform: translateY(0);    opacity: 1; }
    }
    @keyframes slideUpOut {
      from { transform: translateY(0);     opacity: 1; }
      to   { transform: translateY(-110%); opacity: 0; }
    }

    /* Nav */
    .sidebar-nav { padding: 10px 8px; flex: 1; overflow-y: auto; }
    .nav-label {
      font-size: 11px;
      font-weight: 800;
      font-family: var(--sans);
      letter-spacing: 0.18em;
      text-transform: uppercase;
      color: var(--emerald);
      /* Was 0.75, which composited --emerald to #3e6fc1 = 3.80:1 on the
         sidebar's --surface at 11px/800; at opacity 1 it is 5.87:1. The four
         sections whose header is a link carry inline color:inherit, so those
         go from --text at 0.75 (9.43:1) to full --text instead.
         [data-theme="light"] .nav-label re-declares 0.85 and still wins; the
         module variants below re-declare 0.8. */
      opacity: 1;
      padding: 6px 10px 5px;
      /* The divider and the 18px gap used to be declared here. Neither ever
         painted: .nav-label is authored as the FIRST child of
         .nav-section-header in both of its two call sites, so
         `.nav-label:first-child { border-top: none }` (0,2,0) beat it on every
         header in the app, and `.nav-section-header .nav-label { margin: 0
         !important }` killed the gap. Thirteen modules ran together as one
         undifferentiated 40-item list. Both now live on the header, where the
         markup actually is — see .nav-section-items + .nav-section-header. */
      display: flex;
      align-items: center;
      gap: 6px;
    }
    .nav-label::before {
      content: '';
      display: inline-block;
      width: 14px;
      height: 1px;
      background: var(--emerald);
      /* 0.38 = the old 0.5 seen through the parent's former 0.75, so the 14px
         dash keeps the brightness it has today. (In light mode, where
         .nav-label stays at 0.85, it dims from .425 to .323 - a 14x1px
         decorative rule, below the noise floor.) */
      opacity: 0.38;
      flex-shrink: 0;
    }
    .nav-label:first-child { margin-top: 4px; }
    /* Module-specific nav label colors */
    .nav-label.vault-label  { color: var(--vault-color);  opacity: 0.8; }
    .nav-label.vault-label::before  { background: var(--vault-color); }
    .nav-label.ams-label    { color: var(--ams-color);    opacity: 0.8; }
    .nav-label.ams-label::before    { background: var(--ams-color); }
    .nav-label.crm-label    { color: var(--crm-color);    opacity: 0.8; }
    .nav-label.crm-label::before    { background: var(--crm-color); }
    .nav-label.intel-label  { color: var(--intel-color);  opacity: 0.8; }
    .nav-label.intel-label::before  { background: var(--intel-color); }
    .nav-label.re-label     { color: var(--re-color);     opacity: 0.8; }
    .nav-label.re-label::before     { background: var(--re-color); }
    .nav-label.hecm-label   { color: var(--hecm-color);   opacity: 0.8; }
    .nav-label.hecm-label::before   { background: var(--hecm-color); }
    .nav-label.agmt-label   { color: var(--agmt-color);   opacity: 0.8; }
    .nav-label.agmt-label::before   { background: var(--agmt-color); }
    .nav-label.rental-label { color: var(--rental-color); opacity: 0.85; }
    .nav-label.rental-label::before { background: var(--rental-color); }
    .nav-sublabel {
      font-size: 10px;
      font-weight: 700;
      font-family: var(--sans);
      letter-spacing: 0.16em;
      text-transform: uppercase;
      color: var(--text-dim);
      opacity: 0.7;
      padding: 2px 10px 4px;
      margin-top: 8px;
      margin-bottom: 1px;
    }
    .div-badge {
      display: inline-flex;
      align-items: center;
      gap: 5px;
      padding: 3px 9px;
      border-radius: 20px;
      font-size: 9px;
      font-weight: 700;
      letter-spacing: 0.12em;
      text-transform: uppercase;
      margin-bottom: 18px;
    }
    .div-badge.vault  { background: var(--emerald-glow2); color: var(--emerald); border: 1px solid rgba(79,142,247,.2); }
    .div-badge.re     { background: var(--gold-glow2);    color: var(--gold);    border: 1px solid rgba(201,168,76,.25); }
    .div-badge.intel  { background: rgba(160,100,200,.1); color: #a064c8;        border: 1px solid rgba(160,100,200,.2); }
    .div-badge.crm    { background: rgba(100,180,140,.1); color: #64b48c;        border: 1px solid rgba(100,180,140,.2); }
    .div-badge.agmt   { background: rgba(180,130,80,.1);  color: #b48250;        border: 1px solid rgba(180,130,80,.2); }
    .nav-link {
      display: flex;
      align-items: center;
      gap: var(--sp-4);
      padding: var(--sp-3) var(--sp-4) var(--sp-3) var(--sp-5);
      border-radius: 7px;
      color: var(--text-mid);
      text-decoration: none;
      font-size: var(--fs-5);
      font-weight: 500;
      letter-spacing: 0.01em;
      transition: background 0.14s ease, color 0.14s ease, border-color 0.14s ease;
      border-left: 3px solid transparent;
      margin-bottom: 1px;
      position: relative;
    }
    .nav-link:hover {
      background: var(--emerald-glow);
      color: var(--text);
      border-left-color: rgba(79,142,247,0.35);
    }
    .nav-link.active {
      background: linear-gradient(90deg, var(--emerald-glow2) 0%, rgba(79,142,247,0.03) 100%);
      color: var(--text);
      border-left-color: var(--emerald);
      font-weight: 650;
    }
    .nav-icon {
      font-size: 14px;
      width: 19px;
      text-align: center;
      opacity: 0.55;
      font-family: var(--mono);
      flex-shrink: 0;
      transition: opacity 0.14s ease;
    }
    .nav-link:hover .nav-icon { opacity: 0.8; }
    .nav-link.active .nav-icon { opacity: 1; color: var(--emerald); }
    .nav-badge {
      margin-left: auto;
      background: var(--surface3);
      border: 1px solid var(--border);
      color: var(--text-dim);
      font-family: var(--mono);
      font-size: 9px;
      padding: 1px 7px;
      border-radius: 10px;
      font-weight: 500;
    }
    .nav-link.active .nav-badge {
      background: var(--emerald-glow);
      border-color: rgba(79,142,247,0.25);
      color: var(--emerald);
    }

    /* Security strip */
    .sidebar-secure {
      padding: 8px 22px;
      display: flex;
      align-items: center;
      gap: 7px;
      font-size: 8px;
      font-family: var(--mono);
      letter-spacing: 0.18em;
      text-transform: uppercase;
      /* Same call as .sidebar-context-text: 8px label, not an accent.
         --emerald-dim was 3.64:1 on --surface, --text-mid is 9.09:1.
         The pulsing .secure-dot keeps the emerald. */
      color: var(--text-mid);
      border-top: 1px solid var(--border);
    }
    .secure-dot {
      width: 5px; height: 5px;
      border-radius: 50%;
      background: var(--emerald);
      animation: secPulse 2.4s ease-in-out infinite;
      flex-shrink: 0;
    }
    @keyframes secPulse {
      0%, 100% { opacity: 1; box-shadow: 0 0 6px var(--emerald); }
      50%       { opacity: 0.4; box-shadow: none; }
    }

    /* Sidebar footer */
    .sidebar-legal {
      margin-top: 10px;
      padding-top: 10px;
      /* Was white-alpha: literally invisible on the light theme's white rail,
         while the five other dividers in the shell all use the token. */
      border-top: 1px solid var(--border);
      display: flex;
      align-items: center;
      gap: 6px;
      font-size: 10px;
      color: var(--text-dim);
      letter-spacing: .02em;
      font-family: var(--mono);
    }
    .sidebar-legal a {
      color: var(--text-dim);
      text-decoration: none;
      transition: color var(--transition);
    }
    .sidebar-legal a:hover { color: var(--gold); }
    .sidebar-legal .sep   { opacity: .4; }
    .sidebar-legal .copy  { opacity: .7; }

    .sidebar-footer {
      padding: 12px 16px 14px;
      border-top: 1px solid var(--border);
    }
    .sidebar-user {
      display: flex;
      flex-direction: column;
      gap: 8px;
    }
    .sidebar-user-info { display: flex; align-items: center; gap: 8px; text-decoration: none; }
    /* Without this the flex item refuses to shrink and the ellipsis never
       fires at the right place. */
    .sidebar-user-info > div { min-width: 0; }
    .user-avatar {
      width: 30px; height: 30px;
      border-radius: 50%;
      background: var(--emerald-glow2);
      border: 1px solid rgba(79,142,247,0.25);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 12px;
      color: var(--emerald);
      font-weight: 700;
      font-family: var(--mono);
      flex-shrink: 0;
    }
    /* max-width:90px used to cap these two. .sidebar-user is a column, so the
       name is not competing with the Sign out button — it has ~170px of the
       footer to itself, and the cap was throwing away 80px of it and
       ellipsising an ordinary username at about nine characters. The
       overflow/ellipsis below still truncate, just at the real edge. */
    .user-name {
      font-size: 12px;
      font-weight: 600;
      color: var(--text-mid);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    .user-role {
      font-size: 9px;
      color: var(--text-dim);
      font-family: var(--mono);
      letter-spacing: 0.06em;
    }
    .user-last-login {
      font-size: 8px;
      color: var(--text-dim);
      font-family: var(--mono);
      letter-spacing: 0.04em;
      margin-top: 1px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .btn-logout {
      background: transparent;
      border: 1px solid var(--border);
      border-radius: var(--radius-sm);
      color: var(--text-dim);
      font-size: 9px;
      font-weight: 700;
      letter-spacing: 0.08em;
      text-transform: uppercase;
      font-family: var(--sans);
      padding: 4px 9px;
      cursor: pointer;
      transition: all var(--transition);
      flex-shrink: 0;
    }
    .btn-logout:hover { border-color: var(--red); color: var(--red); }

    /* ═══════════════════════════════════════════
       MAIN CONTENT
    ═══════════════════════════════════════════ */
    .main {
      margin-left: 240px;
      flex: 1;
      /* The help button is position:fixed at bottom:24/right:24 in EVERY
         presentation, but the clearance for it lived inside the 900px block —
         so on a desktop it sat on top of the last row of every long table and
         ate the clicks meant for it. 76px is the 40px button plus its 24px
         inset plus a hair. */
      padding: 22px 28px 76px;
      min-width: 0;
    }

    /* ═══════════════════════════════════════════
       TOPBAR
    ═══════════════════════════════════════════ */
    .topbar {
      display: flex;
      align-items: center;
      justify-content: space-between;
      /* 28px is layout-level and already has a [data-density] override;
         only the intra-bar gap is component-level. */
      margin-bottom: 28px;
      gap: var(--sp-7);
    }
    .page-title {
      font-size: var(--fs-7);
      font-weight: 800;
      font-family: var(--display);
      color: var(--text);
      letter-spacing: -0.02em;
      position: relative;
      display: flex;
      align-items: center;
      gap: 10px;
    }
    /* Gold-blue accent bar left of every page title */
    .page-title::before {
      content: '';
      display: inline-block;
      width: 4px;
      min-height: 28px;
      align-self: stretch;
      background: linear-gradient(180deg, var(--emerald) 0%, var(--gold) 100%);
      border-radius: 2px;
      flex-shrink: 0;
    }
    .page-title span { color: var(--emerald); }
    .page-title .dim { color: var(--text-mid); font-weight: 600; }
    .page-subtitle {
      font-size: var(--fs-3);
      color: var(--text-dim);
      font-family: var(--mono);
      letter-spacing: 0.04em;
      /* 3px is off the spacing ramp and --sp-1 is 4px, so tokenising this
         would make every page's topbar 1px taller. Left raw, same call as
         the .badge 3px and the .btn-sm 5px/13px. */
      margin-top: 3px;
    }

    /* Animated underline rule below topbar on main pages */
    .topbar::after {
      content: '';
      display: block;
      position: absolute;
      left: 0; right: 0;
      bottom: -14px;
      height: 1px;
      background: linear-gradient(90deg, var(--emerald-glow2), transparent 60%);
      pointer-events: none;
    }
    .topbar { position: relative; }

    /* ═══════════════════════════════════════════
       SEARCH
    ═══════════════════════════════════════════ */
    .search-form { display: flex; gap: 8px; }
    .search-input {
      background: var(--surface2);
      border: 1px solid var(--border);
      border-radius: var(--radius);
      color: var(--text);
      font-family: var(--mono);
      font-size: 12px;
      padding: 8px 14px;
      width: 240px;
      outline: none;
      transition: border-color var(--transition), box-shadow var(--transition);
    }
    .search-input:focus {
      border-color: var(--emerald-dim);
      box-shadow: 0 0 0 3px var(--emerald-glow);
    }
    .search-input::placeholder { color: var(--text-dim); }

    /* ═══════════════════════════════════════════
       BUTTONS
    ═══════════════════════════════════════════ */
    .btn {
      display: inline-flex;
      align-items: center;
      gap: var(--sp-2);
      /* 18px horizontal is layout-level and stays raw for now; button height
         is the part that has to move with density. */
      padding: var(--sp-3) 18px;
      border-radius: var(--radius);
      font-family: var(--sans);
      font-size: var(--fs-3);
      font-weight: 700;
      letter-spacing: 0.04em;
      cursor: pointer;
      /* A transparent border, not none: .btn-ghost and .btn-danger each add a
         1px border, so the filled primary rendered 2px shorter and 2px
         narrower than the Cancel button sitting next to it in every action
         row. Same box for every variant. */
      border: 1px solid transparent;
      text-decoration: none;
      transition: background 0.16s cubic-bezier(0.16, 1, 0.3, 1),
                  box-shadow 0.16s cubic-bezier(0.16, 1, 0.3, 1),
                  transform 0.16s cubic-bezier(0.34, 1.56, 0.64, 1),
                  border-color 0.16s cubic-bezier(0.16, 1, 0.3, 1),
                  filter 0.16s cubic-bezier(0.16, 1, 0.3, 1);
      white-space: nowrap;
      background: linear-gradient(135deg, var(--btn-fill-a) 0%, var(--btn-fill-b) 100%);
      color: #ffffff;
      position: relative;
      overflow: hidden;
    }
    .btn::after {
      content: '';
      position: absolute;
      inset: 0;
      background: linear-gradient(135deg, rgba(255,255,255,0.18) 0%, transparent 60%);
      opacity: 0;
      transition: opacity 0.2s ease;
      pointer-events: none;
    }
    /* ::after is redefined further down as a click ripple
       (rgba(255,255,255,.12), scale .85 -> 1 on :active). This hover rule kept
       firing that ripple on hover, washing the primary button ~12% lighter and
       dropping the white label to 3.71:1 even after the fill was darkened.
       Hover feedback is now brightness(1.07) + the glow below, plus the
       translateY(-2px) in the later block. Gold keeps the sheen: its label is
       #070c15, which the wash only helps. */
    .btn-gold:hover::after { opacity: 1; }
    .btn:hover {
      box-shadow: 0 0 22px rgba(79,142,247,0.30), 0 2px 10px rgba(0,0,0,0.35);
      filter: brightness(1.07);
    }
    .btn:active {
      transform: scale(0.96);
      box-shadow: none;
      filter: brightness(0.94);
      transition: transform 80ms cubic-bezier(0.4, 0, 0.2, 1),
                  filter 80ms cubic-bezier(0.4, 0, 0.2, 1);
    }

    .btn-gold {
      background: linear-gradient(135deg, var(--gold) 0%, var(--gold-grad2) 100%);
      color: var(--on-gold);
    }
    .btn-gold:hover { box-shadow: 0 0 24px rgba(201,168,76,0.35); filter: brightness(1.1); }

    .btn-ghost {
      background: transparent;
      border: 1px solid var(--border);
      color: var(--text-mid);
    }

    /* DISABLED. The double-submit guard in app.js disables the submit button on
       every POST in the app, and until now the button kept its full-strength
       gradient, its white 700-weight label and cursor:pointer — only the word
       changed to "Processing…". Statically disabled buttons (tasks/import,
       re_ts_import_preview, intelligence/upload) looked 100% live. Three call
       sites had worked around the gap by hand at three different opacities, so
       a disabled control read at 35%, 45%, 50% or 100% depending on the page.
       One treatment, and it also covers readonly inputs, which had none. */
    .btn:disabled, .btn[aria-disabled="true"],
    button:disabled, input:disabled, select:disabled, textarea:disabled {
      opacity: .45;
      cursor: not-allowed;
      box-shadow: none;
    }
    .btn:disabled::after, .btn[aria-disabled="true"]::after { opacity: 0; }
    input:read-only:not([type="checkbox"]):not([type="radio"]),
    textarea:read-only {
      background: var(--surface);
      color: var(--text-mid);
      cursor: default;
    }
    .btn-ghost::after { display: none; }

    .btn-danger {
      /* Was transparent/transparent at 11px — lighter and smaller than the
         Cancel button next to it, and it only resolved into a button on
         hover, which never happens on touch. A destructive action should
         read as one at rest. Size now matches .btn-ghost. */
      background: rgba(224,88,88,0.06);
      border: 1px solid rgba(224,88,88,0.38);
      color: var(--red);
      font-size: var(--fs-3);
      padding: var(--sp-3) 18px;
    }
    .btn-danger.btn-sm { font-size: var(--fs-2); padding: 5px 13px; }
    .btn-danger::after { display: none; }
    .btn-danger:active { transform: scale(0.97); }

    .btn-sm { padding: 5px 13px; font-size: var(--fs-2); }

    /* ═══════════════════════════════════════════
       CARDS
    ═══════════════════════════════════════════ */
    .card {
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: var(--radius-lg);
      /* 20px horizontal is layout-level and stays raw until there is an
         --sp-8; the vertical rhythm is what the density toggle should own.
         Padding is still overridden by [data-density] .card (0,2,0), but
         margin-bottom is not, so the card gap DOES move with density. */
      padding: var(--sp-7) 20px;
      margin-bottom: var(--sp-5);
      transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.18s ease;
      box-shadow: var(--shadow-card);
      position: relative;
      overflow: hidden;
    }
    /* Subtle top edge highlight */
    .card::before {
      content: '';
      position: absolute;
      top: 0; left: 0; right: 0;
      height: 1px;
      background: linear-gradient(90deg, transparent 0%, rgba(79,142,247,0.18) 40%, rgba(201,168,76,0.12) 60%, transparent 100%);
      transition: opacity 0.25s ease;
    }
    .card:hover {
      border-color: var(--border-bright);
      box-shadow: var(--card-hover-shadow), 0 0 0 1px rgba(79,142,247,0.15);
      transform: translateY(-1px);
    }
    .card:hover::before {
      background: linear-gradient(90deg, transparent 0%, rgba(79,142,247,0.32) 40%, rgba(201,168,76,0.18) 60%, transparent 100%);
    }

    /* ═══════════════════════════════════════════
       STAT CARDS
    ═══════════════════════════════════════════ */
    .stats-bar {
      display: grid;
      grid-template-columns: repeat(6, 1fr);
      gap: 12px;
      margin-bottom: 24px;
    }
    .stat-card {
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: var(--radius-lg);
      padding: var(--sp-6) 18px var(--sp-7);
      position: relative;
      overflow: hidden;
      transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.18s ease;
      box-shadow: var(--shadow-card);
    }
    /* Accent top stripe — default subtle, colored for em/gold/amb variants */
    .stat-card::before {
      content: '';
      position: absolute;
      top: 0; left: 0; right: 0;
      height: 2px;
      background: linear-gradient(90deg, transparent 10%, var(--border-bright) 50%, transparent 90%);
      transition: opacity 0.2s ease;
    }
    .stat-card:hover::before { opacity: 1.4; }
    .stat-card.em-card::before   { background: linear-gradient(90deg, transparent 5%, var(--emerald) 50%, transparent 95%); }
    .stat-card.gold-card::before { background: linear-gradient(90deg, transparent 5%, var(--gold) 50%, transparent 95%); }
    .stat-card.amb-card::before  { background: linear-gradient(90deg, transparent 5%, var(--amber) 50%, transparent 95%); }
    /* Subtle tinted glow for colored cards */
    .stat-card.em-card   { box-shadow: var(--shadow-card), 0 0 0 1px rgba(79,142,247,0.06), inset 0 0 50px rgba(79,142,247,0.03); }
    .stat-card.gold-card { box-shadow: var(--shadow-card), 0 0 0 1px rgba(201,168,76,0.08), inset 0 0 50px rgba(201,168,76,0.03); }
    .stat-card.amb-card  { box-shadow: var(--shadow-card), 0 0 0 1px rgba(224,158,64,0.07), inset 0 0 50px rgba(224,158,64,0.03); }

    .stat-icon {
      font-size: 13px;
      margin-bottom: 10px;
      display: block;
      opacity: 0.45;
    }
    .stat-label {
      font-size: var(--fs-2);
      font-weight: 700;
      font-family: var(--sans);
      letter-spacing: 0.18em;
      text-transform: uppercase;
      color: var(--text-mid);
      margin-bottom: var(--sp-3);
      /* Reserve two lines. "TOTAL UPB" sits on one and "WTD AVG RATE" on two
         at narrow widths, which dropped one card's number 17px below its
         neighbour's and broke the band's baseline. Two lines is the observed
         maximum across every KPI label in the app. */
      min-height: calc(2 * 1.3em);
    }
    .stat-value {
      font-size: 34px;
      font-weight: 800;
      color: var(--text);
      font-family: var(--display);
      font-style: italic;
      line-height: 1;
      letter-spacing: -0.03em;
      position: relative;
    }
    /* Unit symbol ($ %) sits smaller at top-left */
    .stat-value .stat-unit {
      font-size: 14px;
      font-weight: 700;
      font-style: normal;
      vertical-align: super;
      letter-spacing: 0;
      margin-right: 1px;
      opacity: 0.75;
    }
    .stat-value.em   { color: var(--emerald); }
    .stat-value.amb  { color: var(--amber); }
    .stat-value.gold { color: var(--gold); }
    .stat-sub {
      font-size: 11px;
      color: var(--text-dim);
      font-family: var(--mono);
      margin-top: 5px;
      font-style: normal;
    }

    /* ═══════════════════════════════════════════
       FILTER BAR
    ═══════════════════════════════════════════ */
    .filter-bar {
      display: flex;
      gap: 4px;
      margin-bottom: 14px;
      flex-wrap: wrap;
      align-items: center;
    }
    .filter-btn {
      padding: 5px 14px;
      border-radius: 20px;
      font-size: 11px;
      font-weight: 700;
      letter-spacing: 0.06em;
      cursor: pointer;
      border: 1px solid var(--border);
      background: transparent;
      color: var(--text-mid);
      font-family: var(--sans);
      transition: all var(--transition);
      text-decoration: none;
      /* inline-flex, not inline-block: the (pointer: coarse) rule stretches
         this pill to min-height:44px, and inline-block leaves the label
         jammed against the top 5px of it. */
      display: inline-flex;
      align-items: center;
    }
    .filter-btn.active {
      background: var(--emerald-glow2);
      border-color: color-mix(in oklab, var(--emerald) 35%, transparent);
      color: var(--emerald);
    }
    .filter-btn:active { transform: scale(0.96); }

    /* ═══════════════════════════════════════════
       STATUS BADGES
    ═══════════════════════════════════════════ */
    .badge {
      display: inline-flex;
      align-items: center;
      gap: 5px;
      /* Vertical 3px and gap 5px left raw on purpose: both are off-ramp and
         tokenising them would be a -1px change to badge height inside table
         rows. Horizontal padding and size carry the density signal. */
      padding: 3px var(--sp-4);
      border-radius: 20px;
      font-size: var(--fs-1);
      font-weight: 700;
      letter-spacing: 0.1em;
      text-transform: uppercase;
      font-family: var(--sans);
      border: 1px solid;
    }
    .badge::before {
      content: '';
      width: 5px; height: 5px;
      border-radius: 50%;
      background: currentColor;
      flex-shrink: 0;
    }
    /* Active is a POSITIVE state (Active / Paid / Occupied / Bid / Current /
       signed), not the brand accent. state_pill('active') maps to st-perform
       (green) and BOTH print sheets already render it green - app.css was the
       odd one out. On --emerald it also became the danger colour under
       [data-accent=rose] and amber under the amber preset. Fill and border
       keep the original 0.12 / 0.30 alphas so the chip's weight and ring are
       unchanged, and stay literal rgba rather than --success-glow2 because
       that token is `transparent` under @media print, where this block
       deliberately keeps its tint. */
    .badge-active {
      background: rgba(52,211,153,0.12);
      color: var(--success);
      border-color: rgba(52,211,153,0.30);
    }
    /* The pulse fired on every active badge at once — 80 of them on a full
       page — so it stopped reading as "look here". The colored dot and the
       status word carry the signal on their own. */
    .badge-active::before { animation: none; }
    @keyframes badgePulse {
      0%, 100% { opacity: 1; }
      50%       { opacity: 0.35; }
    }
    .badge-pending {
      background: rgba(201,168,76,0.12);
      color: var(--gold);
      border-color: rgba(201,168,76,0.30);
    }
    .badge-default {
      background: rgba(91,141,224,0.12);
      color: var(--blue);
      border-color: rgba(91,141,224,0.28);
    }
    .badge-foreclosure {
      background: rgba(224,88,88,0.12);
      color: var(--red);
      border-color: rgba(224,88,88,0.28);
    }
    .badge-mitigation {
      background: rgba(91,141,224,0.12);
      color: var(--blue);
      border-color: rgba(91,141,224,0.28);
    }
    .badge-bankruptcy {
      background: rgba(155,114,224,0.12);
      color: var(--purple);
      border-color: rgba(155,114,224,0.28);
    }
    .badge-reperforming {
      background: rgba(20,184,166,0.12);
      color: #14b8a6;
      border-color: rgba(20,184,166,0.28);
    }

    /* ══════════════════════════════════��════════
       FLASH MESSAGES
    ══════════════════════════════════��════════ */
    .flash {
      display: flex; align-items: center; gap: 10px;
      padding: 10px 14px; border-radius: var(--radius);
      font-size: 12px; font-family: var(--mono);
      margin-bottom: 14px; border: 1px solid;
      animation: vt-fade-in 200ms ease both;
    }
    .flash-success { background: rgba(52,211,153,0.08); border-color: rgba(52,211,153,0.25); color: var(--success); }
    .flash-error   { background: rgba(224,88,88,0.09);  border-color: rgba(224,88,88,0.28);  color: var(--red); }
    .flash-warning { background: rgba(224,158,64,0.09); border-color: rgba(224,158,64,0.28); color: var(--amber); }
    .flash-info    { background: rgba(100,180,255,0.08);border-color: rgba(100,180,255,0.22);color: var(--text-mid); }
    .flash-icon    { font-size: 13px; flex-shrink: 0; }
    .flash-dismiss { margin-left: auto; background: none; border: none; cursor: pointer; color: inherit; opacity: .6; font-size: 16px; line-height: 1; padding: 0 2px; }
    .flash-dismiss:hover { opacity: 1; }

    /* ═══════════════════════════════════════════
       FORMS
    ═══════════════════════════════════════════ */
    .form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
    .form-group { display: flex; flex-direction: column; gap: 7px; }
    .form-group.full { grid-column: 1 / -1; }
    label {
      font-size: var(--fs-1);
      font-weight: 700;
      font-family: var(--sans);
      letter-spacing: 0.14em;
      text-transform: uppercase;
      color: var(--text-dim);
    }
    input, select, textarea {
      background: var(--surface2);
      border: 1px solid var(--border);
      border-radius: var(--radius);
      color: var(--text);
      font-family: var(--mono);
      font-size: var(--fs-4);
      /* Horizontal 13px stays raw: .currency-input-wrap::before is absolutely
         positioned at left:13px and .pct-input-wrap::after at right:13px, so
         the affix would drift off its input if this token moved. Un-couple
         those two rules first, then tokenise all three together. */
      padding: var(--sp-4) 13px;
      outline: none;
      transition: border-color var(--transition), box-shadow var(--transition);
      width: 100%;
    }
    input::placeholder, textarea::placeholder { color: var(--text-dim); }
    select option { background: var(--surface2); }
    textarea { resize: vertical; min-height: 88px; }

    /* .form-control appears ~512 times in markup and .form-control-sm ~215,
       and until now NEITHER had a rule anywhere in the project — the only
       mention was a font-size bump inside the (pointer: coarse) block. So
       every field was styled purely by the bare element selector above, and
       .form-control-sm promised a compact input while delivering a full-size
       one; call sites that genuinely needed a small field hand-patched it
       inline (`style="font-size:11px;padding:2px 6px;height:auto;"`).

       .form-control is defined to render IDENTICALLY to the element selector
       so none of the 512 existing usages shift a pixel. It exists so the
       class means something, and so `.form-control-sm` has a base to vary
       from. Only -sm changes anything. */
    .form-control {
      background: var(--surface2);
      border: 1px solid var(--border);
      border-radius: var(--radius);
      color: var(--text);
      font-family: var(--mono);
      font-size: var(--fs-4);
      padding: var(--sp-4) 13px;
      outline: none;
      transition: border-color var(--transition), box-shadow var(--transition);
      width: 100%;
    }
    .form-control-sm {
      font-size: var(--fs-2);
      padding: var(--sp-2) var(--sp-4);
      border-radius: var(--radius-sm);
      height: auto;
    }

    /* Currency input wrapper */
    .currency-input-wrap {
      position: relative;
    }
    .currency-input-wrap::before {
      content: '$';
      position: absolute;
      left: 13px;
      top: 50%;
      transform: translateY(-50%);
      color: var(--gold);
      font-family: var(--mono);
      font-size: 13px;
      font-weight: 500;
      pointer-events: none;
      z-index: 1;
    }
    .currency-input-wrap input {
      padding-left: 26px;
    }

    /* Percent input wrapper — mirrors .currency-input-wrap but the
       affix is a trailing "%" instead of a leading "$". Used by
       calculators (HECM recovery, auction ROI) that store ratios in
       the DB (0.05) and want to display whole percents (5%) to the
       operator. The client-side data-format="percent" JS handles the
       ratio ↔ display conversion; this class is pure visual chrome. */
    .pct-input-wrap {
      position: relative;
    }
    .pct-input-wrap::after {
      content: '%';
      position: absolute;
      right: 13px;
      top: 50%;
      transform: translateY(-50%);
      color: var(--text-dim);
      font-family: var(--mono);
      font-size: 13px;
      font-weight: 500;
      pointer-events: none;
      z-index: 1;
    }
    .pct-input-wrap input {
      padding-right: 26px;
    }

    /* Sticky asset-context strip — pinned to the top of the viewport
       under the topbar on detail pages. Originally defined inline in
       templates/ams/ams_detail.html; promoted to base so
       templates/asset_profile.html (Vault) can reuse the same classes
       without copying the CSS. */
    .asset-sticky {
      position: sticky;
      top: 0;
      z-index: 40;
      display: flex;
      align-items: center;
      gap: 20px;
      padding: 10px 14px;
      margin: 4px -28px 16px -28px;
      background: var(--surface);          /* fallback: engines without color-mix() */
      background: linear-gradient(180deg,
                  var(--surface) 0%,
                  color-mix(in oklab, var(--surface) 80%, var(--surface2)) 100%);
      border-top: 1px solid var(--border);
      border-bottom: 1px solid var(--border);
      font-size: 12px;
      box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
    }
    .asset-sticky-l {
      display: flex;
      align-items: baseline;
      gap: 12px;
      flex-shrink: 0;
      min-width: 0;
    }
    .asset-sticky-ref {
      font-family: var(--font-num);
      font-variant-numeric: tabular-nums;
      font-size: 11px;
      color: var(--text-dim);
      letter-spacing: 0.06em;
      text-transform: uppercase;
      white-space: nowrap;
    }
    .asset-sticky-name {
      font-weight: 700;
      color: var(--text);
      font-size: 13px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      max-width: 240px;
    }
    .asset-sticky-flag {
      font-size: 10px;
      font-weight: 700;
      letter-spacing: 0.1em;
      color: var(--red);
      padding: 2px 6px;
      border: 1px solid var(--red);
      border-radius: 3px;
      text-transform: uppercase;
    }
    .asset-sticky-m {
      flex: 1;
      color: var(--text-dim);
      font-size: 11px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      min-width: 0;
    }
    .asset-sticky-r {
      display: flex;
      align-items: center;
      gap: 16px;
      flex-shrink: 0;
    }
    .asset-sticky-kpi {
      display: flex;
      align-items: baseline;
      gap: 6px;
    }
    .asset-sticky-label {
      font-size: 10px;
      color: var(--text-dim);
      letter-spacing: 0.1em;
      text-transform: uppercase;
    }
    /* The single definition of this widget. A private copy in ams_detail.html
       re-declared only the .money variant, so on paper the same sticky bar had
       two different specs — tabular figures for every value here, for .money
       only there. That copy is deleted; this rule serves both screens. */
    .asset-sticky-value {
      font-family: var(--font-num);
      font-variant-numeric: tabular-nums;
      font-weight: 700;
      font-size: var(--kpi-size-inline);
      letter-spacing: var(--kpi-track-inline);
    }
    .asset-sticky-value.money {
      color: var(--gold);
    }
    /* Vault loan file: the bar is an action strip at rest and only takes on
       the hero's id / name / UPB once the hero has scrolled away. .js-ctx is
       added by the page's observer script; without it nothing is hidden. */
    .asset-sticky.js-ctx:not(.is-stuck) .asset-sticky-ctx { display: none; }
    /* Once stuck the id / name join five action chips on one line. Let the
       name ellipsize (it already has overflow:hidden) rather than push
       Prev / Next past the bar and pan the page on a 1280px laptop. */
    .asset-sticky.js-ctx .asset-sticky-l { flex-shrink: 1; }
    .asset-nav { display: inline-flex; align-items: center; gap: 2px; }
    .asset-nav .btn { padding-left: 9px; padding-right: 9px; }
    .asset-nav .btn[aria-disabled="true"] { opacity: .35; pointer-events: none; }
    .asset-nav-pos {
      font-family: var(--font-num);
      font-variant-numeric: tabular-nums;
      font-size: 11px;
      color: var(--text-dim);
      padding: 0 6px;
      white-space: nowrap;
    }
    @media (max-width: 900px) {
      /* The -28px bleed is tuned to .main's desktop padding; mobile .main is
         16px, so the bar ran 12px past both edges and panned the page. */
      /* flex-shrink:0 held this group at full content width, so its chips
         never got a narrow line to wrap onto and .detail-hero's
         overflow:hidden sliced the last one down the middle. */
      .detail-hero-actions { flex-shrink: 1; min-width: 0; width: 100%; }
      .asset-sticky { gap: 12px; padding: 8px 12px;
                      margin-left: -16px; margin-right: -16px; }
      .asset-sticky-m { display: none; }
      .asset-sticky-name { max-width: 140px; }
      /* The action strip's chips wrap instead of forcing the page wider. */
      .asset-sticky-r { flex-shrink: 1; flex-wrap: wrap; gap: 8px 12px; min-width: 0; }
      .asset-nav-pos { display: none; }
    }

    .form-section {
      grid-column: 1 / -1;
      display: flex;
      align-items: center;
      gap: 12px;
      margin: 8px 0 -4px;
    }
    .form-section-label {
      font-size: 11px;
      font-weight: 700;
      letter-spacing: 0.2em;
      text-transform: uppercase;
      /* Keeps the accent (it pairs with .gold below to mark financial
         sections) but at full --emerald: --emerald-dim was 3.64:1 on
         --surface / 3.40:1 on --surface2; --emerald is 5.87 / 5.48. */
      color: var(--emerald);
      white-space: nowrap;
    }
    .form-section-label.gold { color: var(--gold-dim); }
    .form-section-line {
      flex: 1;
      height: 1px;
      background: var(--border);
    }

    /* ═══════════════════════════════════════════
       DATA TABLE
    ═══════════════════════════════════════════ */
    .data-table { width: 100%; border-collapse: collapse; }
    .data-table th {
      text-align: left;
      font-size: var(--fs-1);
      font-weight: 700;
      letter-spacing: 0.16em;
      text-transform: uppercase;
      color: var(--text-dim);
      /* 14px, not 18px — the body cells below use 14px, so a right-aligned
         money header was printing 4px inboard of its own column of digits. */
      padding: var(--sp-4) var(--sp-6) var(--sp-5);
      border-bottom: 1px solid var(--border);
      white-space: nowrap;
      background: var(--head-band);
    }
    .data-table th:first-child { border-radius: var(--radius-sm) 0 0 0; }
    .data-table th:last-child  { border-radius: 0 var(--radius-sm) 0 0; }
    .data-table th.r { text-align: right; }
    .data-table td {
      /* calc(): the vertical value here is 9px, which is off the 2px spacing
         ramp. The +1px bridge keeps row height byte-identical at default
         density while still letting the density toggle move it (7px compact
         / 11px spacious). Drop the calc in the later sweep if a 1px
         row-height change is acceptable then. */
      padding: calc(var(--sp-3) + 1px) var(--sp-6);
      border-bottom: 1px solid var(--row-line);
      font-size: var(--fs-5);
      vertical-align: middle;
      transition: background 0.12s ease;
    }
    .data-table td.r { text-align: right; }
    /* Opt-in horizontal density for the wide daily lists (Vault, AMS). 14px
       of side padding per cell is 28px per column: 336px of a 12-column
       table is padding. 10px keeps cells readable and hands ~90px back to
       the columns. Vertical rhythm is untouched, so row height is unchanged.
       Higher specificity than the <=900px `.data-table th, .data-table td`
       rule on purpose: the phone build keeps the same 10px sides. */
    .data-table.dense th,
    .data-table.dense td { padding-inline: var(--sp-4); }
    /* Cell utilities so table rows stop hand-rolling
       style="font-family:var(--font-num);..." on every td. Compose freely:
         td.num              → tabular numeric
         td.num.c            → tabular numeric, centered
         td.num.r            → tabular numeric, right-aligned
         td.money            → gold, weight 600, mono (implicitly right)
         td.dim              → 11px, dim (for portfolio-name / meta columns)
         td.dim.small        → 10px, dim */
    .data-table td.num {
      font-family: var(--font-num);
      font-variant-numeric: tabular-nums slashed-zero;
    }
    .data-table td.num.c { text-align: center; }
    .data-table td.money {
      font-family: var(--font-num);
      font-variant-numeric: tabular-nums slashed-zero;
      text-align: right;
      color: var(--gold);
      font-weight: 600;
    }
    .data-table td.dim { color: var(--text-dim); font-size: var(--fs-2); }
    .data-table td.dim.small { font-size: var(--fs-1); }
    /* Two-line address cell: address on top, small dim city/state below. */
    .addr-sub {
      display: block;
      font-size: 10px;
      color: var(--text-dim);
      margin-top: 2px;
      font-family: var(--font-num);
    }
    .data-table tr:last-child td { border-bottom: none; }
    /* :nth-child counts every <tr>, including display:none ones. The Tasks
       list emits a hidden inline-edit row after EVERY task row, so its visible
       rows are children 1, 3, 5… — odd forever — and the striping was simply
       switched off on the app's longest list. Rentals emits its hidden row
       conditionally, so there the parity drifted and produced two striped rows
       then three plain ones, which is worse than none: the eye keeps trying to
       read the stripes as grouping. Those two tables opt out and stripe from
       the loop index instead (.zebra-manual + .is-alt). */
    .data-table:not(.zebra-manual) tbody tr:nth-child(even) td { background: var(--zebra); }
    .data-table.zebra-manual tbody tr.is-alt td { background: var(--zebra); }
    .data-table a { color: var(--text); text-decoration: none; transition: color var(--transition); }
    .data-table a:hover { color: var(--emerald); }

    /* Sticky table header.
       The sticky `thead th` below only sticks if THIS element is the scroll
       container, which needs both axes scrollable and a height to scroll
       within — with overflow-x alone the wrapper never scrolls vertically and
       the header never sticks. max-height only engages once the table is
       taller than the viewport allows, so short tables are unaffected. */
    .table-sticky-wrap { overflow: auto; max-height: calc(100vh - 260px); }

    /* ── Scroll-edge affordance ───────────────────────────────────────────
       A wide table clipped its last column with no sign that there was one:
       the AMS list loses RECOMMEND below about 1470px, and the analytics tab
       strip loses its eleventh tab, and in both cases the content simply
       stopped at the edge. This is the two-layer gradient trick — a pair of
       shadows painted in the SCROLL background layer, and a pair of covers
       painted in the LOCAL layer directly over them. The local layers move
       with the content, so at either extreme the cover sits exactly on its
       shadow and hides it, and a pane with nothing to scroll shows neither.
       No JS, no measurement, no resize listener, and it costs one declaration.
       --surface is the cover colour because both panes sit on a card. */
    /* The board column's "N more" hand-off. */
    .tb-more {
      display: block; margin: 6px 2px 2px; padding: 9px 10px;
      border: 1px dashed var(--border-bright); border-radius: 8px;
      font-family: var(--mono); font-size: 11px; color: var(--text-dim);
      text-align: center; text-decoration: none;
      transition: color var(--transition), border-color var(--transition);
    }
    .tb-more:hover { color: var(--text); border-color: var(--text-dim); }

    .scroll-edges, .table-sticky-wrap {
      background-color: var(--surface);
      background-image:
        linear-gradient(to right, var(--surface), transparent),
        linear-gradient(to left,  var(--surface), transparent),
        linear-gradient(to right, var(--shadow-edge), transparent),
        linear-gradient(to left,  var(--shadow-edge), transparent);
      background-position: left center, right center, left center, right center;
      background-size: 34px 100%, 34px 100%, 18px 100%, 18px 100%;
      background-repeat: no-repeat;
      background-attachment: local, local, scroll, scroll;
    }
    /* A position:sticky element paints its shadow whether or not it is
       actually stuck, so a five-row table wore a 14px smudge under its header
       for no reason — and the literal was never re-cut for light, where it came
       out twice as dark and five times as blurry as the app's own card
       elevation. Tokenised; see --head-shadow. */
    .data-table thead th { position: sticky; top: 0; background: var(--surface); z-index: 2; box-shadow: 0 1px 0 var(--border), var(--head-shadow); }

    /* Sortable table headers */
    .data-table[data-sortable] th[data-sort-col] {
      cursor: pointer;
      user-select: none;
      white-space: nowrap;
    }
    .data-table[data-sortable] th[data-sort-col]:hover {
      color: var(--emerald);
    }
    .sort-ind {
      display: inline-block;
      margin-left: 5px;
      font-size: 9px;
      opacity: 0.35;
      transition: opacity 0.14s ease, color 0.14s ease;
      vertical-align: middle;
    }
    th[data-sort-col]:hover .sort-ind,
    th[data-sort-active] .sort-ind {
      opacity: 1;
      color: var(--emerald);
    }

    /* ═══════════════════════════════════════════
       LOAN ID CHIP
    ═══════════════════════════════════════════ */
    .loan-id {
      font-family: var(--mono);
      font-size: 11px;
      font-weight: 500;
      color: var(--emerald);
      background: var(--emerald-glow);
      padding: 3px 9px;
      border-radius: var(--radius-sm);
      letter-spacing: 0.07em;
      border: 1px solid rgba(79,142,247,0.14);
      white-space: nowrap;
    }

    /* Gold money chip */
    .money-chip {
      font-family: var(--mono);
      /* Was 12px/500 against a 14px/600 borrower name in the same row — the
         name outweighed the balance on both axes, so a row had to be read
         rather than scanned. Money is now the heaviest thing in the row. */
      font-size: 15px;
      font-weight: 600;
      color: var(--gold);
    }

    /* ═══════════════════════════════════════════
       DATE CHIPS
    ═══════════════════════════════════════════ */
    .date-chip {
      font-family: var(--mono);
      font-size: 11px;
      color: var(--text-mid);
    }
    /* Same reason: app.js marks every past-due date urgent, and on a
       distressed book that is most rows. Red + the "42d overdue" pill that
       app.js appends already say it. */
    .date-chip.urgent { color: var(--red); font-weight: 600; }
    .date-chip.soon   { color: var(--amber); }
    @keyframes dateUrgent {
      0%, 100% { opacity: 1; }
      50%       { opacity: 0.5; }
    }

    .days-badge {
      display: inline-block;
      padding: 2px 8px;
      border-radius: 10px;
      font-size: 9px;
      font-weight: 700;
      font-family: var(--mono);
      letter-spacing: 0.04em;
    }
    .days-badge.overdue { background: rgba(224,88,88,0.14);  color: var(--red); }
    .days-badge.soon    { background: rgba(224,158,64,0.14); color: var(--amber); }
    .days-badge.future  { background: rgba(79,142,247,0.10);  color: var(--emerald); }

    /* ═══════════════════════════════════════════
       SECTION HEADERS
    ═══════════════════════════════════════════ */
    .section-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 16px;
    }
    .section-title {
      font-size: 11px;
      font-weight: 700;
      font-family: var(--sans);
      letter-spacing: 0.16em;
      text-transform: uppercase;
      color: var(--text-dim);
    }

    /* ═══════════════════════════════════════════
       DETAIL GRID
    ═══════════════════════════════════════════ */
    .detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
    .detail-item { display: flex; flex-direction: column; gap: 5px; }
    .detail-label {
      font-size: 11px;
      font-weight: 700;
      font-family: var(--sans);
      letter-spacing: 0.16em;
      text-transform: uppercase;
      color: var(--text-dim);
    }
    .detail-value { font-size: 14px; color: var(--text); }

    /* ═══════════════════════════════════════════
       P&L ROW
    ═══════════════════════════════════════════ */
    .pnl-row {
      display: flex;
      align-items: center;
      gap: 20px;
      padding: 12px 16px;
      background: var(--surface2);
      border: 1px solid var(--border);
      border-radius: var(--radius);
      margin-bottom: 20px;
      flex-wrap: wrap;
    }
    .pnl-item { display: flex; flex-direction: column; gap: 3px; }
    .pnl-label {
      font-size: 8px;
      font-weight: 700;
      font-family: var(--sans);
      letter-spacing: 0.18em;
      text-transform: uppercase;
      color: var(--text-dim);
    }
    .pnl-value {
      font-family: var(--mono);
      font-size: 15px;
      font-weight: 700;
    }
    .pnl-sep { width: 1px; height: 28px; background: var(--border); }
    .pnl-neu { color: var(--text-mid); }

    /* ═══════════════════════════════════════════
       LIFECYCLE STEPPER
    ═══════════════════════════════════════════ */
    .lifecycle {
      display: flex;
      align-items: center;
      gap: 0;
      margin-bottom: 20px;
      overflow-x: auto;
      scrollbar-width: none;
      padding-bottom: 2px;
    }
    .lifecycle::-webkit-scrollbar { display: none; }
    .lc-step {
      display: flex;
      align-items: center;
      flex-shrink: 0;
    }
    .lc-label {
      font-size: 9px;
      font-weight: 700;
      letter-spacing: 0.1em;
      text-transform: uppercase;
      font-family: var(--mono);
      color: var(--text-dim);
      padding: 5px 12px;
      border-radius: 20px;
      border: 1px solid var(--border);
      background: var(--surface2);
      transition: all var(--transition);
      white-space: nowrap;
    }
    .lc-label.done {
      /* --emerald-dim was 3.41:1 on this chip's own tinted ground (3.15:1 if
         the chip sits on --surface2). Full --emerald is 5.49 / 5.08. .current
         now shares the text colour and is told apart by its heavier fill
         (glow2 vs .06), 2x border alpha and box-shadow instead of by hue. */
      color: var(--emerald);
      border-color: rgba(79,142,247,0.2);
      background: rgba(79,142,247,0.06);
    }
    .lc-label.current {
      color: var(--emerald);
      border-color: rgba(79,142,247,0.4);
      background: var(--emerald-glow2);
      box-shadow: 0 0 10px rgba(79,142,247,0.12);
    }
    .lc-connector {
      width: 20px; height: 1px;
      background: var(--border);
      flex-shrink: 0;
    }
    .lc-connector.done { background: rgba(79,142,247,0.25); }

    /* ═══════════════════════════════════════════
       TAB SYSTEM
    ═══════════════════════════════════════════ */
    .tab-bar {
      display: flex;
      gap: 0;
      border-bottom: 1px solid var(--border);
      margin-bottom: 20px;
      overflow-x: auto;
      scrollbar-width: none;
    }
    .tab-bar::-webkit-scrollbar { display: none; }
    .tab-btn {
      background: none;
      border: none;
      border-bottom: 2px solid transparent;
      color: var(--text-mid);
      font-family: var(--sans);
      font-size: 12px;
      font-weight: 600;
      letter-spacing: 0.04em;
      padding: 10px 18px;
      cursor: pointer;
      margin-bottom: -1px;
      transition: all 0.15s;
      white-space: nowrap;
      display: inline-flex;
      align-items: center;
      gap: 7px;
    }
    .tab-btn:hover {
      color: var(--text);
      background: var(--wash-weak);
    }
    .tab-btn.active {
      color: var(--emerald);
      border-bottom-color: var(--emerald);
    }
    .tab-panel { display: none; }
    .tab-panel.active { display: block; }
    .tab-count {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      background: var(--wash-strong);
      border-radius: 10px;
      font-size: 10px;
      padding: 1px 7px;
      font-family: var(--mono);
      min-width: 20px;
      transition: background 0.15s;
    }
    .tab-btn.active .tab-count {
      background: var(--emerald-glow2);
      color: var(--emerald);
    }

    /* ═══════════════════════════════════════════
       EMPTY STATES
    ═══════════════════════════════════════════ */
    .empty {
      text-align: center;
      padding: 72px 20px;
      color: var(--text-dim);
    }
    /* --emerald-muted is #dbeafe in light mode against a #ffffff card — the
       32px hero glyph rendered at about 1.1:1, so a new user saw a blank gap
       where the icon should be. The halo was a literal default-blue that never
       re-pointed, so under the rose/violet/amber presets a plum glyph wore a
       blue glow. Both tokens below are redefined by every preset AND by the
       light theme, so one edit covers all ten combinations. */
    .empty-gem {
      font-size: 32px;
      color: var(--emerald-dim);
      margin-bottom: 14px;
      filter: drop-shadow(0 0 10px var(--emerald-glow2));
    }
    .empty h3 {
      font-size: 16px;
      font-weight: 700;
      font-family: var(--display);
      color: var(--text-mid);
      margin-bottom: 8px;
    }
    .empty p { font-size: 13px; margin-bottom: 20px; color: var(--text-dim); }
    /* The .empty-section definition that used to sit here was dead: an
       identical selector ~750 lines below overrode every one of its
       declarations except letter-spacing, which has been folded into the
       surviving rule. Editing this copy changed nothing on screen — which is
       exactly why it had to go. */

    /* ═══════════════════════════════════════════
       ALERTS
    ═══════════════════════════════════════════ */
    .alert {
      padding: 11px 16px;
      border-radius: var(--radius);
      font-size: 12px;
      margin-bottom: 18px;
      border: 1px solid;
      font-family: var(--mono);
      line-height: 1.6;
      animation: alertSlideIn 0.22s ease both;
    }
    @keyframes alertSlideIn {
      from { opacity: 0; transform: translateY(-6px); }
      to   { opacity: 1; transform: translateY(0); }
    }
    .alert-error   { background: rgba(224,88,88,0.08);  border-color: rgba(224,88,88,0.28);  color: var(--red); }
    /* -warning as well as -warn: .alert uses `border: 1px solid` with no
       colour, so a class that does not match resolves the border to
       currentcolor — var(--text) — and the banner came out with the brightest
       border in the product, no tint, and body-text ink. Live on
       admin/inbound_mail_queue.html and admin/ams_call_recap.html. The flash
       family spells it -warning, which is where the two templates got it. */
    .alert-warn,
    .alert-warning { background: rgba(224,158,64,0.08); border-color: rgba(224,158,64,0.28); color: var(--amber); }
    .alert-info    { background: rgba(91,141,224,0.08); border-color: rgba(91,141,224,0.22); color: var(--blue); }
    .alert-success { background: rgba(52,211,153,0.08);  border-color: rgba(52,211,153,0.22);  color: var(--success); }

    /* ═══════════════════════════════════════════
       CONTACT LOG
    ═══════════════════════════════════════════ */
    .contact-entry {
      border: 1px solid var(--border);
      border-left: 3px solid var(--emerald-muted);
      border-radius: var(--radius);
      padding: 16px 20px;
      margin-bottom: 10px;
      background: var(--surface);
      position: relative;
      transition: border-color var(--transition), box-shadow var(--transition);
    }
    .contact-entry:hover {
      border-color: var(--border-bright);
      border-left-color: var(--emerald-dim);
      box-shadow: 0 2px 14px rgba(0,0,0,0.25);
    }
    .contact-meta {
      display: flex;
      align-items: center;
      gap: 10px;
      margin-bottom: 12px;
      flex-wrap: wrap;
    }
    .contact-field { font-size: 12px; color: var(--text-mid); }
    .contact-field strong { color: var(--text); }
    .contact-body {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 14px;
      margin-top: 4px;
    }
    .contact-body-item { display: flex; flex-direction: column; gap: 4px; }
    .contact-body-label {
      font-size: 9px;
      font-weight: 700;
      font-family: var(--sans);
      letter-spacing: 0.14em;
      text-transform: uppercase;
      color: var(--text-dim);
    }
    .contact-body-value { font-size: 12px; color: var(--text-mid); line-height: 1.5; }
    .contact-followup-row {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-top: 12px;
      padding-top: 10px;
      border-top: 1px solid var(--border);
    }
    .contact-followup-label {
      font-size: 9px;
      font-weight: 700;
      font-family: var(--sans);
      letter-spacing: 0.14em;
      text-transform: uppercase;
      color: var(--text-dim);
    }
    .contact-delete {
      position: absolute;
      top: 12px; right: 14px;
    }

    /* ═══════════════════════════════════════════
       INLINE FORM
    ═══════════════════════════════════════════ */
    .inline-form {
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: var(--radius-lg);
      padding: 22px 26px;
      margin-top: 18px;
    }
    .inline-form-title {
      font-size: 10px;
      font-weight: 700;
      font-family: var(--sans);
      letter-spacing: 0.18em;
      text-transform: uppercase;
      color: var(--emerald);
      margin-bottom: 18px;
      display: flex;
      align-items: center;
      gap: 9px;
    }
    .inline-form-title::before {
      content: '';
      width: 3px; height: 13px;
      background: var(--emerald);
      border-radius: 2px;
    }

    /* ═══════════════════════════════════════════
       FILE INPUT
    ═══════════════════════════════════════════ */
    .file-input {
      background: var(--wash);
      border: 1px solid var(--border);
      border-radius: 8px;
      padding: 8px 12px;
      color: var(--text);
      font-size: 13px;
      cursor: pointer;
      flex: 1;
      font-family: var(--mono);
    }
    .file-input:focus { outline: none; border-color: var(--emerald-dim); }

    /* ═══════════════════════════════════════════
       DIVIDER
    ═══════════════════════════════════════════ */
    .divider { border: none; border-top: 1px solid var(--border); margin: 20px 0; }

    /* ═══════════════════════════════════════════
       MODULE-COLORED NAV ACTIVE STATES
    ═══════════════════════════════════════════ */
    /* AMS module */
    .nav-ams-active.active { border-left-color: var(--ams-color); color: var(--ams-color); }
    .nav-ams-active.active .nav-icon { color: var(--ams-color); }
    .nav-ams-active:hover { border-left-color: rgba(20,184,166,0.4); }
    /* CRM module */
    .nav-crm-active.active { border-left-color: var(--crm-color); color: var(--crm-color); }
    .nav-crm-active.active .nav-icon { color: var(--crm-color); }
    /* Intelligence */
    .nav-intel-active.active { border-left-color: var(--intel-color); color: var(--intel-color); }
    .nav-intel-active.active .nav-icon { color: var(--intel-color); }
    /* HECM */
    .nav-hecm-active.active { border-left-color: var(--hecm-color); color: var(--hecm-color); }
    .nav-hecm-active.active .nav-icon { color: var(--hecm-color); }
    /* Agreements */
    .nav-agmt-active.active { border-left-color: var(--agmt-color); color: var(--agmt-color); }
    .nav-agmt-active.active .nav-icon { color: var(--agmt-color); }
    /* RE */
    .nav-re-active.active { border-left-color: var(--re-color); color: var(--re-color); }
    .nav-re-active.active .nav-icon { color: var(--re-color); }
    /* Rentals */
    .nav-rental-active.active { border-left-color: var(--rental-color); color: var(--rental-color); }
    .nav-rental-active.active .nav-icon { color: var(--rental-color); }

    /* ═══════════════════════════════════════════
       LIGHT MODE BODY BACKGROUND
    ═══════════════════════════════════════════ */
    [data-theme="light"] body {
      background-image:
        radial-gradient(circle at 18% 82%, rgba(37,99,235,0.04) 0%, transparent 48%),
        radial-gradient(circle at 82% 18%, rgba(40,86,168,0.03) 0%, transparent 48%),
        radial-gradient(circle, rgba(0,0,0,0.025) 1px, transparent 1px);
    }
    [data-theme="light"] .logo-gem {
      filter: drop-shadow(0 0 8px rgba(37,99,235,0.18));
    }
    [data-theme="light"] .sidebar {
      box-shadow: 2px 0 12px rgba(0,0,0,0.08);
    }
    [data-theme="light"] .sidebar::before {
      background: linear-gradient(90deg, transparent 0%, var(--emerald) 50%, transparent 100%);
      opacity: 0.5;
    }
    [data-theme="light"] .card::before {
      background: linear-gradient(90deg, transparent 0%, rgba(37,99,235,0.08) 40%, rgba(122,90,24,0.06) 60%, transparent 100%);
    }
    [data-theme="light"] .card:hover::before {
      background: linear-gradient(90deg, transparent 0%, rgba(37,99,235,0.16) 40%, rgba(122,90,24,0.10) 60%, transparent 100%);
    }
    [data-theme="light"] .nav-link.active {
      background: linear-gradient(90deg, rgba(37,99,235,0.1) 0%, rgba(37,99,235,0.03) 100%);
      color: var(--emerald-dim);
      border-left-color: var(--emerald);
    }
    [data-theme="light"] .nav-label {
      color: var(--emerald-dim);
      opacity: 0.85;
      border-top-color: var(--border);
    }
    [data-theme="light"] .login-card {
      box-shadow: 0 8px 32px rgba(0,0,0,.10), 0 0 0 1px var(--border);
    }
    /* Ink on --emerald in light mode. Guarded rather than declared in the
       plain [data-theme="light"] block because the accent presets re-point
       --emerald in BOTH themes to a light hue (#60a5fa 8.26:1 / #8b5cf6
       4.96:1 / #f43f5e 5.72:1 / #f59e0b 9.78:1 under BLACK), where black
       stays the correct ink. Default/emerald accent in light mode is #2563eb
       — 4.06:1 under black, 5.17:1 under white.
       Wrapped in @media screen so this (0,5,0) rule cannot outrank the
       @media print token block, which needs DARK ink: browsers drop
       backgrounds on paper, so white ink would print invisibly. */
    @media screen {
      [data-theme="light"]:not([data-accent="sapphire"]):not([data-accent="violet"]):not([data-accent="rose"]):not([data-accent="amber"]) {
        --on-accent: #ffffff;
      }

      /* Accent presets on a white page.
         [data-theme="light"] and [data-accent="x"] are both (0,1,0) and both
         match <html>, and the preset blocks are LATER in the file — so they
         won. A light-theme user who picked an accent in Profile kept the
         dark-tuned hue on white: amber #f59e0b is 2.15:1 on --surface and
         sapphire #60a5fa is 2.54:1, both under even the 3:1 non-text floor.
         Because --focus is var(--emerald), that did not just dim some labels;
         it disarmed the focus ring, which is the one affordance a keyboard
         user cannot work without. It also hit ~40 text rules — .page-title
         span, .nav-link.active .nav-icon, .tab-btn.active, .loan-id.
         Re-cut at (0,2,0) so these beat the bare presets, and INSIDE
         @media screen so they cannot outrank the print token block the way
         the --on-accent guard above already documents.
         Ratios on --surface / worst surface (--surface4), measured:
           sapphire #1d4ed8  6.70 / 5.16
           violet   #6d28d9  7.10 / 5.47
           rose     #be123c  6.29 / 4.84
           amber    #a3510a  5.62 / 4.32
         For comparison the default light accent #2563eb is 5.17 / 3.98, so
         every preset now clears the bar the default set. */
      [data-theme="light"][data-accent="sapphire"] {
        --emerald:       #1d4ed8;
        --emerald-dim:   #1e3a8a;
        --emerald-muted: #dbeafe;
        --emerald-glow:  rgba(29,78,216,0.09);
        --emerald-glow2: rgba(29,78,216,0.16);
      }
      [data-theme="light"][data-accent="violet"] {
        --emerald:       #6d28d9;
        --emerald-dim:   #4c1d95;
        --emerald-muted: #ede9fe;
        --emerald-glow:  rgba(109,40,217,0.09);
        --emerald-glow2: rgba(109,40,217,0.16);
      }
      [data-theme="light"][data-accent="rose"] {
        --emerald:       #be123c;
        --emerald-dim:   #9f1239;
        --emerald-muted: #ffe4e6;
        --emerald-glow:  rgba(190,18,60,0.09);
        --emerald-glow2: rgba(190,18,60,0.16);
      }
      [data-theme="light"][data-accent="amber"] {
        /* #b45309 is the recognisable amber-700 but only 3.86:1 on --surface4;
           one step darker holds 4.32 there and still reads as amber rather
           than brown. */
        --emerald:       #a3510a;
        --emerald-dim:   #7c3d06;
        --emerald-muted: #fef3c7;
        --emerald-glow:  rgba(163,81,10,0.09);
        --emerald-glow2: rgba(163,81,10,0.16);
      }
    }
    /* The .empty-section diagonal hatch is white-alpha at .008 — invisible on
       white. Light-only override so the dark hatch is untouched. */
    [data-theme="light"] .empty-section {
      background: repeating-linear-gradient(
        -45deg,
        transparent,
        transparent 6px,
        rgba(0,0,0,0.022) 6px,
        rgba(0,0,0,0.022) 12px
      );
    }

    /* ═══════════════════════════════════════════
       THEME TOGGLE BUTTON
    ═══════════════════════════════════════════ */
    .btn-theme {
      background: transparent;
      border: 1px solid var(--border);
      border-radius: var(--radius-sm);
      color: var(--text-mid);
      font-size: 14px;
      width: 30px; height: 26px;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      transition: all var(--transition);
      flex-shrink: 0;
      line-height: 1;
    }
    .btn-theme:hover {
      border-color: var(--emerald-dim);
      color: var(--emerald);
      background: var(--emerald-glow);
    }

    /* ═══════════════════════════════════════════
       SCROLLBAR
    ═══════════════════════════════════════════ */

    /* ═══════════════════════════════════════════
       PAGINATION
    ═══════════════════════════════════════════ */
    .pagination-bar {
      display: flex;
      align-items: center;
      justify-content: space-between;
      flex-wrap: wrap; gap: 8px 12px;
      padding: 14px 4px 4px;
    }
    .page-btns { display: flex; flex-wrap: wrap; gap: 4px; }
    .page-btn {
      /* An <a>/<span> is inline, and min-height does not apply to inline
         boxes — so the coarse-pointer 44px floor silently did nothing here. */
      display: inline-flex;
      align-items: center;
      padding: 5px 11px;
      border-radius: var(--radius-sm);
      font-size: 12px;
      font-family: var(--mono);
      color: var(--text-mid);
      border: 1px solid var(--border);
      background: transparent;
      text-decoration: none;
      transition: all var(--transition);
      cursor: pointer;
    }
    .page-btn:hover { border-color: var(--border-bright); color: var(--text); }
    .page-btn-active {
      background: var(--emerald-glow2);
      border-color: color-mix(in oklab, var(--emerald) 35%, transparent);
      color: var(--emerald);
      cursor: default;
    }

    /* ═══════════════════════════════════════════
       MOBILE RESPONSIVE
    ═══════════════════════════════════════════ */
    .mobile-menu-btn { display: none; }

    @media (max-width: 900px) {
      .app-shell { flex-direction: column; }
      /* The top stripe is drawn 2× wide and slid with translateX. On desktop
         .sidebar is position:fixed, so that overflow never reaches the
         document. On phones it becomes position:relative and in-flow — so a
         200%-wide pseudo-element made EVERY page scroll sideways into a blank
         void exactly twice the width of the screen. Pin it to the bar. */
      .sidebar::before { width: 100%; animation: none; transform: none; }
      .sidebar {
        width: 100%; min-height: unset;
        /* STICKY, not relative. In-flow was what fixed the 200%-wide ::before
           above, but it also meant the bar — and with it the lockup and the
           only hamburger — scrolled away with the page. The Vault index and
           the AMS list run to several thousand pixels on a phone, and
           .table-sticky-wrap deliberately makes the page the only scroller, so
           after a few flicks the only affordance left on screen was the help
           FAB, and reaching another module meant flinging back to the top.
           Sticky keeps the ::before pinned to the bar exactly as relative did.
           z-index clears .asset-sticky (40) and sticky thead (2). */
        position: sticky; top: 0; z-index: 300;
        border-right: none; border-bottom: 1px solid var(--border);
        flex-direction: row; flex-wrap: wrap; align-items: center;
        padding: 12px 16px; gap: 0; box-shadow: none;
      }
      /* With the drawer open the sticky box is bar + 70vh of nav + the sign-out
         strip, which on a landscape phone is taller than the viewport — a
         sticky box taller than its scrollport pins at the top and strands its
         own bottom. The open drawer is a full takeover anyway, so let it
         scroll normally and Sign out stays reachable. */
      .sidebar:has(.sidebar-nav.open) { position: relative; z-index: 300; }

      /* Everything else that pins to the viewport top now has a bar above it.
         --mobile-bar-h is measured and published by app.js because the bar is
         one row or two depending on whether the account has both divisions. */
      .data-table thead th,
      .table-sticky-wrap .data-table thead th { top: var(--mobile-bar-h, 0px); }
      .asset-sticky { top: var(--mobile-bar-h, 0px); }
      /* [data-density="compact"|"spacious"] .sidebar (0,2,0) outranks the
         .sidebar rule above (0,1,0) — media queries add no specificity — so a
         user on Compact got a 216px top bar with bare page beside it. Restated
         at equal specificity, later in source order. Unlike sidebar-mini,
         density is NOT stripped by syncMobileNav(). */
      [data-density="compact"] .sidebar,
      [data-density="spacious"] .sidebar { width: 100%; }
      /* Same class of overflow as the ::before stripe: an absolutely-placed
         orb at left:-30px on a bar that is now in-flow. */
      .sidebar::after { display: none; }
      .sidebar-context { display: none; }
      .sidebar-logo { flex: 1; padding: 0; border: none; }
      .logo-tagline { display: none; }
      /* `flex: 1` on the desktop .sidebar-nav resolves to `flex-basis: 0%`,
         which beats `width: 100%` for a flex item on the main axis. On mobile
         .sidebar turns into a ROW, so the drawer never wrapped onto its own
         line — it stayed beside the logo and got crushed to a ~50px sliver
         against the right edge with every label clipped mid-word. Re-state the
         basis so it wraps to a full-width line of its own. */
      .sidebar-nav {
        display: none; flex: 0 0 100%; width: 100%; padding: 8px 0 4px;
        flex-direction: column; gap: 4px;
        max-height: 70vh; overflow-y: auto;
      }
      .sidebar-nav.open { display: flex; }
      /* New nested nav structure needs explicit overrides on mobile:
         pinned wrap + section items should stack as full-width strips
         rather than try to flex-wrap as a row of disconnected pills. */
      .sidebar-nav.open .nav-pinned-wrap,
      .sidebar-nav.open .nav-section-items {
        display: flex; flex-direction: column; width: 100%;
        border-bottom: none; padding: 4px 0;
      }
      /* THE drawer bug. .sidebar-nav is a column flex container with a 70svh
         cap, so its children are flex items and shrink when the content is
         taller than the cap — which it always is, with thirteen sections. A
         flex item's automatic minimum size normally stops that at its content
         height, but that protection only applies while `overflow` is visible,
         and .nav-section-items carries `overflow: hidden` for its collapse
         animation. So every section's list was shrunk to its 8px of padding
         and then clipped its own links: measured 8px tall around 83px of
         content. What was left on screen was a column of section headings
         separated by gaps, with tapping a chevron appearing to do nothing —
         the section did expand, from nothing to nothing.
         The headers, whose overflow is visible, kept their height, which is
         why they were the only thing that survived.
         Nothing in the drawer may shrink; the drawer scrolls instead. */
      .sidebar-nav.open > * { flex: 0 0 auto; }
      .sidebar-nav.open .nav-section-header {
        width: 100%; padding: 6px 12px;
        /* A thumb target, not a mouse target. */
        min-height: 44px;
      }
      .sidebar-nav.open .nav-link { min-height: 44px; }
      /* The header is a thumb target; so is the chevron inside it, which on a
         phone is the thing a thumb actually goes for. */
      .sidebar-nav.open .nav-section-toggle { min-width: 44px; min-height: 44px; }
      .sidebar-nav.open .nav-section-chevron {
        border-left-width: 6px; border-right-width: 6px; border-top-width: 7px;
      }
      .sidebar-nav.open .nav-pinned-title { font-size: 9px; padding: 4px 12px 2px; }
      .sidebar-secure { display: none; }
      .sidebar-footer { display: none; }
      .mobile-menu-btn {
        display: flex; align-items: center; justify-content: center;
        width: 44px; height: 44px;
        /* Was white-alpha, i.e. gone on a white rail — the one control the
           whole phone nav depends on was the faintest thing in the bar. */
        background: var(--surface2);
        border: 1px solid var(--border);
        border-radius: 8px;
        cursor: pointer;
        font-size: 16px;
        color: var(--text);
        flex-shrink: 0;
      }
      /* It had no hover, focus, press or open state of any kind, and the
         tap-highlight reset below removes the platform's own feedback, so a
         tap on a 44px target reported nothing back. */
      .mobile-menu-btn:active { background: var(--surface3); transform: scale(0.96); }
      .mobile-menu-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
      .sidebar:has(.sidebar-nav.open) .mobile-menu-btn {
        background: var(--emerald-glow2); border-color: var(--emerald-dim); color: var(--emerald);
      }
      .main {
        margin-left: 0 !important;
        padding: 18px 16px !important;
        max-width: 100% !important;
        width: 100%;
        animation: none;
      }
      .topbar { flex-direction: column; align-items: flex-start; gap: 10px; }
      .stats-bar { grid-template-columns: 1fr 1fr 1fr !important; }
      .detail-grid { grid-template-columns: 1fr !important; }
      .form-grid { grid-template-columns: 1fr !important; }
      .card { padding: 16px; }
      .data-table th, .data-table td { padding: 10px 12px; }
      /* Column hiding is now OPT-IN. The old blind nth-child(4)/(5) rule
         silently deleted business-critical columns (UPB/Status/Address) whose
         position shifts by role — a data-integrity bug. Mark genuinely-optional
         columns with .col-hide-md / .col-hide-sm instead; everything else
         scrolls (see .card:has(.data-table) below). */
      .data-table th.col-hide-md, .data-table td.col-hide-md { display: none; }
      /* .card { overflow: hidden } clips anything wide that is not a
         .data-table. The Resolution Recommendation panel's alternates table
         asks for 390px of fixed columns inside a ~296px card and is not a
         .data-table, so its reason column was clipped with no scroll path.
         Shared by the Vault, AMS and HECM detail screens. */
      .rec-alt-table { display: block; overflow-x: auto; }
      .rec-alt-chip, .rec-alt-score, .rec-alt-recovery { width: auto; }
    }

    @media (max-width: 600px) {
      .stats-bar { grid-template-columns: 1fr 1fr !important; }
      .data-table th.col-hide-sm, .data-table td.col-hide-sm { display: none; }
    }

    @media (max-width: 420px) {
      /* Was `1fr`. Eight KPI tiles then stacked into ~950px — nearly three
         phone screens of scrolling before the first row of data. Two columns
         with a tighter tile puts the whole KPI band above the fold. */
      .stats-bar { grid-template-columns: 1fr 1fr !important; gap: 8px !important; }
      .stat-card { padding: 12px 12px 11px !important; }
      /* 20px, not 22: "$6,930,412" clipped its last digit in a 134px tile on
         a 360px Galaxy (the 390px iPhone width hid it). */
      .stat-value { font-size: 20px !important; letter-spacing: -.03em; min-width: 0; }
      .stat-label { font-size: 9px !important; }
    }

    /* ── Elevation: responsive data-integrity ──────────────────────────────
       Dense tables SCROLL horizontally instead of crushing/clipping columns,
       and a 901–1280px step keeps KPI rows from cramping on laptops. */
    .card:has(.data-table) { overflow-x: auto; }
    /* Un-wrapped dense tables sit in cards with inline overflow:hidden — force
       horizontal scroll so their columns stay reachable (not clipped). Tables
       already inside a .table-sticky-wrap are unaffected (not a direct child). */
    .card:has(> table.data-table) { overflow-x: auto !important; }
    /* .stat-row — referenced by the Intelligence dashboard but never defined, so
       its KPI row crushed. Define as a responsive auto-fit grid (no crush). */
    .stat-row {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
      gap: 12px; margin-bottom: 16px;
    }

    /* Visible Cmd+K search trigger in the sidebar (the palette was keyboard-only). */
    .nav-search-wrap { padding: 8px 10px 4px; }
    .nav-search-btn {
      width: 100%; display: flex; align-items: center; gap: 8px;
      padding: 7px 10px; border-radius: 8px; cursor: pointer;
      background: var(--surface2); border: 1px solid var(--border);
      color: var(--text-dim); font-family: var(--font-sans); font-size: 12px;
      transition: border-color .14s, color .14s, background .14s;
    }
    .nav-search-btn:hover { border-color: var(--border-bright); color: var(--text); }
    .nav-search-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
    .nav-search-ico { font-size: 13px; opacity: .8; }
    .nav-search-txt { flex: 1; text-align: left; }
    .nav-search-kbd {
      background: var(--surface3); border: 1px solid var(--border-bright);
      border-radius: 4px; padding: 1px 5px; font-size: 9px; color: var(--text-mid);
      font-family: var(--font-num);
    }

    /* ── Mobile nav fix ──────────────────────────────────────────────────────
       On phones the sidebar becomes a top bar with a hamburger drawer, but the
       DESKTOP collapse/mini system leaked in: the fixed .sidebar-toggle /
       .sidebar-mini-toggle circles floated over the bar, and a saved
       collapsed/mini state shoved the bar off-screen (transform:translateX)
       or shrank it to 60px. Hide the desktop toggles and neutralize those
       states below 900px. (JS also strips the classes — see syncMobileNav.) */
    @media (max-width: 900px) {
      .sidebar-toggle, .sidebar-mini-toggle { display: none !important; }
      body.sidebar-collapsed .sidebar { transform: none !important; }
      html:not([data-nav="top"]) body.sidebar-mini .sidebar { width: 100% !important; }
      body.sidebar-collapsed .main, body.sidebar-mini .main { margin-left: 0 !important; }
    }

    @media (max-width: 1280px) and (min-width: 901px) {
      .stats-bar { grid-template-columns: repeat(4, 1fr) !important; }
    }

    /* ═══════════════════════════════════════════
       HELP WIDGET
    ═══════════════════════════════════════════ */
    .help-btn {
      position: fixed;
      bottom: 24px; right: 24px;
      width: 40px; height: 40px;
      border-radius: 50%;
      background: var(--surface2);
      border: 1px solid var(--border-bright);
      color: var(--text-mid);
      font-size: 15px;
      font-family: var(--mono);
      font-weight: 700;
      cursor: pointer;
      z-index: 600;
      display: flex; align-items: center; justify-content: center;
      transition: all var(--transition);
      box-shadow: 0 2px 14px rgba(0,0,0,.45);
      line-height: 1;
    }
    .help-btn:hover {
      background: var(--emerald-glow2);
      border-color: rgba(79,142,247,0.45);
      color: var(--emerald);
      box-shadow: 0 0 22px rgba(79,142,247,0.18), 0 2px 14px rgba(0,0,0,.45);
      transform: translateY(-1px);
    }
    .help-btn.active {
      background: var(--emerald);
      color: var(--on-accent);
      border-color: var(--emerald);
      box-shadow: 0 0 22px rgba(79,142,247,0.35), 0 2px 14px rgba(0,0,0,.45);
    }
    .help-panel {
      position: fixed;
      bottom: 76px; right: 24px;
      width: 300px;
      max-height: 70vh;
      background: var(--surface);
      border: 1px solid var(--border-bright);
      border-radius: var(--radius-lg);
      box-shadow: var(--shadow-raise);
      z-index: 599;
      display: none;
      flex-direction: column;
      overflow: hidden;
    }
    .help-panel.open {
      display: flex;
      animation: helpSlideUp 0.22s cubic-bezier(0,0,0.2,1) both;
    }
    @keyframes helpSlideUp {
      from { opacity: 0; transform: translateY(10px); }
      to   { opacity: 1; transform: translateY(0); }
    }
    .help-panel-header {
      display: flex; align-items: center; justify-content: space-between;
      padding: 13px 16px 11px;
      border-bottom: 1px solid var(--border);
      flex-shrink: 0;
      background: var(--surface2);
    }
    .help-panel-title {
      font-size: 9px; font-weight: 700; font-family: var(--sans);
      letter-spacing: 0.2em; text-transform: uppercase;
      color: var(--emerald);
      display: flex; align-items: center; gap: 7px;
    }
    .help-panel-title::before {
      content: '?';
      width: 16px; height: 16px;
      background: var(--emerald-glow2);
      border: 1px solid rgba(79,142,247,0.3);
      border-radius: 50%;
      display: flex; align-items: center; justify-content: center;
      font-size: 9px; color: var(--emerald);
      font-weight: 700; font-family: var(--mono);
    }
    .help-panel-close {
      background: none; border: none;
      color: var(--text-dim); font-size: 18px;
      cursor: pointer; line-height: 1; padding: 0 2px;
      transition: color var(--transition);
    }
    .help-panel-close:hover { color: var(--text); }
    .help-panel-body {
      padding: 14px 16px 16px;
      overflow-y: auto; flex: 1;
    }
    .help-tip {
      display: flex; gap: 10px;
      margin-bottom: 13px;
      font-size: 12px; line-height: 1.55;
      color: var(--text-mid);
    }
    .help-tip:last-child { margin-bottom: 0; }
    .help-tip-dot {
      color: var(--emerald); font-size: 10px;
      flex-shrink: 0; margin-top: 3px;
      font-family: var(--mono);
    }
    .help-tip strong { color: var(--text); font-weight: 600; }
    .help-tip a { color: var(--emerald); text-decoration: none; }
    .help-tip a:hover { text-decoration: underline; }
    .help-tip-section {
      font-size: 9px; font-weight: 700; font-family: var(--sans);
      letter-spacing: 0.16em; text-transform: uppercase;
      color: var(--text-dim);
      margin: 14px 0 8px;
      padding-bottom: 6px;
      border-bottom: 1px solid var(--border);
    }
    .help-tip-section:first-child { margin-top: 0; }
    @media (max-width: 900px) {
      .help-btn { bottom: 16px; right: 16px; }
      /* At pointer:coarse the button is 48x48, so it owns the bottom-right
         64px of the viewport — directly over the action button in the last
         card-mode row and over the pagination bar's Next. The toast stack was
         already given this clearance; page content never was. */
      .main { padding-bottom: 76px !important; }
      .help-panel { right: 16px; bottom: 68px; width: calc(100vw - 32px); max-width: 300px; }
    }

    /* ═══════════════════════════════════════════
       VISUAL UPLIFT — Interactions & Polish
    ═══════════════════════════════════════════ */

    /* ── Custom scrollbars ───────────────────── */
    /* Windows Chrome/Edge draw these on every scroller (page, sidebar,
       table wrappers, textareas); Android and iOS use overlay scrollbars
       and ignore them. 10px hit area with a 4px visible bar — the old 5px
       thumb in --border-bright failed 3:1 and could not be grabbed. */
    ::-webkit-scrollbar { width: 10px; height: 10px; }
    ::-webkit-scrollbar-track { background: transparent; }
    ::-webkit-scrollbar-thumb {
      background-color: var(--scroll-thumb);
      background-clip: padding-box;
      border: 3px solid transparent;
      border-radius: 10px;
      min-height: 40px;
    }
    ::-webkit-scrollbar-thumb:hover { background-color: var(--emerald); }
    ::-webkit-scrollbar-corner { background: transparent; }

    /* ── Form inputs — richer focus glow ─────── */
    input:focus, select:focus, textarea:focus {
      border-color: var(--emerald-dim);
      box-shadow: 0 0 0 3px var(--emerald-glow), 0 0 12px rgba(79,142,247,0.08);
    }
    input:hover:not(:focus), select:hover:not(:focus), textarea:hover:not(:focus) {
      border-color: var(--border-bright);
    }
    /* Valid state hint */
    input:not(:placeholder-shown):not(:focus) {
      border-color: rgba(79,142,247,0.2);
    }

    /* ── Buttons — refined interactions ──────── */
    .btn {
      position: relative;
      overflow: hidden;
    }
    /* Ripple effect on click */
    .btn::after {
      content: '';
      position: absolute;
      inset: 0;
      border-radius: inherit;
      background: rgba(255,255,255,0.12);
      opacity: 0;
      transform: scale(0.85);
      transition: transform 0.25s ease, opacity 0.25s ease;
    }
    .btn:active::after {
      opacity: 1;
      transform: scale(1);
      transition: none;
    }
    .btn:hover {
      transform: translateY(-2px);
    }
    .btn:active { transform: translateY(0) scale(0.97); }
    .btn-ghost:hover {
      transform: none;
      background: var(--wash);
      border-color: var(--emerald-dim);
      color: var(--emerald);
      box-shadow: 0 0 0 1px color-mix(in oklab, var(--emerald) 12%, transparent);
    }
    .btn-ghost:active { transform: scale(0.97); }
    /* .btn:hover still fires on a disabled button in Firefox. */
    .btn:disabled:hover, .btn[aria-disabled="true"]:hover {
      transform: none; box-shadow: none; filter: none;
    }
    .btn:disabled:active, .btn[aria-disabled="true"]:active { transform: none; }
    .btn-danger:hover {
      transform: none;
      background: rgba(224,88,88,0.1);
      border-color: var(--red);
      box-shadow: none;
    }

    /* ── Stat cards — lift + accent on hover ─── */
    .stat-card {
      cursor: default;
      transition: border-color var(--transition), box-shadow var(--transition),
                  transform 0.2s cubic-bezier(0.16,1,0.3,1);
    }
    .stat-card:hover {
      transform: translateY(-3px);
      box-shadow: var(--shadow-raise), 0 0 28px rgba(79,142,247,0.07);
      border-color: rgba(79,142,247,0.25);
    }
    .stat-card.gold-card:hover {
      border-color: rgba(201,168,76,0.3);
      box-shadow: var(--shadow-raise), 0 0 24px rgba(201,168,76,0.07);
    }

    /* ── Data table — richer hover accent ─────
       Per-row transitions removed so hover on a 100+ row table (HECM
       pool detail, AMS list) doesn't queue up a hundred parallel
       background/border/padding tweens that eat the compositor.
       Hover visual is kept, but the padding-transition (which forces
       layout) is gone — only border-color remains (compositor-safe). */
    .data-table {
      /* `contain: content` tells the browser this subtree's layout,
         paint, and style don't leak out — so a hover / focus inside
         the table can't invalidate layout on the whole page. Cuts the
         cost of the OS-native <select> popup elsewhere on the page
         needing to recompute its anchor position. */
      contain: content;
    }
    .data-table tbody tr {
      position: relative;
      /* `content-visibility: auto` lets Chrome/Edge/Safari skip layout
         and paint for rows that are off-screen — the row is treated as
         a 1200px×34px placeholder until it scrolls into view, then it
         renders on demand. Turns a 500-row loan table's layout cost
         from linear-in-N into constant. `contain-intrinsic-size` is
         the hint for the placeholder height so the scrollbar behaves. */
      content-visibility: auto;
      contain-intrinsic-size: auto 34px;
    }
    @media (hover: hover) {
      .data-table tbody tr:hover td {
        background: var(--row-hover);
      }
      .data-table tbody tr:hover td:first-child {
        border-left: 2px solid var(--emerald-dim);
      }
    }
    .data-table tbody tr td:first-child {
      border-left: 2px solid transparent;
    }

    /* Row-click affordance. The delegated handler in app.js opens
       <tr data-href="…">, but a <tr> is not a link and carries no cursor of
       its own — without this the whole-row click target is invisible and the
       feature may as well not exist. Text stays selectable on purpose (the
       handler ignores a click that ended a drag-selection), so deliberately
       no `user-select: none` here. The hover rail on the first cell goes from
       the dim default to solid emerald to say "this row is armed". */
    .data-table tbody tr[data-href] { cursor: pointer; }
    @media (hover: hover) {
      .data-table tbody tr[data-href]:hover td:first-child {
        border-left-color: var(--emerald);
      }
    }

    /* ── Section titles — emerald accent rule ── */
    .section-title {
      position: relative;
      padding-bottom: 6px;
    }
    .section-title::after {
      content: '';
      position: absolute;
      left: 0; bottom: 0;
      width: 20px; height: 2px;
      background: var(--emerald);
      border-radius: 2px;
      transition: width 0.3s ease;
    }
    .card:hover .section-title::after { width: 36px; }

    /* ── Sidebar nav items — refined hover ───── */
    .nav-item {
      transition: all 0.18s ease;
      border-left: 2px solid transparent;
    }
    .nav-item:hover {
      background: var(--emerald-glow) !important;
      border-left-color: rgba(79,142,247,0.4);
      color: var(--text) !important;
    }
    .nav-item.active {
      background: var(--emerald-glow2) !important;
      border-left-color: var(--emerald) !important;
      color: var(--emerald) !important;
    }

    /* ── Filter pills — smoother active state ── */
    .filter-btn {
      transition: all 0.18s ease;
      position: relative;
    }
    .filter-btn:hover {
      border-color: rgba(79,142,247,0.3);
      color: var(--text);
      background: var(--emerald-glow);
    }
    .filter-btn.active {
      box-shadow: 0 0 0 1px rgba(79,142,247,0.2), 0 2px 8px rgba(79,142,247,0.1);
    }

    /* ── Cards — gold-card variant ──────────── */
    .card.gold-card {
      border-color: rgba(201,168,76,0.18);
    }
    .card.gold-card:hover {
      border-color: rgba(201,168,76,0.32);
    }

    /* ── Badges — hover shimmer ──────────────── */
    .badge {
      transition: box-shadow 0.2s ease;
    }
    .badge:hover {
      box-shadow: 0 0 8px rgba(79,142,247,0.2);
    }
    .badge-active:hover  { box-shadow: 0 0 8px rgba(52,211,153,0.3); }
    .badge-foreclosure:hover { box-shadow: 0 0 8px rgba(224,88,88,0.25); }
    .badge-pending:hover { box-shadow: 0 0 8px rgba(201,168,76,0.25); }

    /* ── Loan-ID chips — hover glow ─────────── */
    .loan-id {
      transition: all 0.18s ease;
    }
    .loan-id:hover {
      background: var(--emerald-glow2);
      border-color: rgba(79,142,247,0.3);
      color: var(--emerald);
      box-shadow: 0 0 10px rgba(79,142,247,0.15);
    }

    /* ── Empty states ─────────────────────────── */
    .empty-section {
      text-align: center;
      padding: 32px 20px;
      color: var(--text-dim);
      font-size: 12px;
      font-family: var(--mono);
      letter-spacing: 0.02em;  /* folded in from the dead duplicate above */
      background: repeating-linear-gradient(
        -45deg,
        transparent,
        transparent 6px,
        rgba(255,255,255,0.008) 6px,
        rgba(255,255,255,0.008) 12px
      );
      border: 1px dashed var(--border);
      border-radius: var(--radius);
    }
    .empty-section::before {
      content: '—';
      display: block;
      font-size: 18px;
      margin-bottom: 8px;
      opacity: 0.3;
    }

    /* ── Detail items — hover hint ───────────── */
    .detail-item {
      transition: opacity var(--transition);
    }
    /* .detail-grid:hover .detail-item:not(:hover) { opacity: 0.7 } used to sit
       here. Moving the pointer anywhere in a detail grid faded every other
       field, which in light mode took .detail-label — 11px/700 uppercase, the
       smallest text on the page — from 4.5:1 to 3.08:1. It revealed nothing
       and indicated nothing; it was decoration applied to the data the
       operator was in the middle of reading, with no way to switch it off
       except by using a touch screen. */

    /* ── Topbar action buttons — tighter group ─
       backdrop-filter dropped — the sidebar/topbar already sits on a
       solid surface, and each blurred region forced a per-frame GPU
       recomposite that added to compositor pressure (dropdown-lag
       diagnosis, 2026-08). */

    /* ── Tab buttons — refined state ─────────── */
    .tab-btn {
      transition: all 0.18s ease !important;
    }
    .tab-btn:hover:not(.active) {
      color: var(--text) !important;
      border-bottom-color: var(--border-bright) !important;
    }
    .tab-btn.active {
      color: var(--emerald) !important;
      border-bottom: 2px solid var(--emerald) !important;
    }

    /* ── Flash messages — slide-in animation ─── */
    .flash {
      animation: flash-in 0.35s cubic-bezier(0.16,1,0.3,1) both;
    }
    @keyframes flash-in {
      from { opacity:0; transform:translateX(-8px); }
      to   { opacity:1; transform:translateX(0); }
    }

    /* ── Textarea — subtle focus expand ─────── */
    /* textarea:focus { min-height: 108px } used to live here. Textareas sit in
       normal flow, so focusing one lurched the hint text, the next section and
       the submit row 20px down at the moment of the click — the button moved
       out from under the cursor. It also fought `resize: vertical`: a textarea
       dragged shorter snapped back to 108px on the next focus. */

    /* ── Alert boxes — accent left border ────── */
    .alert {
      border-left-width: 3px;
    }

    /* ── Divider lines — gradient ────────────── */
    .divider {
      border: none;
      height: 1px;
      background: linear-gradient(90deg, transparent, var(--border), transparent);
      margin: 24px 0;
    }

    /* ═══════════════════════════════════════════
       TOP NAV MODE  ([data-nav="top"] on <html>)
       Sidebar → fixed 52px bar + JS-injected section
       dropdown menus replacing the sidebar-nav.
    ═══════════════════════════════════════════ */

    @media (min-width: 901px) {
    [data-nav="top"] .sidebar {
      position: fixed !important;
      top: 0 !important;
      left: 0 !important;
      right: 0 !important;
      bottom: auto !important;
      width: 100% !important;
      height: 52px !important;
      min-height: 0 !important;
      max-height: 52px !important;
      flex-direction: row !important;
      flex-wrap: nowrap !important;
      align-items: center !important;
      overflow: hidden !important;
      border-right: none !important;
      border-bottom: 1px solid var(--border) !important;
      z-index: 300 !important;
      padding: 0 !important;
      gap: 0 !important;
      box-shadow: 0 2px 20px rgba(0,0,0,.6) !important;
      backdrop-filter: blur(16px) !important;
      -webkit-backdrop-filter: blur(16px) !important;
      background: rgba(11,17,32,0.94) !important;
    }
    [data-theme="light"][data-nav="top"] .sidebar {
      background: rgba(248,250,252,0.94) !important;
    }
    [data-nav="top"] .sidebar::before,
    [data-nav="top"] .sidebar::after { display: none !important; }
    [data-nav="top"] .sidebar-context,
    [data-nav="top"] .sidebar-secure,
    [data-nav="top"] .mobile-menu-btn,
    [data-nav="top"] .sidebar-nav { display: none !important; }

    /* Logo */
    [data-nav="top"] .sidebar-logo {
      padding: 0 14px !important; border-bottom: none !important;
      border-right: 1px solid var(--border) !important; height: 100% !important;
      display: flex !important; align-items: center !important; flex-shrink: 0 !important;
    }
    [data-nav="top"] .logo-img-wrap { padding: 3px 7px !important; }
    [data-nav="top"] .logo-img { height: 22px !important; width: auto !important; max-width: 90px !important; }

    /* Division switcher */
    [data-nav="top"] .div-switcher {
      display: flex !important; flex-direction: row !important; align-items: center !important;
      gap: 3px !important; height: 100% !important; padding: 0 10px !important;
      border-bottom: none !important; border-right: 1px solid var(--border) !important;
      flex-shrink: 0 !important;
    }
    [data-nav="top"] .div-switcher a {
      height: 28px !important; display: flex !important; align-items: center !important;
      padding: 0 9px !important; font-size: 10px !important;
      /* The sidebar layout gives these flex:1 so the two pills split the rail.
         In a 52px bar that same rule squeezed VAULT onto two lines inside a
         box 28px tall, so the text overflowed its own pill. */
      flex: 0 0 auto !important; white-space: nowrap !important;
    }

    /* ── Injected top-nav section dropdown menus ──────────────── */
    /* overflow:hidden with flex-shrink:0 children silently clipped whole
       modules: the nine section triggers measure ~1090px, and below about
       1400px the last of them simply were not there — with .sidebar-nav
       hidden in this mode there was no other way to reach them. The row now
       scrolls, and app.js additionally folds whatever does not fit into a
       trailing "More" group, so the scroll is a fallback rather than the
       affordance. scrollbar-width:none keeps the 52px bar clean. */
    .tn-menus {
      display: none; flex: 1; align-items: center; min-width: 0;
      height: 52px; overflow-x: auto; overflow-y: hidden; padding: 0 6px; gap: 2px;
      scrollbar-width: none; -ms-overflow-style: none;
    }
    .tn-menus::-webkit-scrollbar { height: 0; width: 0; }
    [data-nav="top"] .tn-menus { display: flex; }

    .tn-group { position: relative; height: 52px; display: flex; align-items: center; flex-shrink: 0; }

    .tn-trigger {
      height: 34px; padding: 0 11px; background: transparent; border: none;
      /* Some of these are <a>, some are <button>. The anchors were picking up
         the browser's default underline and reading as a different kind of
         control from the buttons beside them. */
      text-decoration: none;
      color: var(--text-mid); cursor: pointer; font-size: 11.5px; font-weight: 500;
      border-radius: 6px; display: flex; align-items: center; gap: 5px;
      white-space: nowrap; font-family: var(--sans); letter-spacing: 0.01em;
      transition: background 0.14s, color 0.14s;
    }
    .tn-trigger.active { color: var(--emerald); background: var(--emerald-glow2); font-weight: 600; }
    .tn-trigger:hover:not(.active) { background: var(--surface2); color: var(--text); }
    .tn-group.open > .tn-trigger { background: var(--surface2); color: var(--text); }
    /* Same glyph, same problem, and worse: 8px of U+25BE at 45% opacity inked
       4x3 px in eight barely-visible pixels — the only thing distinguishing a
       top-bar item that opens a panel from one that just navigates. */
    .tn-arrow {
      width: 0; height: 0;
      font-size: 0; line-height: 0;
      border-left: 4px solid transparent;
      border-right: 4px solid transparent;
      border-top: 5px solid currentColor;
      color: var(--text-dim);
      transition: transform 0.14s, color var(--transition);
      margin-top: 2px; flex-shrink: 0;
    }
    .tn-trigger:hover .tn-arrow, .tn-group.open .tn-arrow { color: var(--text); }
    .tn-group.open .tn-arrow { transform: rotate(180deg); }

    .tn-panel {
      display: none; position: fixed; top: 52px; min-width: 175px;
      max-height: calc(100vh - 72px); overflow-y: auto;
      background: var(--surface); border: 1px solid var(--border);
      border-radius: 10px; padding: 5px;
      box-shadow: 0 8px 40px rgba(0,0,0,.55), 0 2px 8px rgba(0,0,0,.3);
      z-index: 500;
    }
    .tn-group.open .tn-panel { display: block; }
    .tn-panel .nav-link {
      display: flex !important; border-left: none !important;
      margin-bottom: 1px !important; border-radius: 6px !important;
      padding: 7px 10px !important;
    }
    .tn-panel .nav-link.active {
      background: var(--emerald-glow2) !important; color: var(--emerald) !important;
    }
    /* Section heading inside the "More" panel, which holds whole modules
       rather than one module's pages. */
    .tn-panel-label {
      font-size: 9px; font-weight: 800; letter-spacing: 0.16em;
      text-transform: uppercase; color: var(--text-dim);
      padding: 8px 10px 4px;
    }
    .tn-panel-label:first-child { padding-top: 2px; }

    /* Footer */
    [data-nav="top"] .sidebar-footer {
      border-top: none !important; border-left: 1px solid var(--border) !important;
      padding: 0 12px !important; flex-shrink: 0 !important; height: 100% !important;
      display: flex !important; align-items: center !important;
    }
    [data-nav="top"] .sidebar-user { flex-direction: row !important; gap: 8px !important; align-items: center !important; }
    [data-nav="top"] .sidebar-user-info { display: none !important; }

    /* Main — remove left offset, clear the 52px fixed bar */
    [data-nav="top"] .main { margin-left: 0 !important; padding-top: 88px !important; }

    /* Everything that pins to the top of the viewport has to clear the bar.
       Every sticky element in the shell pins at top:0 with a single-digit
       z-index, and the bar is opaque (rgba(11,17,32,.94) + blur) — so in
       top-nav mode the column headers of every list, and the loan file's
       action strip, scrolled straight underneath it and were gone. The offset
       is a token so the 52px lives in one place. */
    [data-nav="top"] { --topbar-h: 52px; }
    [data-nav="top"] .data-table thead th,
    [data-nav="top"] .asset-sticky,
    [data-nav="top"] .table-sticky-wrap .data-table thead th { top: var(--topbar-h); }

    /* The legal strip is a child of .sidebar-footer, which in top-nav mode
       becomes a 52px-tall flex row — so Terms · Privacy · © rendered INSIDE
       the bar, 10px below its centre, with its own divider hanging over it,
       pushing Sign out left and eating the width the section menus need. */
    [data-nav="top"] .sidebar-legal { display: none !important; }
    /* Sidebar-specific toggles are meaningless in top mode */
    [data-nav="top"] .sidebar-toggle,
    [data-nav="top"] .sidebar-mini-toggle { display: none !important; }
    /* Pin stars only make sense in the dynamic sidebar — hide inside
       the top-nav dropdown panels */
    [data-nav="top"] .nav-pin-btn { display: none !important; }
    } /* end @media (min-width: 901px) — top-nav mode */

    /* ═══════════════════════════════════════════════════════════════════
       DESIGN SYSTEM v2 — Phase 3 · Batch 1 (Foundation)  [Direction A: Terminal]
       Additive & source-order-late on purpose. Introduces: the tabular-figure
       numeric font (the #1 audit fix), semantic token aliases, the credit-state
       status language, an accessibility focus ring, and a correctness fix for
       the phantom --accent-red token. No layouts are restructured in this batch.
       See docs/design-proposal.md.
    ═══════════════════════════════════════════════════════════════════ */

    /* Self-hosted Geist + Geist Mono variable fonts (SIL OFL 1.1; see
       static/fonts/OFL.txt). The system stacks in --font-sans / --font-num stay
       as fallback — system monospace already renders tabular figures. */
    @font-face{font-family:'Geist';src:url('/static/fonts/Geist-Variable.woff2') format('woff2');font-weight:100 900;font-style:normal;font-display:swap;}
    @font-face{font-family:'Geist Mono';src:url('/static/fonts/GeistMono-Variable.woff2') format('woff2');font-weight:100 900;font-style:normal;font-display:swap;}

    :root{
      /* Numbers ride the SAME sans family as body text (uniform, per Eddy) —
         money is differentiated by gold color + weight + tabular alignment, NOT
         a separate monospaced face. font-variant-numeric:tabular-nums (applied
         on the .num/.money/td.r rule below) keeps columns aligned decimal-for-
         decimal without the code-y mono look. --mono aliases this too, so every
         figure — money, IDs, refs — shares one font. */
      --font-sans:'Geist','Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;
      --font-num: var(--font-sans);

      /* Semantic aliases over the existing palette (non-breaking). */
      --primary: var(--emerald);
      --primary-weak: var(--emerald-glow2);
      --focus: var(--emerald);
      --pos: var(--success);
      --neg: var(--red);
      --warn: var(--amber);
      --accent-red: var(--red);   /* fix: phantom token used by hecm_pl.html negatives */

      /* Credit-state ramp — the loan-status signature language (dark values). */
      --st-perform:#2fb67c; --st-perform-weak:rgba(47,182,124,.14);
      --st-30:#c9a83e;      --st-30-weak:rgba(201,168,62,.14);
      --st-60:#d98a2b;      --st-60-weak:rgba(217,138,43,.14);
      --st-90:#e0574c;      --st-90-weak:rgba(224,87,76,.14);
      /* fc/bk text lifted for AA at 10px. The -weak fills stay put on purpose:
         they are a light tint over a dark surface, so leaving them dark is
         what buys the contrast back (dropping the tint alpha alone tops out
         at 3.78:1, so the text had to move). Caveat: this leaves fc lighter
         than --st-90 (#e0574c), inverting the ramp's lightness direction at
         the top end. Straightening that out means re-cutting st-90 (itself
         only 4.42:1) and st-fc together - deferred. */
      --st-fc:#d76e66;      --st-fc-weak:rgba(198,65,58,.16);
      --st-reo:#8a7cf0;     --st-reo-weak:rgba(138,124,240,.14);
      --st-bk:#8593b2;      --st-bk-weak:rgba(108,122,153,.16);
    }
    [data-theme="light"]{
      /* Contrast is measured against the pill's own fill (the -weak rgba at .12
         composited over --surface), not bare white, because that is what
         .state-pill actually renders. Old: perform 3.96, 30 3.50, 60 3.74,
         90 4.43. New: 5.19 / 4.81 / 4.68 / 5.26, and still 4.77 / 4.41 / 4.30
         / 4.82 over --surface2. Hue held within ~1.5deg. */
      --st-perform:#106f4b; --st-perform-weak:rgba(16,111,75,.12);
      --st-30:#7f631a;      --st-30-weak:rgba(127,99,26,.12);
      --st-60:#9d5714;      --st-60-weak:rgba(157,87,20,.12);
      --st-90:#b03029;      --st-90-weak:rgba(176,48,41,.12);
      --st-fc:#a81f18;      --st-fc-weak:rgba(168,31,24,.12);
      --st-reo:#5b46c9;     --st-reo-weak:rgba(91,70,201,.12);
      --st-bk:#4a5878;      --st-bk-weak:rgba(74,88,120,.14);

      /* Light-mode flips of the surface tints (dark-alpha so they show on white). */
      --row-line:              rgba(0,0,0,0.08);
      --zebra:                 rgba(0,0,0,0.025);
      /* Dark-alpha so the hover is DARKER than the 2.5% zebra rather than a
         lighter wash of near-identical lightness, which is why hovering a
         striped row in light mode used to do nothing at all. */
      --row-hover:             rgba(0,0,0,0.05);
      --head-shadow:           0 2px 6px rgba(0,0,0,0.07);
      --shadow-edge:           rgba(15,23,42,0.16);
      --scroll-thumb:          #4a6888;
      --head-band:             rgba(0,0,0,0.03);
      --card-hover-shadow:     0 8px 24px rgba(0,0,0,.10);
      --statcard-hover-shadow: 0 6px 18px rgba(0,0,0,.08);
      --wash-weak:             rgba(0,0,0,0.03);
      --wash:                  rgba(0,0,0,0.045);
      --wash-strong:           rgba(0,0,0,0.075);

      /* --gold is #7a5a18 here (3.30:1 vs black, 6.36:1 vs white) and --amber
         is #9a6a18 (4.45 vs black, 4.72 vs white), so warm-accent ink goes
         white. The gradient far stop has to darken with it — #a8841a is only
         3.51:1 under white text, so the right half of .btn-gold would still
         fail. #5c4210 is 9.37:1.
         --on-accent is NOT set here on purpose — see the @media screen rule
         next to [data-theme="light"] .login-card, which has to outrank the
         four [data-accent] presets. */
      --on-gold:               #ffffff;
      --gold-grad2:            #5c4210;
    }

    /* #1 FIX — tabular figures for every numeric context. Right-aligned table
       data cells, KPI values, money chips, P&L values, and ID chips get
       font-variant-numeric:tabular-nums so digits are equal-width and columns
       line up decimal-for-decimal — in the shared sans family, no mono face.
       Opt-in utilities: .num / .money / .tnum. */
    .num,.money,.tnum,.date-chip,
    .stat-value,.stat-sub,.money-chip,.pnl-value,.loan-id,
    .data-table td.r{
      font-family: var(--font-num);
      font-variant-numeric: tabular-nums;
      font-feature-settings:"tnum" 1,"lnum" 1;
    }
    /* .date-chip joined the list above: it is the shared date renderer for the
       whole product and was the one numeric column left proportional, so a
       column of 01/07/2026 over 11/11/2026 staggered — in a column whose only
       job is date comparison.

       And the rule keyed on hooks: .r, .money, .num. 294 cells across the
       templates right-align with an inline style instead, and they cluster in
       the RE Results suite — 64 in the report alone — so the same money column
       rendered aligned on Rentals and ragged on Geography. Rather than convert
       294 call sites, every cell in a data table gets the figures: in a table
       of loan ids, counts, dates and money there is no column that wants
       proportional digits. Only the numeric properties, so nothing else about
       a cell changes. */
    .data-table td, .data-table th{
      font-variant-numeric: tabular-nums;
      font-feature-settings:"tnum" 1,"lnum" 1;
    }
    /* A chip class landing on the <td> itself instead of a <span> inside it
       loses its size to .data-table td (0,1,1) — colour, weight and family
       still apply, so nothing looks broken, it just is not the chip. HECM
       Pools rendered .money-chip at 14px while the Vault list rendered the
       same class at 15px. */
    .data-table td.money-chip{ font-size: 15px; }
    .data-table td.date-chip { font-size: 11px; }
    .stat-value{ font-style: normal; letter-spacing:-.01em; }  /* de-italicize KPI numbers for scanability */
    .money.neg,.num.neg{ color: var(--neg); }
    .money.pos,.num.pos{ color: var(--pos); }

    /* Credit-state pill — ONE status language, used everywhere (adoption in
       later batches; CSS lands now). */
    .state-pill{
      display:inline-flex;align-items:center;gap:6px;
      padding:2px 9px 2px 8px;border-radius:20px;
      font-family:var(--font-sans);font-size:10px;font-weight:700;
      letter-spacing:.06em;text-transform:uppercase;white-space:nowrap;
      border:1px solid transparent;line-height:1.5;
    }
    .state-pill::before{content:'';width:6px;height:6px;border-radius:50%;background:currentColor;flex-shrink:0;}
    .state-pill.st-perform{color:var(--st-perform);background:var(--st-perform-weak);border-color:var(--st-perform-weak);}
    .state-pill.st-30{color:var(--st-30);background:var(--st-30-weak);border-color:var(--st-30-weak);}
    .state-pill.st-60{color:var(--st-60);background:var(--st-60-weak);border-color:var(--st-60-weak);}
    .state-pill.st-90{color:var(--st-90);background:var(--st-90-weak);border-color:var(--st-90-weak);}
    .state-pill.st-fc{color:var(--st-fc);background:var(--st-fc-weak);border-color:var(--st-fc-weak);}
    .state-pill.st-reo{color:var(--st-reo);background:var(--st-reo-weak);border-color:var(--st-reo-weak);}
    .state-pill.st-bk{color:var(--st-bk);background:var(--st-bk-weak);border-color:var(--st-bk-weak);}
    /* Fallback for a status outside the credit ramp. state_pill() used to fall
       back to st-bk, so anything unmapped rendered as bankruptcy. */
    .state-pill.st-neutral{color:var(--text-dim);background:var(--surface3);border-color:var(--border);}

    /* Resolution recommendation chip — shared across AMS list, Vault list,
       and HECM views. Colour tone comes from the engine's PATH_META. */
    .rec-chip{
      display:inline-flex;align-items:center;gap:4px;
      font-family:var(--font-sans);font-size:10px;font-weight:700;letter-spacing:.03em;
      padding:3px 8px;border-radius:10px;white-space:nowrap;border:1px solid transparent;
    }
    .rec-chip.rec-green{color:var(--st-perform);background:var(--st-perform-weak);border-color:var(--st-perform-weak);}
    .rec-chip.rec-blue {color:var(--primary,#6ea3ff);background:rgba(110,163,255,.12);border-color:rgba(110,163,255,.28);}
    .rec-chip.rec-amber{color:var(--st-60);background:var(--st-60-weak);border-color:var(--st-60-weak);}
    .rec-chip.rec-red  {color:var(--st-90);background:var(--st-90-weak);border-color:var(--st-90-weak);}
    .rec-chip.rec-gray {color:var(--text-dim);background:var(--wash-strong);border-color:var(--border);}

    /* Filter-chip variant of the rec-chip — used on the AMS list rollup
       row so the tone colour flows through the .ws-filter shell. */
    .ws-filter.rec-green{color:var(--st-perform);border-color:var(--st-perform-weak);}
    .ws-filter.rec-green.active{background:var(--st-perform-weak);}
    .ws-filter.rec-blue {color:var(--primary,#6ea3ff);border-color:rgba(110,163,255,.28);}
    .ws-filter.rec-blue.active{background:rgba(110,163,255,.14);}
    .ws-filter.rec-amber{color:var(--st-60);border-color:var(--st-60-weak);}
    .ws-filter.rec-amber.active{background:var(--st-60-weak);}
    .ws-filter.rec-red  {color:var(--st-90);border-color:var(--st-90-weak);}
    .ws-filter.rec-red.active{background:var(--st-90-weak);}
    .ws-filter.rec-gray {color:var(--text-dim);}

    /* Resolution Recommendation panel — shared layout across AMS/Vault/HECM. */
    .resolution-card{padding:16px 20px;}
    .rec-hero{display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;padding:10px 0 14px;border-bottom:1px solid var(--border);margin-bottom:12px;}
    .rec-hero-left{display:flex;align-items:center;gap:12px;}
    .rec-score{font-family:var(--font-num);font-size:10px;color:var(--text-dim);letter-spacing:.06em;text-transform:uppercase;}
    .rec-hero-right{display:flex;gap:24px;flex-wrap:wrap;}
    .rec-metric-label{font-family:var(--font-sans);font-size:9px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.06em;}
    .rec-metric-value.money{font-family:var(--font-num);font-size:15px;font-weight:600;color:var(--text);}
    .rec-metric-value.money.neg{color:var(--st-90);}
    .rec-pct{font-size:11px;color:var(--text-dim);font-weight:400;}
    .rec-body{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:12px;}
    @media (max-width:780px){.rec-body{grid-template-columns:1fr;}}
    .rec-list-block--cautions .rec-list-title{color:var(--st-60);}
    .rec-list-title{font-family:var(--font-sans);font-size:10px;color:var(--text-dim);letter-spacing:.06em;text-transform:uppercase;margin-bottom:6px;}
    .rec-list{margin:0;padding-left:18px;font-size:12px;line-height:1.55;}
    .rec-list li{margin-bottom:3px;}
    .rec-alt-header{font-family:var(--font-sans);font-size:10px;color:var(--text-dim);letter-spacing:.06em;text-transform:uppercase;padding:8px 0 4px;border-top:1px solid var(--border);}
    .rec-alt-table{width:100%;border-collapse:collapse;font-size:11px;}
    .rec-alt-table td{padding:6px 8px 6px 0;vertical-align:middle;}
    .rec-alt-chip{width:140px;}
    .rec-alt-score{width:90px;color:var(--text-dim);font-family:var(--font-num);text-transform:uppercase;letter-spacing:.06em;font-size:10px;}
    .rec-alt-recovery{width:160px;font-family:var(--font-num);}
    .rec-alt-reason{color:var(--text-dim);}
    .rec-gaps{margin-top:12px;padding:8px 12px;background:var(--wash);border-left:3px solid var(--text-dim);border-radius:4px;}
    .rec-gaps-title{font-size:11px;color:var(--text-dim);margin-bottom:4px;}

    /* Accessibility floor — visible focus ring on interactive controls that
       previously fell back to the (often invisible) UA outline. */
    .btn:focus-visible,.btn-gold:focus-visible,.btn-ghost:focus-visible,
    .btn-danger:focus-visible,.btn-sm:focus-visible,.nav-link:focus-visible,
    .filter-btn:focus-visible,.tab-btn:focus-visible,.page-btn:focus-visible,
    .btn-logout:focus-visible{
      /* No border-radius here: it applies to the ELEMENT, not the outline, and
         this selector's (0,2,0) beats every base rule — so tabbing along a row
         squared off each control in turn and .filter-btn's 999px pill snapped
         to a rectangle and back. The outline already follows the element's own
         radius. */
      outline:2px solid var(--focus);outline-offset:2px;
    }

    /* ═══════════════════════════════════════════════════════════════════
       DESIGN SYSTEM v2 — Phase 3 · Batch 2 (Core primitives)
       Additive: the two primitives the audit found missing (skeleton loader,
       generic modal), badge variants reconciled onto tokens + the credit-state
       ramp, and a fix for the undefined .empty-state classes. Behavior wiring
       (modal open/close, skeleton adoption) lands in the states pass.
    ═══════════════════════════════════════════════════════════════════ */

    /* Skeleton loader — async panels/charts had none (only 1 existed app-wide). */
    .skeleton{ position:relative; overflow:hidden;
      background:var(--surface2); border-radius:var(--radius-sm); }
    .skeleton::after{ content:''; position:absolute; inset:0; transform:translateX(-100%);
      background:linear-gradient(90deg,transparent,var(--emerald-glow2),transparent);
      animation:skel-shimmer 1.35s ease-in-out infinite; }
    @keyframes skel-shimmer{ 100%{ transform:translateX(100%);} }
    .skeleton-line{ height:12px; margin:8px 0; }
    .skeleton-line.sm{ height:9px; width:60%; }
    .skeleton-line.lg{ height:20px; width:40%; }
    .skeleton-block{ height:120px; }
    .skeleton-circle{ border-radius:50%; width:36px; height:36px; }
    @media (prefers-reduced-motion: reduce){ .skeleton::after{ animation:none; } }

    /* Generic modal — the app had no shared modal (#kbModal was inline). Pairs
       with the [data-modal-open]/[data-modal-close] delegated handler. */
    .modal-overlay{ display:none; position:fixed; inset:0; z-index:10300;
      background:rgba(3,7,14,.62); backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px);
      align-items:center; justify-content:center; padding:24px; }
    .modal-overlay.open{ display:flex; }
    .modal-card{ background:var(--surface); border:1px solid var(--border-bright);
      border-radius:var(--radius-lg); box-shadow:var(--shadow-raise);
      width:520px; max-width:94vw; max-height:88vh; overflow:auto;
      animation:cmd-in .22s cubic-bezier(.16,1,.3,1) both; }
    .modal-head{ display:flex; align-items:center; justify-content:space-between;
      padding:16px 20px; border-bottom:1px solid var(--border); }
    .modal-title{ font-size:16px; font-weight:700; color:var(--text); }
    .modal-body{ padding:18px 20px; }
    .modal-foot{ display:flex; justify-content:flex-end; gap:10px;
      padding:14px 20px; border-top:1px solid var(--border); }
    .modal-close{ background:none; border:none; color:var(--text-dim); cursor:pointer;
      font-size:19px; line-height:1; padding:2px 6px; border-radius:6px; }
    .modal-close:hover{ color:var(--text); background:var(--surface2); }

    /* Badge status variants → tokens (removes the two hardcoded hex fills the
       audit flagged) and aligns with the credit-state ramp. */
    /* Resolved / Inactive is not a credit state. Borrowing --st-bk /
       --st-bk-weak (the ramp's BANKRUPTCY slot) both misuses the token and
       lands at 3.69:1 in dark mode - under AA for 10px uppercase. Use the
       neutral the ramp already reserves for "no state" (.state-pill
       .st-neutral): 7.47:1 dark, 4.89:1 light, 7.2:1 print. border-color is
       set explicitly because the earlier .badge-inactive at ~1640 still
       supplies a translucent one that would sit over an opaque fill. */
    .badge-inactive{ color:var(--text-dim); background:var(--surface3); border-color:var(--border-bright); }
    .badge-reperforming{ color:var(--ams-color); background:var(--ams-glow); }

    /* ═══════════════════════════════════════════════════════════════════
       DESIGN SYSTEM v2 — Phase 3 · Batch 3 (Shell: type system + layout)
       Completes the Direction-A type system: the three legacy font aliases
       (which all resolved to a placeholder serif) now point at the real faces
       — Geist for text, Geist Mono for numbers/IDs. This is the headline visual
       shift. Ambient animation retained per direction. Adds a reusable
       page-header + breadcrumb component (adopted screen-by-screen in Batch 4).
    ═══════════════════════════════════════════════════════════════════ */
    :root{
      --sans:    var(--font-sans);   /* was: Cambria serif placeholder */
      --display: var(--font-sans);   /* headings/body chrome → Geist */
      --mono:    var(--font-num);     /* aliased to the uniform sans/number font (no separate mono face) */
    }

    /* Terminal page-title: crisp grotesque, tighter optical tracking. */
    .page-title{ letter-spacing:-.02em; }

    /* Reusable page header + breadcrumb (component; adoption in Batch 4). */
    .page-head{ margin-bottom:22px; }
    .breadcrumbs{ display:flex; align-items:center; gap:6px; flex-wrap:wrap;
      font-size:11px; color:var(--text-dim); margin-bottom:8px;
      font-family:var(--font-num); letter-spacing:.02em; }
    .breadcrumbs a{ color:var(--text-dim); text-decoration:none; }
    .breadcrumbs a:hover{ color:var(--primary); }
    .breadcrumbs .sep{ opacity:.45; }
    .breadcrumbs .current{ color:var(--text-mid); }
    .page-actions{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; }

    /* ═══════════════════════════════════════════════════════════════════
       KPI FIGURE SCALE — one ramp for every headline number in the app
       ═══════════════════════════════════════════════════════════════════
       The same conceptual element — a big figure with a small label — had
       drifted to nine sizes across a dozen screens plus six template-local
       copies (.ds-value, .kpi-value, .hero-stat-value, .rpt-stat-value,
       .bid-stat-value, .bc-bignum). These five steps are the ramp.

         hero    38px/800  page-level stat cards (.stat-value), single bid card
         lead    28px/800  card lead figure, secondary hero, .stat-value.sm
         tile    22px/700  compact hero, .stat-value.xs, report stat grid
         cell    18px/700  ordinary .kpi-tile value, dense stat grids
         inline  13px/700  sticky bars and inline chips

       DELIBERATELY NOT on the ramp yet: .kpi-tile.kpi-lead (24px) and
       .bid-stat-value (15px). Snapping the first would SHRINK a lead tile and
       flatten the hierarchy it exists to create; snapping the second would
       grow a nowrap+ellipsis money readout inside a fixed 5-column grid that
       has no responsive fallback, which can truncate a dollar figure on a
       narrow desktop. Both need someone to look at the rendered screen first.

       Tracking travels with the size so a figure keeps its optical density
       when it moves a step. KPI figures are ALWAYS roman — the display face's
       italic was killed for scanability and must not come back. The rule
       below is a floor, not a guarantee: a template <style> block still wins
       on specificity ties, which is exactly how .ds-value kept its italic. */
    :root{
      --kpi-size-hero:   38px;  --kpi-track-hero:   -.03em;
      --kpi-size-lead:   28px;  --kpi-track-lead:   -.02em;
      --kpi-size-tile:   22px;  --kpi-track-tile:   -.02em;
      --kpi-size-cell:   18px;  --kpi-track-cell:   -.012em;
      --kpi-size-inline: 13px;  --kpi-track-inline: -.005em;
    }
    .stat-value, .ds-value, .kpi-value, .k-value, .hero-stat-value,
    .rpt-stat-value, .bid-stat-value, .bc-bignum, .asset-sticky-value{
      font-style: normal;
    }

    /* ─── Type refinement (review feedback): bigger, bolder KPI figures ───
       The numbers are the product — give the hero metrics more presence.
       Tighter tracking claws back the extra width so 6-up stat rows still fit. */
    .stat-value{ font-size:var(--kpi-size-hero); font-weight:800; letter-spacing:var(--kpi-track-hero); }
    .stat-value.sm{ font-size:var(--kpi-size-lead); letter-spacing:var(--kpi-track-lead); }
    .stat-value.xs{ font-size:var(--kpi-size-tile); letter-spacing:var(--kpi-track-tile); }

    /* ═══════════════════════════════════════════════════════════════════
       DESIGN SYSTEM v2 — Phase 3 · States & Accessibility pass
    ═══════════════════════════════════════════════════════════════════ */

    /* Honor prefers-reduced-motion: users who ask their OS for reduced motion
       get the decorative/ambient animation stilled (orbs, sidebar shimmer/orb,
       secure-dot pulse, nav-bar shimmer, skeleton). The default experience is
       unchanged for everyone else. */
    @media (prefers-reduced-motion: reduce){
      *, *::before, *::after{
        animation-duration:.001ms !important;
        animation-iteration-count:1 !important;
        transition-duration:.001ms !important;
        scroll-behavior:auto !important;
      }
      body::before, .sidebar::before, .sidebar::after, .secure-dot{ animation:none !important; }
      /* The blanket rule only collapses durations; it never cancels a
         transform, so a reduced-motion user still got the 2-3px lift under the
         cursor on every button and every KPI tile — instantly, which is
         precisely the vestibular trigger the preference exists to suppress.
         .watch-star and the landing page already did this; the signed-in shell
         was the surface that was missed. */
      .btn:hover, .btn-gold:hover, .btn-ghost:hover, .btn-danger:hover,
      .stat-card:hover, .card:hover, .hover-lift:hover,
      .dash-stats > a:hover .ds-card {
        transform: none;
      }
    }

    /* The document while a dialog is open. position:fixed rather than
       overflow:hidden because iOS Safari ignores overflow on <body>; the
       negative top set in JS preserves the scroll position, which is restored
       on unlock. */
    body.is-locked {
      position: fixed;
      left: 0; right: 0;
      width: 100%;
      overflow: hidden;
    }

    /* Field-level validation primitive (was absent app-wide; adoption is
       incremental). Pair a flagged control with a message:
         <input class="is-invalid"> <div class="field-error">…</div> */
    .field-error{ color:var(--neg); font-size:11px; margin-top:4px; font-family:var(--font-sans); }
    input.is-invalid, select.is-invalid, textarea.is-invalid{
      border-color:var(--neg) !important;
      box-shadow:0 0 0 3px rgba(224,87,76,.16);
    }
    /* The rest of the field vocabulary. .field-error/.is-invalid above already
       existed but were never emitted by a template; these three are what was
       actually missing, and field() in templates/_components.html emits them.
       Colors ride --neg/--red, which are re-pointed for light mode and for
       print, so all three themes track from one definition. */
    /* Required marker. 41 call sites inline a red asterisk span today
       (37 via var(--red), 4 via var(--accent-red), which aliases --red) --
       this is the shared spelling. */
    .req { color: var(--red); margin-left: 3px; font-weight: 700; }
    /* Helper text under a control. text-transform/letter-spacing are reset
       explicitly because a hint can also sit inside a <label>, which is
       uppercase + tracked. */
    .form-hint {
      font-family: var(--font-sans);
      font-size: 11px; line-height: 1.45;
      color: var(--text-dim);
      text-transform: none; letter-spacing: normal; font-weight: 400;
    }
    /* .form-group is a flex column with gap:7px, so the 4px margin-top on
       .field-error would stack on top of the gap and sit the message lower
       than the hint above it. Inside a form group the gap is the spacing. */
    .form-group > .field-error, .form-group > .form-hint { margin-top: 0; }
    /* Form-level error summary, for the "3 fields were rejected" banner above
       a submit row. Same background/border/color triple as .flash-error
       (app.css:1677) so a validation banner and a flash banner do not read as
       two systems. */
    .form-error {
      display: block;
      font-family: var(--font-sans);
      font-size: 12px; line-height: 1.5;
      color: var(--red);
      background: rgba(224,88,88,0.09);
      border: 1px solid rgba(224,88,88,0.28);
      border-radius: var(--radius);
      padding: 9px 12px;
      margin-bottom: 14px;
    }

    /* ═══════════════════════════════════════════════════════════════════
       DESIGN SYSTEM v2 — Elevation Batch 1: define referenced-but-undefined
       classes. The audit found these used across HECM/AMS/rentals/admin/tasks
       (~84 usages) but NEVER defined — so status pills rendered monochrome,
       card titles as plain body text, and loss cards lost their red signal.
       Defined here on the token system.
    ═══════════════════════════════════════════════════════════════════ */
    /* Generic color badges (used as `class="badge badge-blue"` etc.) */
    /* .badge declares `border: 1px solid` with no colour and the shorthand
       resets border-color to currentColor — so these five, which set `color`
       and nothing else, each wore a solid ring at full saturation of their own
       hue while every other variant softens it to ~30%. One component, two
       silhouettes, depending on which blueprint you were looking at. */
    .badge-blue  { color: var(--blue);    background: color-mix(in oklab, var(--blue) 13%, transparent);
                   border-color: color-mix(in oklab, var(--blue) 30%, transparent); }
    .badge-green { color: var(--success); background: var(--success-glow2);
                   border-color: color-mix(in oklab, var(--success) 30%, transparent); }
    .badge-gold  { color: var(--gold);    background: var(--gold-glow2);
                   border-color: color-mix(in oklab, var(--gold) 30%, transparent); }
    .badge-gray  { color: var(--text-dim); background: var(--surface3);
                   border-color: var(--border-bright); }
    .badge-red   { color: var(--red);     background: rgba(224,88,88,.13);
                   border-color: color-mix(in oklab, var(--red) 30%, transparent); }

    /* Card header (used in ~11 templates) */
    .card-title {
      font-family: var(--font-sans); font-size: 13px; font-weight: 700;
      letter-spacing: .02em; color: var(--text);
      margin-bottom: 12px; display: flex; align-items: center; gap: 8px;
    }

    /* Loss / alert stat-card variant */
    .red-card { border-color: rgba(224,88,88,.35) !important; }
    .red-card .stat-value, .red-card .dg-value { color: var(--red); }

    /* Detail-grid label / value pairs */
    .dg-label {
      font-size: 10px; text-transform: uppercase; letter-spacing: .12em;
      color: var(--text-dim); font-family: var(--font-sans); margin-bottom: 3px;
    }
    .dg-value { font-size: 14px; color: var(--text); }

    /* Tabular monospace utility */
    .font-mono { font-family: var(--font-num); font-variant-numeric: tabular-nums; }

    /* ── Native date/time picker — theme-aware ──────────────────────────────
       Browsers paint the calendar-picker glyph (and the picker popup) for a
       LIGHT UI by default, so on the dark theme the icon is near-black and
       disappears. `color-scheme` tells the browser which palette to use.
       Scoped to date/time inputs so selects, checkboxes, and scrollbars are
       untouched. Default theme is dark; the [data-theme="light"] block flips
       it back for light mode. */
    input[type="date"], input[type="datetime-local"], input[type="month"],
    input[type="time"], input[type="week"] { color-scheme: dark; }
    [data-theme="light"] input[type="date"],
    [data-theme="light"] input[type="datetime-local"],
    [data-theme="light"] input[type="month"],
    [data-theme="light"] input[type="time"],
    [data-theme="light"] input[type="week"] { color-scheme: light; }
    /* Comfortable click target for the glyph in both themes. */
    input[type="date"]::-webkit-calendar-picker-indicator,
    input[type="datetime-local"]::-webkit-calendar-picker-indicator,
    input[type="month"]::-webkit-calendar-picker-indicator,
    input[type="time"]::-webkit-calendar-picker-indicator,
    input[type="week"]::-webkit-calendar-picker-indicator { cursor: pointer; opacity: .85; }
    input[type="date"]::-webkit-calendar-picker-indicator:hover,
    input[type="datetime-local"]::-webkit-calendar-picker-indicator:hover,
    input[type="month"]::-webkit-calendar-picker-indicator:hover,
    input[type="time"]::-webkit-calendar-picker-indicator:hover,
    input[type="week"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }

    /* ── Accessibility + touch floor (UI-review follow-ups) ─────────────────
       Systemic, additive — desktop mouse/pointer layout is unchanged. */
    /* Global visible focus ring. Previously only an allowlist of buttons got one,
       so icon buttons, plain links, and dismiss × fell back to the near-invisible
       UA outline on the dark surfaces and keyboard users lost their place. */
    :focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
    /* Fields already signal focus via border + glow — keep them outline-free. */
    input:focus-visible, select:focus-visible, textarea:focus-visible { outline: none; }
    /* …but checkboxes and radios do NOT: the input[type="checkbox"] reset below
       strips their border and box-shadow, and it sits later in the file so its
       box-shadow:none beats the input:focus glow. Combined with the outline:none
       above they were the only interactive controls in the app with no focus
       indicator whatsoever — including the row-select boxes that drive bulk edit
       and the investor packet. Specificity (0,2,1) wins wherever this lands. */
    input[type="checkbox"]:focus-visible,
    input[type="radio"]:focus-visible {
      outline: 2px solid var(--focus);
      outline-offset: 2px;
    }

    /* Checkboxes & radios must NOT inherit the full-width, padded text-input box
       (input{width:100%;padding:10px 13px} above) — that stretches the control
       to 100% and collapses any adjacent label text into a vertical sliver.
       The attribute selector outranks the bare `input` rule, so this wins. */
    input[type="checkbox"], input[type="radio"] {
      width: auto !important; min-width: 0 !important; height: auto;
      padding: 0 !important; margin: 0; flex: 0 0 auto;
      background: none; border: none; border-radius: 0; box-shadow: none;
      appearance: auto; -webkit-appearance: auto;
      accent-color: var(--primary); vertical-align: middle; cursor: pointer;
    }

    @media (pointer: coarse) {
      /* iOS Safari zooms the viewport when a focused field is < 16px, and stays
         zoomed. Bump form fields to 16px on touch devices only. */
      input, select, textarea, .form-control, .form-control-sm { font-size: 16px; }
      /* 44px minimum touch targets for the dense row-level icon actions
         (pay / edit / delete) without disturbing desktop pixel sizing. */
      .btn, .btn-sm, .filter-btn, .tab-btn, .page-btn, button { min-height: 44px; }
      /* …which also stretched the invisible (opacity:0) pin button to a 44px
         strip down the right edge of every nav row. There is no hover on
         touch to reveal it, so tapping the right of a nav link pinned the
         page instead of opening it. Pinning is a desktop affordance. */
      .nav-pin-btn { display: none !important; }
      /* The blanket `button { min-height: 44px }` above also stretched the two
         round sidebar handles — 22px and 26px wide — into overlapping 22x44
         and 26x44 ovals sitting on top of each other. They are edge handles,
         not row actions, and they already clear the 24px AAA target. */
      .sidebar-toggle, .sidebar-mini-toggle { min-height: 0 !important; }
      /* 37px drawer rows -> 44px. */
      .nav-link { padding-top: 12px; padding-bottom: 12px; }
      /* Icon-only buttons: the min-height above made them 44px tall but left
         them glyph-wide. Square hit areas, grown into the surrounding padding
         so row heights do not change. */
      .help-btn { width: 48px; height: 48px; }
      .help-panel-close, .flash-dismiss, .toast-close, #kbModalClose,
      .modal-close, .db-photo-x, .contact-copy, .watch-star {
        min-width: 44px; min-height: 44px;
        display: inline-flex; align-items: center; justify-content: center;
      }
      /* The bare `button` selector above clamps height to 44px but leaves the
         inline width:28px alone, and border-radius:50% resolves per axis — so
         the accent picker became a row of 28x44 ovals on every phone. Keep
         them circular and give them a real target by spacing, not by size. */
      .accent-swatch {
        min-height: 28px !important; min-width: 28px;
        margin: 8px;
      }
      .flash-dismiss, .toast-close, .help-panel-close, .modal-close { margin: -8px -8px -8px auto; }
      .contact-copy { opacity: 1; font-size: 15px; }
      /* No hardware keyboard: the shortcut chips are noise. */
      .nav-search-kbd, #cmd-kbd-hint { display: none; }
    }

    /* Topbar action groups don't wrap, so several buttons overflow a phone and
       force horizontal body scroll; and dense inline KPI grids only collapsed at
       700px, leaving the 701–900px tablet band crushed. Step them down: 2 cols on
       tablet, 1 col on phones. (The 700px rule is last so it wins ≤700px.) */
    /* The detail pages hard-code multi-column grids INLINE
       (grid-template-columns:280px 1fr, repeat(5,1fr), …), which class-based
       breakpoints cannot reach — hence the attribute-substring selector, and
       the !important that beats a non-important inline style. Scoped to .main
       so shell/nav chrome is never affected. Two columns on tablet, one on
       phones; the 700px block is last so it wins below 700px. */
    /* A grid item's automatic minimum size is its content's min-content
       width, so a `1fr` track holding a wide table refuses to shrink and the
       PAGE scrolls sideways — at every width, not just on phones. Fifth
       instance of this trap in the codebase: /hecm/<id> at 1440px measured
       1623px inside a 1422px viewport (its `1fr 320px` grid pushed the
       320px sidebar off-screen); 1422 after. The wide content still has
       somewhere to go — .table-sticky-wrap and .card:has(.data-table) scroll
       it inside the column — so nothing becomes unreachable. 64 inline
       two-column grids in the templates share this shape. */
    .main [style*="grid-template-columns"] > * { min-width: 0; }
    @media (max-width: 900px) {
      .topbar > div { flex-wrap: wrap; }
      .main [style*="grid-template-columns"]:not(.stats-bar) { grid-template-columns: 1fr 1fr !important; }
    }
    @media (max-width: 700px) {
      /* :not(.stats-bar) — the KPI band keeps its own 3/2-column phone rules
         above; this (0,2,0) rule was beating them and stacking every tile. */
      .main [style*="grid-template-columns"]:not(.stats-bar) { grid-template-columns: 1fr !important; }
    }

    /* ── CARD-ROW LIST MODE (phones) ──────────────────────────────────────
       Opt-in per table with `class="data-table is-cards"`. Below 700px a
       12-column .data-table is a 1278px spreadsheet panned inside a 328px
       card with a thumb. Here each <tr> becomes a card instead: the
       template marks the cells that lead (mc-id / mc-sub / mc-status /
       mc-money / mc-date / mc-act, plus mc-sel for a bulk checkbox) and the
       grid places them; every other cell auto-flows into a two-column
       label/value grid underneath, its label drawn from data-label, so the
       <thead> can go. Nothing in here applies above 700px and the extra
       classes/attributes are inert there, so desktop is untouched. The
       !important rules exist only to beat the inline text-align /
       font-size styles the three list templates put on their cells. */
    @media (max-width: 700px) {
      .table-sticky-wrap:has(> .data-table.is-cards) { max-height: none; overflow: visible; }
      .data-table.is-cards,
      .data-table.is-cards tbody { display: block; width: 100%; }
      .data-table.is-cards thead { display: none; }
      .data-table.is-cards tbody tr {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        grid-template-areas: "id id" "sub sub" "status money" "date act";
        column-gap: var(--sp-5);
        align-items: center;
        padding: var(--sp-6) var(--sp-7);
        border-bottom: 1px solid var(--border);
        contain-intrinsic-size: auto 220px;
      }
      .data-table.is-cards tbody tr:last-child { border-bottom: 0; }
      .data-table.is-cards tbody tr:nth-child(even) { background: var(--zebra); }
      /* Cells shed their table chrome — padding, row line, zebra — the
         card carries it now… */
      .data-table.is-cards tbody tr td {
        display: block; min-width: 0; margin-top: var(--sp-4);
        padding: 0; border: 0; background: none;
        font-size: var(--fs-3); white-space: normal;
        text-align: left !important;
      }
      /* …and the hover tint, first-cell rail and tier stripe, which the
         desktop rules attach at a higher specificity. Only those three
         properties, so the mc-* rules below still win on everything else. */
      .data-table.is-cards tbody tr:hover td { background: none; }
      .data-table.is-cards tbody tr td:first-child,
      .data-table.is-cards tbody tr:hover td:first-child { border-left: 0; box-shadow: none; }
      .data-table.is-cards td[data-label] { align-self: start; }
      .data-table.is-cards td[data-label]::before {
        content: attr(data-label);
        display: block; margin-bottom: 2px;
        font-family: var(--sans); font-size: var(--fs-1); font-weight: 700;
        letter-spacing: .12em; text-transform: uppercase; color: var(--text-dim);
      }
      .data-table.is-cards td.mc-id     { grid-area: id; margin-top: 0; font-size: var(--fs-5) !important; font-weight: 600; }
      .data-table.is-cards td.mc-sub    { grid-area: sub; margin-top: 2px; font-size: var(--fs-2); color: var(--text-dim); }
      .data-table.is-cards td.mc-status { grid-area: status; }
      .data-table.is-cards td.mc-money  { grid-area: money; align-self: center; font-size: var(--fs-6); text-align: right !important; }
      .data-table.is-cards td.mc-money .money-chip { font-size: inherit; }
      .data-table.is-cards td.mc-date   { grid-area: date; align-self: center; font-size: var(--fs-4) !important; }
      .data-table.is-cards td.mc-date .date-chip { font-size: inherit; }
      .data-table.is-cards td.mc-act    { grid-area: act; justify-self: end; text-align: right !important; }
      /* Bulk-select checkbox: a 44px strip down the card's left edge, box
         level with the borrower's name. The <tr> is already
         position:relative, so the cell leaves the grid; the row-click
         handler in index.html still toggles it from anywhere on the card
         that is not a link or a button. */
      .data-table.is-cards tbody tr:has(> td.mc-sel) { padding-left: 44px; }
      .data-table.is-cards td.mc-sel {
        position: absolute; left: 0; top: var(--sp-5); bottom: 0; width: 44px; margin: 0;
        display: flex; align-items: flex-start; justify-content: center;
      }
      .data-table.is-cards td.mc-sel input { width: 22px !important; height: 22px; }
      /* Touch targets: anything tappable inside a card is at least 44px.
         .btn already gets this under (pointer: coarse); repeated here so a
         narrow window / DevTools phone emulation behaves the same. Plain
         text links (borrower name, Loan #, TS#) keep their size and line
         box but grow a 15px hit area above and below — the classic
         padding/negative-margin pair, so nothing around them moves. */
      .data-table.is-cards td .btn,
      .data-table.is-cards td a.flag-pill,
      .data-table.is-cards td .bid-quick-chip,
      .data-table.is-cards td select { min-height: 44px; }
      .data-table.is-cards td .btn { min-width: 44px; justify-content: center; }
      .data-table.is-cards td a:not(.btn):not(.flag-pill) {
        display: inline-block; padding-block: 15px; margin-block: -15px;
      }
    }

    /* ═══════════════════════════════════════════════════════════════════════
       PREMIUM DETAIL LANGUAGE (v3) — detail hero, KPI tiles, data sheet.
       Showcased on the AMS asset detail first; rolled out to other detail
       pages once approved. Additive: only elements that opt in are affected.
    ═══════════════════════════════════════════════════════════════════════ */
    .detail-hero {
      position: relative; overflow: hidden;
      background:
        radial-gradient(120% 80% at 100% -12%, var(--gold-glow) 0%, transparent 55%),
        linear-gradient(158deg, var(--surface) 0%, var(--surface2) 100%);
      border: 1px solid var(--border); border-radius: var(--radius-lg);
      padding: 18px 20px 20px; margin-bottom: 16px;
      box-shadow: var(--shadow-hero), inset 0 1px 0 0 var(--inner-hi);
      animation: hero-in .5s var(--spring) both;
    }
    @keyframes hero-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
    .detail-hero::before {
      content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
      background: linear-gradient(90deg, transparent 4%, var(--primary) 26%, var(--gold) 70%, transparent 96%);
      box-shadow: 0 1px 7px -1px var(--gold-glow2); opacity: .92;
    }
    .detail-hero-crumb {
      font-size: 10px; font-family: var(--font-num); letter-spacing: .14em;
      text-transform: uppercase; color: var(--text-dim);
      display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px;
    }
    .detail-hero-crumb a { color: var(--text-dim); text-decoration: none; transition: color var(--transition); }
    .detail-hero-crumb a:hover { color: var(--primary); }
    .detail-hero-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
    .detail-hero-title {
      font-family: var(--font-sans); font-size: 21px; font-weight: 700; letter-spacing: -.022em;
      color: var(--text); line-height: 1.12; margin-bottom: 6px; text-wrap: balance;
    }
    .detail-hero-meta { font-size: 12px; font-family: var(--font-num); color: var(--text-dim); letter-spacing: .01em; }
    .detail-hero-meta .dot { opacity: .38; margin: 0 6px; }
    .detail-hero-actions { display: flex; gap: 8px; flex-shrink: 0; flex-wrap: wrap; align-items: center; }
    .detail-hero-asof {
      display: inline-flex; align-items: center; gap: 6px; margin-top: 6px;
      font-family: var(--font-num); font-size: 10px; letter-spacing: .08em;
      text-transform: uppercase; color: var(--text-dim);
    }
    .detail-hero-asof::before {
      content: ''; width: 5px; height: 5px; border-radius: 50%;
      background: var(--success); box-shadow: 0 0 0 3px var(--success-glow);
    }

    /* Status / progress strip (de-inlined from hero markup for reuse) */
    .hero-progress { margin-top: 16px; }
    .hero-progress-head { display: flex; justify-content: space-between; font-size: 9px; color: var(--text-dim); margin-bottom: 6px; letter-spacing: .1em; text-transform: uppercase; }
    .hero-progress-track { height: 4px; background: var(--surface2); border-radius: 2px; overflow: hidden; }
    .hero-progress-fill { height: 4px; border-radius: 2px; background: linear-gradient(90deg, var(--primary), var(--emerald)); animation: hero-fill .7s var(--spring) both; }
    @keyframes hero-fill { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }

    /* Segmented KPI tile row (1px gaps over a border-colored bg = hairline dividers) */
    .kpi-row {
      display: grid; grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); gap: 1px;
      margin-top: 16px; background: var(--border);
      border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden;
      box-shadow: inset 0 1px 0 0 var(--inner-hi);
    }
    .kpi-tile { background: var(--surface); padding: 12px 16px; position: relative; transition: background var(--transition); }
    .kpi-tile::before {
      content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
      background: var(--border-bright); opacity: 0; transition: opacity var(--transition);
    }
    .kpi-tile:has(.k-value.money)::before { background: var(--gold); opacity: .5; }
    .kpi-tile:hover { background: var(--surface2); }
    .kpi-tile:hover::before { opacity: 1; }
    .kpi-tile .k-label {
      font-size: 9px; font-family: var(--font-num); font-weight: 500;
      letter-spacing: .1em; text-transform: uppercase; color: var(--text-mid);
      margin-bottom: 6px; white-space: nowrap;
    }
    .kpi-tile .k-value {
      font-family: var(--font-num); font-variant-numeric: tabular-nums slashed-zero;
      font-size: var(--kpi-size-cell); font-weight: 700; color: var(--text);
      line-height: 1.05; letter-spacing: var(--kpi-track-cell);
    }
    .kpi-tile .k-value:not(.money) { color: var(--text-mid); font-weight: 600; }
    .kpi-tile .k-value.money { color: var(--gold); }
    .kpi-tile .k-sub { font-size: 10px; color: var(--text-dim); margin-top: 4px; }
    /* Lead tile — the single headline figure (Note UPB) */
    .kpi-tile.kpi-lead { background: var(--surface2); }
    .kpi-tile.kpi-lead::after {
      content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 2px; background: var(--gold); opacity: .8;
    }
    .kpi-tile.kpi-lead .k-label { color: var(--text-mid); }
    .kpi-tile.kpi-lead .k-value { font-size: 24px; letter-spacing: -.02em; }

    /* Data sheet — refined label/value rows for detail summaries */
    .dsheet { display: flex; flex-direction: column; }
    .dsheet-row {
      display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
      padding: 8px 0; border-bottom: 1px solid var(--row-line); transition: background var(--transition);
    }
    .dsheet-row:last-child { border-bottom: none; }
    .dsheet-row:hover { background: var(--head-band); box-shadow: inset 2px 0 0 0 var(--primary); }
    .dsheet-row .d-label { font-size: 11px; line-height: 1.35; color: var(--text-dim); letter-spacing: .02em; flex-shrink: 0; }
    .dsheet-row .d-value {
      font-size: 13px; line-height: 1.35; color: var(--text); text-align: right;
      font-variant-numeric: tabular-nums slashed-zero; word-break: break-word; letter-spacing: -.005em;
    }
    .dsheet-row .d-value.money { font-family: var(--font-num); color: var(--gold); font-weight: 600; }
    .dsheet-row .d-value.mono  { font-family: var(--font-num); }
    .dsheet-row .d-value.dim   { color: var(--text-dim); }
    .dsheet-row .d-value.none  { color: var(--text-dim); opacity: .5; font-weight: 400; font-variant-numeric: normal; }
    .k-value.money.neg,  .dsheet-row .d-value.money.neg  { color: var(--red); }
    .k-value.money.zero, .dsheet-row .d-value.money.zero { color: var(--text-dim); font-weight: 500; }
    /* Credit — a reduction to what's owed (suspense, escrow refund, etc.).
       Green means "reduces the debt in the borrower's favor." Prefix the value
       with a minus glyph so the sign reads unambiguously in dark or light mode. */
    .dsheet-row .d-value.money.credit { color: var(--success); }
    /* Lead — the headline number in a dsheet (Total Payoff, etc.). Sized like
       a KPI so the eye lands on the row-total. Kept inside a normal dsheet-row
       so it stacks below the components without a separate primitive. */
    .dsheet-row .d-value.lead { font-size: 17px; font-weight: 700; letter-spacing: -.01em; }
    /* Total row — engraves a hairline above the sum, promotes label weight,
       and suppresses the hover wash so the total feels settled, not clickable. */
    .dsheet-row.d-total {
      border-top: 1px solid var(--border-bright);
      border-bottom: none;
      margin-top: 4px;
      padding-top: 10px;
    }
    .dsheet-row.d-total:hover { background: transparent; box-shadow: none; }
    .dsheet-row.d-total .d-label { color: var(--text); font-weight: 700; font-size: 12px; }
    /* Note — a small dim aside living INSIDE a label (e.g., "credit", "from 2026-06-01").
       Kept intentionally small so the label's information stays scannable. */
    .d-note {
      display: inline-block; margin-left: 4px;
      font-size: 10px; font-weight: 400; color: var(--text-dim); letter-spacing: 0;
      text-transform: none;
    }

    /* ── Asset-info (ai-*) typography scale ─────────────────────────────
       Uniform type stack for detail-panel label/value pairs so the whole
       card reads as one system. Used in ams_detail's merged Asset Info +
       Contact Information card; portable to any other detail panel that
       isn't quite a full .dsheet but needs the same visual language. */
    .ai-label {
      font-size: 11px; font-weight: 700;
      letter-spacing: .06em; text-transform: uppercase;
      color: var(--text-dim); line-height: 1.4;
      margin-bottom: 4px;
    }
    .ai-value {
      font-size: 13px; font-weight: 500;
      color: var(--text); line-height: 1.45;
      font-variant-numeric: tabular-nums slashed-zero;
    }
    .ai-value.mono { font-family: var(--font-num); }
    .ai-sub {
      font-size: 11px; font-weight: 400;
      color: var(--text-dim); line-height: 1.5;
    }
    .ai-sub.mono { font-family: var(--font-num); }
    .ai-group-head {
      font-size: 10px; font-weight: 800;
      letter-spacing: .14em; text-transform: uppercase;
      line-height: 1.4;
    }

    /* ── Premium hero ────────────────────────────────────────────────
       Branded welcome / section header used on top-level pages
       (Home Dashboard, Investor Portfolio, and future landing surfaces).
       Extract of two inline-styled duplicates that were drifting apart:
         • gradient surface  • top accent stripe  • ambient corner glow
         • .eyebrow micro-label  • page-title inline  • sub line
       Optional .premium-hero-actions slot floats to the right on wide
       screens, wraps below the title on narrow. */
    .premium-hero {
      background: linear-gradient(135deg, var(--surface) 0%, var(--surface2) 100%);
      border: 1px solid var(--border);
      border-radius: var(--radius-lg);
      padding: 22px 28px;
      margin-bottom: 22px;
      position: relative;
      overflow: hidden;
    }
    .premium-hero::before {
      content: '';
      position: absolute;
      top: 0; left: 0; right: 0;
      height: 2px;
      background: linear-gradient(90deg, transparent 0%, var(--emerald) 30%, var(--gold) 70%, transparent 100%);
    }
    .premium-hero::after {
      content: '';
      position: absolute;
      top: -40px; right: -40px;
      width: 160px; height: 160px;
      background: radial-gradient(circle, rgba(79,142,247,0.06) 0%, transparent 70%);
      pointer-events: none;
    }
    .premium-hero-row {
      display: flex; align-items: center; justify-content: space-between;
      flex-wrap: wrap; gap: 12px;
      position: relative;
    }
    .premium-hero-sub {
      font-size: 12px;
      color: var(--text-dim);
      margin-top: 4px;
      font-family: var(--font-num);
    }
    .premium-hero-actions { display: flex; gap: 8px; flex-wrap: wrap; }

    /* Small micro-label used above section titles and inside .premium-hero.
       Standalone class so it can live outside a hero too (future section
       eyebrows). */
    .eyebrow {
      font-size: 11px;
      font-family: var(--font-num);
      letter-spacing: .12em;
      text-transform: uppercase;
      color: var(--text-dim);
      margin-bottom: 5px;
    }
    .eyebrow-gold { color: var(--gold); }

    /* ── P&L / signed-money value ────────────────────────────────────
       "Is this profitable?" indicator. Colors follow the sign of the
       value: green above zero, red below, dim at exactly zero. Applied
       via the signed_money() / pct_signed() macros in _components.html
       so no template ever hand-rolls the "if x>=0 emerald else red"
       inline ternary again. */
    .pnl-value {
      font-family: var(--font-num);
      font-variant-numeric: tabular-nums slashed-zero;
      font-weight: 600;
    }
    .pnl-pos  { color: var(--success); }
    .pnl-neg  { color: var(--red); }
    .pnl-zero { color: var(--text-dim); font-weight: 500; }

    /* ── Party strip / party card ─────────────────────────────────────
       Third-party record row (Servicer / Attorney / Portfolio / Counsel
       / Broker) shown as a same-width strip of labeled cards separated
       by vertical borders. Legacy asset_detail's Servicer/Attorney/
       Portfolio strip is the first customer; future asset-detail
       redesigns can drop the same primitive in. */
    .party-strip {
      display: flex; gap: 0;
      border-bottom: 1px solid var(--border);
    }
    .party-card {
      flex: 1;
      padding: 12px 26px;
      border-right: 1px solid var(--border);
      display: flex; flex-direction: column; gap: 3px;
    }
    .party-card:last-child { border-right: none; }
    .party-label {
      font-size: 9px;
      font-weight: 700;
      letter-spacing: 0.16em;
      text-transform: uppercase;
      color: var(--text-mid);
    }
    .party-name {
      font-size: 13px;
      color: var(--text);
      display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
    }
    .party-name-mono { font-family: var(--font-num); }
    .party-phone {
      font-family: var(--font-num);
      font-variant-numeric: tabular-nums slashed-zero;
      font-size: 11px;
      color: var(--text-dim);
    }

    /* ── Timeline entry ───────────────────────────────────────────────
       Presentation-only pattern for chronological event lists — notes,
       auction/status/stage history, audit trails. Consolidates 4 hand-
       rolled copies of the same colored-left-border card across the RE
       module and gives every consumer one hover, one type scale, and
       one place to add future features (edit, comment count, etc.). */
    .timeline {
      display: flex; flex-direction: column; gap: 10px;
    }
    .timeline-entry {
      position: relative;
      padding: 10px 14px;
      background: var(--surface2);
      border-radius: 6px;
      border-left: 3px solid var(--border);
      transition: background var(--transition);
    }
    .timeline-entry:hover { background: var(--surface3); }
    .timeline-entry--ok      { border-left-color: var(--emerald); }
    .timeline-entry--warn    { border-left-color: var(--gold); }
    .timeline-entry--bad     { border-left-color: var(--red); }
    .timeline-entry--neutral { border-left-color: var(--border-bright); }
    .timeline-entry-head {
      display: flex; align-items: center; gap: 10px;
      font-size: 11px;
      margin-bottom: 6px;
    }
    .timeline-entry-title {
      font-weight: 600; color: var(--text); letter-spacing: -.005em;
    }
    .timeline-entry-title.is-ok    { color: var(--emerald); }
    .timeline-entry-title.is-warn  { color: var(--gold); }
    .timeline-entry-title.is-bad   { color: var(--red); }
    .timeline-entry-time {
      margin-left: auto;
      font-family: var(--font-num);
      font-size: 10px; color: var(--text-dim);
      font-variant-numeric: tabular-nums slashed-zero;
      white-space: nowrap;
    }
    .timeline-entry-body {
      font-size: 12px; line-height: 1.55; color: var(--text);
      margin-bottom: 4px; white-space: pre-wrap; word-break: break-word;
    }
    .timeline-entry-foot {
      font-size: 10px; color: var(--text-dim);
      font-family: var(--font-num); margin-top: 4px;
    }
    .timeline-empty {
      font-size: 12px; color: var(--text-dim);
      text-align: center; padding: 20px 0; font-style: italic;
    }

    /* ── Contact card ─────────────────────────────────────────────────
       Presentation-only pattern for linked CRM contacts (borrower + kin)
       inside the AMS Asset Info panel. Splits phones + emails into their
       own labeled columns so operators scan each channel independently
       instead of triaging a run-on line of ☎ · ☎ · ☎ · ✉ · ✉. Emerald
       accent for the borrower, amber for next-of-kin. */
    .contact-card {
      padding: 12px 14px;
      background: var(--surface2);
      border-radius: 8px;
      border-left: 2px solid var(--emerald);
    }
    .contact-card--kin { border-left-color: var(--amber); }

    .contact-card-head {
      display: flex; justify-content: space-between;
      align-items: baseline; gap: 10px; flex-wrap: wrap;
      margin-bottom: 10px;
    }
    .contact-name {
      font-size: 14px; font-weight: 600;
      color: var(--text); text-decoration: none;
      letter-spacing: -.005em;
    }
    .contact-name:hover { color: var(--primary); }
    .contact-age {
      font-size: 11px; color: var(--text-dim);
      margin-left: 8px; font-weight: 400;
    }
    .contact-source {
      font-size: 10px; font-family: var(--font-num);
      color: var(--text-dim);
      font-variant-numeric: tabular-nums slashed-zero;
    }

    .contact-channels {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 14px;
      padding: 4px 0 2px;
      border-top: 1px solid var(--border);
      border-bottom: 1px solid var(--border);
      margin-bottom: 10px;
    }
    /* Single-channel contacts stack cleanly instead of leaving one column
       blank + centered. */
    .contact-channels:has(.contact-channel:only-child) {
      grid-template-columns: 1fr;
    }
    .contact-channel {
      display: flex; flex-direction: column; gap: 2px;
      padding: 8px 0;
    }
    .contact-channel + .contact-channel {
      border-left: 1px solid var(--border);
      padding-left: 14px;
    }
    .contact-channel-head {
      font-size: 10px; font-weight: 800;
      letter-spacing: .12em; text-transform: uppercase;
      color: var(--text-dim);
      margin-bottom: 4px;
    }
    /* Row wraps the click-to-dial/email link + a copy button so the
       copy sits outside the <a> (avoiding nested interactive elements). */
    .contact-row {
      display: flex; align-items: center; gap: 2px;
      margin: 0 -6px;
      border-radius: 4px;
      transition: background var(--transition);
    }
    .contact-row:hover { background: var(--emerald-glow); }
    .contact-link {
      display: flex; align-items: center; gap: 8px;
      padding: 4px 6px;
      flex: 1; min-width: 0;
      border-radius: 4px;
      font-size: 13px; color: var(--emerald);
      text-decoration: none;
      font-family: var(--font-num);
      font-variant-numeric: tabular-nums slashed-zero;
      transition: color var(--transition);
    }
    .contact-link-icon {
      font-family: var(--font-sans);
      font-size: 12px;
      color: var(--text-dim);
      width: 14px; text-align: center;
      flex-shrink: 0;
    }
    .contact-row:hover .contact-link-icon { color: var(--emerald); }
    .contact-link-value { flex: 1; overflow-wrap: anywhere; }
    .contact-link-tag {
      font-family: var(--font-sans);
      font-size: 9px; font-weight: 700;
      letter-spacing: .08em; text-transform: uppercase;
      color: var(--text-dim);
      background: var(--surface3);
      padding: 1px 6px; border-radius: 8px;
      flex-shrink: 0;
    }

    /* Copy button — subtle at rest, comes forward on row hover.
       Uses opacity so the row layout stays stable (no shift on reveal). */
    .contact-copy {
      flex-shrink: 0;
      background: transparent;
      border: 1px solid transparent;
      border-radius: 4px;
      color: var(--text-dim);
      cursor: pointer;
      font-size: 12px; line-height: 1;
      padding: 4px 7px;
      margin-right: 4px;
      opacity: 0;
      transition: opacity var(--transition), background var(--transition),
                  border-color var(--transition), color var(--transition);
    }
    .contact-row:hover .contact-copy,
    .contact-copy:focus-visible {
      opacity: 1;
    }
    .contact-copy:hover {
      background: var(--surface);
      border-color: var(--border-bright);
      color: var(--text);
    }
    .contact-copy.is-copied {
      opacity: 1;
      color: var(--pos, #34d399);
      border-color: transparent;
    }

    .contact-address {
      display: flex; align-items: center; gap: 8px;
      font-size: 12px; color: var(--text-mid);
      line-height: 1.5;
    }
    .contact-address > span[aria-hidden] {
      font-size: 10px; color: var(--text-dim);
      width: 14px; text-align: center; flex-shrink: 0;
    }

    .contact-notes { margin-top: 8px; }
    .contact-notes > summary {
      font-size: 11px; color: var(--text-dim);
      cursor: pointer;
      padding: 4px 0;
      list-style: none;
    }
    .contact-notes > summary::before {
      content: "▶";
      display: inline-block;
      font-size: 8px; margin-right: 6px;
      transition: transform var(--transition);
      transform: translateY(-1px);
    }
    .contact-notes[open] > summary::before { transform: translateY(-1px) rotate(90deg); }
    .contact-notes-body {
      font-size: 12px; color: var(--text-mid);
      margin-top: 6px; padding: 10px 12px;
      background: var(--surface3);
      border-radius: 6px;
      line-height: 1.55;
      white-space: pre-wrap;
    }

    /* Canonical form-label — was used across 100+ places with individual
       inline font-size:11px overrides. Setting a real rule here retires
       that per-instance styling and gives every form label the same face. */
    .form-label {
      display: block;
      font-size: var(--fs-2); font-weight: 700;
      letter-spacing: .06em; text-transform: uppercase;
      color: var(--text-dim); line-height: 1.4;
      margin-bottom: var(--sp-1);
    }
    .form-label--plain {
      /* Escape hatch for a form label that should read as a value, not a
         section header (e.g. below a heading that already covers the label). */
      font-size: 12px; font-weight: 500;
      letter-spacing: normal; text-transform: none;
      color: var(--text);
    }

    /* Status-dot system (token-driven; a neutral marker for status-type fields) */
    .d-dot, .k-dot {
      display: inline-block; width: 7px; height: 7px; border-radius: 50%;
      margin-right: 7px; vertical-align: baseline; position: relative; top: -1px; background: var(--text-dim);
    }
    .d-dot.ok,   .k-dot.ok   { background: var(--success); box-shadow: 0 0 0 3px var(--success-glow); }
    .d-dot.info, .k-dot.info { background: var(--emerald); box-shadow: 0 0 0 3px var(--emerald-glow); }
    .d-dot.warn, .k-dot.warn { background: var(--amber); }
    .d-dot.bad,  .k-dot.bad  { background: var(--red); }

    /* Motion guard — the two hero entrances only */
    @media (prefers-reduced-motion: reduce) {
      .detail-hero { animation: none; }
      .hero-progress-fill { animation: none; clip-path: none; }
    }

    /* ── Global print ──────────────────────────────────────────────────────
       <html data-theme="dark"> is the hardcoded default, so without this every
       page printed --text (#e8f0f8) onto white paper — legible on 6 of the 120
       base-extending templates, blank on the other 114. Because the design
       system is token-driven, re-pointing the tokens flips the whole page in
       one move; the rules below only handle chrome and pagination.
       The 9 templates with their own @media print keep theirs — they load
       after this block and win. */
    @media print {
      :root, [data-theme], [data-theme="light"], [data-theme="dark"] {
        --bg: #fff; --surface: #fff; --surface2: #fff;
        --surface3: #fafafa; --surface4: #f4f4f4; --card: #fff;
        --border: #bbb; --border-bright: #999;
        --row-line: #ddd; --zebra: transparent; --head-band: #f0f0f0;
        --text: #000; --text-mid: #333; --text-dim: #555; --dim: #666;
        --emerald: #14508c; --emerald-dim: #14508c; --emerald-muted: #5b7fa6;
        --gold: #8a6b1f; --gold-dim: #8a6b1f;
        --emerald-glow: transparent; --emerald-glow2: transparent;
        --gold-glow: transparent; --gold-glow2: transparent;
        --success: #1c6b45; --success-glow: transparent; --success-glow2: transparent;
        --red: #a32b1e; --amber: #8a6b1f; --blue: #14508c; --purple: #5a3f8c;
        /* Chart ramp for paper. This block re-points every other token family
           but a var(--chart-*) with no print value falls through to the dark
           :root hex - --chart-4 #f97316 is 2.87:1 on white, and report.html
           uses it as both a risk-bar fill under white text and (via
           risk_engine.TIERS) a "High Risk" label colour. The report is the
           board-room PDF, so those have to be paper-legible. */
        --chart-1: #1c6b45; --chart-2: #14508c; --chart-3: #8a6b1f;
        --chart-4: #a34a0c; --chart-5: #a32b1e; --chart-neutral: #555;
        --shadow-card: none; --shadow-hero: none; --shadow-raise: none;
        --shadow-glow: none; --inner-hi: none;
        --card-hover-shadow: none; --statcard-hover-shadow: none;
        /* Washes are decorative fills — drop them on paper, same as --zebra.
           Accent/gold ink stays DARK here: browsers do not print backgrounds
           by default, and only .badge/.state-pill/.rec-chip/.money-chip/
           .date-chip/.risk-seg opt into print-color-adjust:exact below — so
           .btn-gold and the inline accent buttons print with no fill, and
           white ink would be invisible on white paper. These values reproduce
           today's printed output exactly. --gold-grad2 tracks the print --gold
           so the .btn-gold gradient stays one flat tone if a user does enable
           background printing. */
        --wash-weak: transparent; --wash: transparent; --wash-strong: transparent;
        --on-accent: #000; --on-gold: #000; --gold-grad2: #8a6b1f;
        /* The credit-state ramp and the module palette were the two token
           families this block forgot. Their dark values (e.g. --st-30
           #c9a83e) land at 2.3:1 on paper — a status pill nobody can read on
           a document handed to a servicer or an attorney. */
        --st-perform:#0b6b48; --st-30:#6f5714; --st-60:#8a4c0f;
        --st-90:#a82f29;      --st-fc:#8a1610;  --st-reo:#41309c;
        --st-bk:#3f4a63;
        --st-perform-weak:#e6f2ec; --st-30-weak:#f4efdf; --st-60-weak:#f6ebdf;
        --st-90-weak:#f7e6e5;      --st-fc-weak:#f6e3e2; --st-reo-weak:#eae7fa;
        --st-bk-weak:#e9ecf3;
        --vault-color:#14508c; --ams-color:#0f5f59; --crm-color:#0b6b48;
        --intel-color:#4c2a9e; --re-color:#7a5a18; --hecm-color:#9c1c1c;
        --agmt-color:#a13508; --rental-color:#0b5c70;
      }
      /* Status is carried by a tinted fill. Browsers drop backgrounds on
         paper by default, which erased the fill and left only the (now
         re-pointed) text color — so keep the fill for the badge families. */
      .badge, .state-pill, .rec-chip, .money-chip, .date-chip, .risk-seg {
        -webkit-print-color-adjust: exact; print-color-adjust: exact;
      }
      @page { margin: 14mm; }
      html, body {
        background: #fff !important; color: #000 !important;
        font-size: 11pt;
      }
      body::before, body::after { display: none !important; }

      /* Chrome that must never reach paper. */
      .no-print, .sidebar, .sidebar-toggle, .sidebar-mini-toggle,
      .mobile-menu-btn, #nav-bar, .topbar, .help-btn, .help-panel,
      #cmd-overlay, #toast-container, #kbModal, #nav-progress,
      .tour-backdrop, .tour-tip, .tour-spotlight, .tour-tooltip,
      .nav-search-wrap, nav, header, footer {
        display: none !important;
      }

      /* The shell reserves 240px for a sidebar that is no longer there. */
      .main {
        margin-left: 0 !important; padding: 0 !important;
        width: auto !important; animation: none !important;
      }
      .card, .stat-card, .data-table, .detail-hero {
        box-shadow: none !important; border-color: #ccc !important;
        break-inside: avoid;
      }
      a { text-decoration: none; color: inherit; }
      /* Keep table headers on every printed page. */
      .data-table thead { display: table-header-group; }
      .data-table thead th { position: static !important; box-shadow: none !important; }
      .table-sticky-wrap { max-height: none !important; overflow: visible !important; }
      tr, img { break-inside: avoid; }
      /* A closed <details> prints closed — CSS cannot force one open, so the
         summary stays visible rather than hiding the label too. */
    }

    /* ═══════════════════════════════════════════
       GRADE CHIP — A/B/C/D recovery grade (hecm_pool_detail)
       Fixed fills, not theme tokens. The chip carries white 10px/800 text, and
       the old token fills (--emerald / --gold / --text-mid / --red) flip value
       between themes, so in dark mode the letter sat at 3.21 / 2.29 / 2.07 /
       3.68:1. These four clear 5:1 against #fff in both themes. Unlike
       .risk-seg, .grade-chip is NOT in this file's print-color-adjust list, so
       it declares its own — without it the fill drops on paper and white text
       lands on white.
    ═══════════════════════════════════════════ */
    .grade-chip {
      display: inline-block; min-width: 18px; text-align: center;
      padding: 1px 6px; border-radius: 3px;
      font-family: var(--mono); font-size: 10px; font-weight: 800;
      line-height: 1.5; color: #fff;
      -webkit-print-color-adjust: exact; print-color-adjust: exact;
    }
    .grade-chip.g-a { background: #2563eb; }   /* 5.17:1 vs #fff */
    .grade-chip.g-b { background: #7a5a18; }   /* 6.36:1 */
    .grade-chip.g-c { background: #4a5568; }   /* 7.53:1 */
    .grade-chip.g-d { background: #b82828; }   /* 6.23:1 */

    /* ═══════════════════════════════════════════
       RISK DISTRIBUTION BAND FILLS (intelligence/report.html)
       Same reasoning as .grade-chip: the count digit lives inside the band in
       white, so the fill cannot be a token that changes value per theme.
       All five clear 4.7:1 against #fff. The inset rule keeps band edges
       visible when hue is gone (greyscale / mono print); the tier initial
       rendered by the template is what actually carries severity. .risk-seg is
       already covered by the print-color-adjust list in the print block above.
    ═══════════════════════════════════════════ */
    .risk-seg--critical { background: #8f1f1f; }   /* 8.81:1 vs #fff */
    .risk-seg--high     { background: #b83232; }   /* 5.93:1 */
    .risk-seg--elevated { background: #9a6a18; }   /* 4.72:1 */
    .risk-seg--moderate { background: #2856a8; }   /* 7.03:1 */
    .risk-seg--low      { background: #1a7a4a; }   /* 5.35:1 */
    .risk-seg + .risk-seg { box-shadow: inset 1px 0 0 rgba(255,255,255,.45); }

    /* ═══════════════════════════════════════════
       SORTABLE COLUMN HEADER — server-side sort links
       One affordance for every <th> that sorts by navigating. There were four
       private copies: index.html (emerald arrow + a dim ⇅ placeholder, defined
       in a <style> block only that page could see), ams_list.html and
       re_ts_today.html (near-identical local macros, gold arrow, no
       placeholder), and re_ts_results_comps.html (a bare anchor, no arrow).
       Rendered by sort_th() / sort_link() in templates/_components.html.

       Deliberately sets no base color: `.data-table a` (0,1,1) already colors
       these links and out-scores a bare class, so leaving it alone keeps every
       screen's idle header exactly the color it is today. Active + hover match
       the client-side .sort-ind ramp above (--emerald).
    ═══════════════════════════════════════════ */
    .sort-th {
      text-decoration: none; cursor: pointer; white-space: nowrap;
      display: inline-flex; align-items: center; gap: 4px;
      transition: color var(--transition);
    }
    .sort-th:hover,
    .data-table th .sort-th:hover,
    .sort-th.is-active,
    .data-table th .sort-th.is-active { color: var(--emerald); }
    .sort-th-arrow { font-size: 9px; line-height: 1; transition: opacity 0.14s ease; }
    .sort-th-arrow.is-idle { opacity: 0.35; }
    .sort-th:hover .sort-th-arrow.is-idle { opacity: 0.85; }
    .sort-th:focus-visible { outline: 2px solid var(--emerald); outline-offset: 2px; border-radius: 3px; }

    /* ═══════════════════════════════════════════════════════════════════
       EMPTY STATE — one component, three densities
       ═══════════════════════════════════════════════════════════════════
       Backs empty_state() in templates/_components.html. The hand-rolled
       markups collapse onto this. .empty / .empty-gem / .empty-section stay
       defined above for the sites not yet migrated (crm/cycles.html still
       uses .empty; ~27 tab/detail screens still use .empty-section) — delete
       them once the last caller is gone, not before.

         --page     a screen came back empty
         --section  a card or tab section is empty
         --row      a table body is empty (caller supplies <td colspan>)
         .is-filtered  filtered/searched to zero — a narrowed view, not an
                       empty dataset, so it gets a quieter icon and never a
                       "create your first" CTA.

       Tokens only: this renders in dark (the default) and light alike. The
       section variant's spec is copied from the surviving .empty-section
       rule + its ::before, so a migrated and an unmigrated section are
       pixel-identical during the rollout. */
    .empty-state{ text-align: center; color: var(--text-dim); }
    .empty-state-icon{ line-height: 1; }
    .empty-state-title{ color: var(--text-mid); font-weight: 700; margin: 0; }
    .empty-state-body{ color: var(--text-dim); margin: 0; }
    .empty-state-actions{ display: flex; gap: 10px; justify-content: center;
      flex-wrap: wrap; }

    .empty-state--page{ padding: 72px 20px; }
    .empty-state--page .empty-state-icon{ font-size: 32px; margin-bottom: 14px;
      color: var(--emerald-dim);                        /* see .empty-gem */
      filter: drop-shadow(0 0 10px var(--emerald-glow2)); }
    .empty-state--page .empty-state-title{ font-size: 16px;
      font-family: var(--display); margin-bottom: 8px; }
    .empty-state--page .empty-state-body{ font-size: 13px; line-height: 1.5;
      max-width: 460px; margin: 0 auto 20px; }
    /* Filtered-to-zero must not read as a first-run screen: drop the hero glow. */
    .empty-state--page.is-filtered .empty-state-icon{ color: var(--text-dim);
      filter: none; opacity: .6; }

    .empty-state--section{ padding: 32px 20px; font-family: var(--mono);
      letter-spacing: 0.02em; border: 1px dashed var(--border);
      border-radius: var(--radius);
      background: repeating-linear-gradient(-45deg, transparent, transparent 6px,
        rgba(255,255,255,0.008) 6px, rgba(255,255,255,0.008) 12px); }
    .empty-state--section .empty-state-icon{ font-size: 18px; opacity: .3;
      margin-bottom: 8px; }
    .empty-state--section .empty-state-title{ font-size: 12px; font-weight: 600; }
    .empty-state--section .empty-state-body{ font-size: 11px; line-height: 1.5;
      margin-top: 5px; }
    .empty-state--section .empty-state-actions{ margin-top: 12px; }

    .empty-state--row{ padding: 24px 14px; font-family: var(--mono);
      letter-spacing: 0.02em; }
    .empty-state--row .empty-state-icon{ font-size: 16px; opacity: .3;
      margin-bottom: 6px; }
    .empty-state--row .empty-state-title{ font-size: 12px; font-weight: 600; }
    .empty-state--row .empty-state-body{ font-size: 11px; line-height: 1.5;
      margin-top: 4px; }
    .empty-state--row .empty-state-actions{ margin-top: 10px; }

    /* ══════════════════════════════════════════════════════════════
       PORTFOLIO ANALYTICS COLLAPSE (/)
       The band is 262.05px above the loan table: 36.5 header row + 201.55
       chart cards (36 padding + 2 border + 27.05 title + a 136.5px canvas,
       which is a 227.5px inner track at Chart.js's 300×180 aspect ratio) +
       4 grid margin + 20 section margin. It now starts collapsed unless the
       user has said otherwise.
       data-charts-hidden is stamped on <html> by the head script in
       base.html before first paint, so nothing renders and then vanishes.
       The button's label is swapped by CSS for the same reason — JS running
       at the bottom of <body> would flash the wrong word first.
    ═════════════════════════════════════════════════════════════ */
    html[data-charts-hidden="1"] #chartsPanel { display: none; }
    .charts-lbl-show { display: none; }
    html[data-charts-hidden="1"] .charts-lbl-hide { display: none; }
    html[data-charts-hidden="1"] .charts-lbl-show { display: inline; }

    /* ══════════════════════════════════════════════════════════════
       KEYBOARD / SCREEN-READER ACCESS
       .skip-link is the first tab stop on every page (base.html). It sits
       off-canvas until focused, then drops in at the top-left over the
       sidebar so a keyboard user can jump past ~40 nav links to <main>.
       .sr-only is the standard visually-hidden utility for text that only
       assistive tech should read (pagination "Page 3", the Cmd+K count).
       Mouse/pointer layout is untouched: nothing here paints unless a
       focus ring would have anyway.
    ═════════════════════════════════════════════════════════════ */
    .skip-link {
      position: absolute; left: -9999px; top: 0;
      z-index: 10500;
      padding: var(--sp-4) var(--sp-7);
      background: var(--surface2); color: var(--text);
      border: 1px solid var(--emerald); border-radius: var(--radius-sm);
      font-family: var(--mono); font-size: var(--fs-4); font-weight: 600;
      text-decoration: none;
    }
    .skip-link:focus {
      left: 8px; top: 8px;
      outline: 2px solid var(--focus); outline-offset: 2px;
    }
    /* <main tabindex="-1"> is only ever focused by the skip link; no ring
       around the whole content column. */
    .main:focus { outline: none; }
    .sr-only {
      position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
      overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
    }

    /* ── Watch star — the one control shared by every trustee-sale list ── */
    /* Trustee-sale rows can be starred from the Today board, a batch grid, the
       property page and the Watchlist itself. One class, one handler
       ([data-watch-toggle] in app.js) — three per-screen copies is how the
       bid-decision chips drifted before they were unified. Dim and quiet
       until hover so an unstarred column reads as empty, not as a row of
       grey stars competing with the score arc. */
    .watch-star {
      background: transparent; border: 1px solid transparent; border-radius: 4px;
      color: var(--text-dim); cursor: pointer; font-size: 15px; line-height: 1;
      padding: 2px 5px; opacity: .38;
      transition: opacity .12s ease, color .12s ease, transform .12s ease;
    }
    @media (hover: hover) {
      .watch-star:hover { opacity: 1; color: var(--gold); transform: scale(1.12); }
    }
    .watch-star:active { opacity: 1; transform: scale(1.12); }
    .watch-star.is-on { opacity: 1; color: var(--gold); }
    .watch-star.is-busy { opacity: .3; pointer-events: none; }
    .watch-star.is-error { color: var(--red); animation: watch-star-shake .3s ease; }
    .watch-star:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; opacity: 1; }
    /* Read-only RE roles get the same glyph as a plain mark, never a button. */
    .watch-star-static { color: var(--gold); font-size: 15px; line-height: 1; }
    td.watch-cell { width: 28px; text-align: center; padding-left: 6px; padding-right: 2px; }
    /* Phones: the star is a <button> but not a .btn, so the card-mode 44px
       touch-target rule above skips it. */
    @media (max-width: 700px) {
      .data-table.is-cards td .watch-star { min-height: 44px; min-width: 44px; font-size: 19px; }
    }
    @keyframes watch-star-shake {
      0%, 100% { transform: translateX(0); }
      30%      { transform: translateX(-2px); }
      60%      { transform: translateX(2px); }
    }
    /* A row leaving the Watchlist collapses rather than vanishing, so the
       eye can track which one went. */
    tr.watch-row-leaving { opacity: 0; transition: opacity .18s ease; }
    @media (prefers-reduced-motion: reduce) {
      .watch-star, tr.watch-row-leaving { transition: none; }
      .watch-star.is-error { animation: none; }
      .watch-star:hover, .watch-star:active { transform: none; }
    }

    /* ── Drive-bys — field visits with three fixed photo slots ─────────── */
    /* A = front, B = right, C = left. The slots are big square targets on a
       phone (that is where they get used — in a car, one-handed) and shrink
       to a strip on desktop. The file input is visually hidden inside the
       label so tapping the tile opens the camera; app.js shows a preview in
       the tile and downscales the image before it is sent. */
    .db-slots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 12px; }
    .db-slot {
      position: relative; aspect-ratio: 4 / 3; max-width: 100%; cursor: pointer; overflow: hidden;
      display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
      border: 1px dashed var(--border); border-radius: var(--radius-sm); background: var(--surface2);
      color: var(--text-dim); text-align: center; transition: border-color .12s ease, color .12s ease;
    }
    .db-slot:focus-within { border-color: var(--emerald); color: var(--text); }
    @media (hover: hover) {
      .db-slot:hover { border-color: var(--emerald); color: var(--text); }
    }
    .db-slot.has-photo { border-style: solid; border-color: var(--emerald); }
    .db-slot-letter {
      font-family: var(--mono); font-weight: 700; font-size: 22px; line-height: 1;
      color: var(--gold); position: relative; z-index: 1;
    }
    .db-slot-letter.sm { font-size: 11px; display: inline-block; min-width: 14px; }
    .db-slot-label { font-size: 12px; font-weight: 600; position: relative; z-index: 1; }
    .db-slot-hint { font-size: 10px; position: relative; z-index: 1; }
    .db-slot.has-photo .db-slot-hint { display: none; }
    .db-slot.has-photo .db-slot-letter, .db-slot.has-photo .db-slot-label {
      color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,.8);
    }
    .db-slot-preview { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
    .db-slot-preview[hidden] { display: none; }
    .db-slot.is-working::after {
      content: 'Shrinking…'; position: absolute; inset: auto 0 0 0; z-index: 2; font-size: 10px;
      padding: 2px 0; background: rgba(0,0,0,.55); color: #fff;
    }
    .db-fields {
      display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)) auto; gap: 8px; align-items: end;
    }
    .db-card-check {
      display: inline-flex; align-items: center; gap: 6px; font-size: 12px; white-space: nowrap;
      min-height: 32px; cursor: pointer;
    }
    .db-card-check input { width: 16px; height: 16px; margin: 0; }
    .db-visits { display: flex; flex-direction: column; gap: 16px; }
    .db-visit { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; }
    .db-visit-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
    .db-gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
    .db-photo { margin: 0; min-width: 0; }
    .db-photo a { display: block; aspect-ratio: 4 / 3; overflow: hidden; border-radius: 4px; background: var(--surface2); }
    .db-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
    .db-photo-missing {
      aspect-ratio: 4 / 3; display: flex; align-items: center; justify-content: center;
      border: 1px dashed var(--border); border-radius: 4px; color: var(--text-dim); font-size: 11px;
    }
    .db-photo figcaption { display: flex; align-items: center; gap: 4px; font-size: 11px; margin-top: 4px; color: var(--text-dim); }
    .db-photo-x {
      background: none; border: 0; color: var(--text-dim); cursor: pointer; font-size: 11px;
      padding: 2px 6px; border-radius: 3px;
    }
    .db-photo-x:hover { color: var(--red); background: var(--surface2); }
    .db-fill { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
    .db-fill label { cursor: pointer; }
    .db-note { font-family: var(--font-num); font-size: 12px; margin-top: 8px; white-space: pre-wrap; }
    @media (max-width: 700px) {
      .db-fields { grid-template-columns: 1fr 1fr; }
      .db-fields .form-group:nth-child(3) { grid-column: 1 / -1; }
      .db-card-check { grid-column: 1 / -1; min-height: 44px; }
      .db-slot { aspect-ratio: 1; }
      .db-slot-letter { font-size: 28px; }
    }
    @media (prefers-reduced-motion: reduce) { .db-slot { transition: none; } }

    /* ═══════════════════════════════════════════════════════════════════
       PLATFORM PASS — Windows PC + Samsung Galaxy first, iPhone must work.
       Appended per convention; everything platform-conditional is gated on
       a media/supports query so the desktop look is untouched.
    ═══════════════════════════════════════════════════════════════════ */

    /* ── Colour scheme ─────────────────────────────────────────────────────
       Declared at the root so every UA-drawn part follows the theme: on
       Windows Chrome/Edge the page scrollbar, <select> popups, date pickers,
       the file "Choose File" button and autofill; on Samsung Internet the
       "dark mode for web content" inverter leaves a page alone only when it
       says it already has a dark scheme. Print stays light. */
    :root { color-scheme: dark; }
    :root[data-theme="light"] { color-scheme: light; }
    @media print { :root, :root[data-theme] { color-scheme: light; } }
    input[type="file"]::file-selector-button {
      background: var(--surface3); color: var(--text);
      border: 1px solid var(--border-bright); border-radius: var(--radius-sm);
      padding: 4px 10px; margin-right: 10px; font: inherit; cursor: pointer;
    }

    /* ── Autofill ──────────────────────────────────────────────────────────
       Chromium (Windows + Android) and Samsung Pass repaint a filled field
       pale blue with black text via a UA !important background. An inset
       shadow paints over it and -webkit-text-fill-color beats the colour. */
    input:-webkit-autofill,
    input:-webkit-autofill:hover,
    input:-webkit-autofill:focus,
    textarea:-webkit-autofill,
    select:-webkit-autofill {
      -webkit-box-shadow: 0 0 0 1000px var(--surface2) inset;
      box-shadow: 0 0 0 1000px var(--surface2) inset;
      -webkit-text-fill-color: var(--text);
      caret-color: var(--text);
      border-color: var(--emerald-dim);
      transition: background-color 600000s 0s;
    }

    /* ── Touch: hover is not a state ──────────────────────────────────────
       Chromium on Android and iOS Safari keep :hover on the last tapped
       element until the next tap, so every lifted card, raised button and
       highlighted nav row stayed that way after a tap. Neutralise the
       hover-only transforms/highlights where there is no hover-capable
       pointer; :active keeps a brief press feedback. Windows mice, DeX with
       a mouse and touch laptops report hover:hover and are unaffected. */
    @media (hover: none) {
      .btn:hover, .help-btn:hover, .card:hover, .stat-card:hover,
      .stat-card.gold-card:hover { transform: none; }
      .btn:hover { box-shadow: none; filter: none; }
      .card:hover { border-color: var(--border); box-shadow: var(--shadow-card); }
      .card:hover::before { background: none; }
      .stat-card:hover, .stat-card.gold-card:hover { border-color: var(--border); box-shadow: var(--shadow-card); }
      .help-btn:hover { background: var(--surface2); border-color: var(--border-bright); color: var(--text-mid); box-shadow: 0 2px 14px rgba(0,0,0,.45); }
      .nav-link:hover:not(.active) { background: transparent; color: var(--text-mid); border-left-color: transparent; }
      .filter-btn:hover:not(.active) { background: transparent; border-color: var(--border); color: var(--text-mid); }
      .detail-grid:hover .detail-item:not(:hover) { opacity: 1; }
      .btn-ghost:hover { background: transparent; border-color: var(--border); color: var(--text-mid); box-shadow: none; }
      .nav-link:active { background: var(--emerald-glow); }
      .cmd-item:active { background: var(--surface2); }
      .db-slot:active { border-color: var(--emerald); }
    }
    /* Android paints its own grey tap flash over controls that already have
       press feedback — two flashes per press. Plain text links keep it. */
    .btn, .btn-ghost, .btn-danger, .watch-star, .filter-btn, .tab-btn, .page-btn,
    .mobile-menu-btn, .nav-link, .cmd-item, .db-slot, .help-btn, .nav-search-btn,
    .stat-card, tr[data-href], .div-switcher a {
      -webkit-tap-highlight-color: transparent;
    }

    /* ── Scroll chaining / pull-to-refresh ────────────────────────────────
       A flick that reaches the top of an inner scroller (table wrapper, nav
       drawer, palette results) chained to the document and fired Chrome
       Android's pull-to-refresh, reloading a page mid-form. Contain it at
       the root and in every inner scroller. Pinch-zoom is untouched. */
    html { overscroll-behavior-y: contain; }
    .table-sticky-wrap, .sidebar-nav, #cmd-results, .help-panel-body,
    .modal-card, .modal-overlay, #cmd-overlay { overscroll-behavior: contain; }
    #cmd-overlay { touch-action: pinch-zoom; }
    #cmd-results { touch-action: pan-y; }

    /* ── Short desktop viewports ──────────────────────────────────────────
       1366×768 @125% (the common Windows laptop) leaves ~490px of page:
       calc(100vh - 260px) capped list tables at 5 rows inside a scrolling
       page. Smaller chrome allowance below 760px tall; svh where supported
       so the cap never overshoots a phone/tablet's visible area. */
    @media (min-width: 901px) and (max-height: 760px) {
      .table-sticky-wrap { max-height: calc(100vh - 120px); }
      @supports (height: 100svh) { .table-sticky-wrap { max-height: calc(100svh - 120px); } }
    }
    @supports (height: 100svh) {
      @media (min-width: 901px) and (min-height: 761px) {
        .table-sticky-wrap { max-height: calc(100svh - 260px); }
      }
    }

    /* ── Phones (≤900px) ──────────────────────────────────────────────────── */
    @media (max-width: 900px) {
      /* The page is the only vertical scroller: a 100vh-capped inner table
         scroller kept the URL bar from collapsing and trapped the thumb. */
      .table-sticky-wrap { max-height: none; }
      .sidebar-nav { max-height: 70svh; }

      /* The account row (theme toggle, profile, Sign out) lives in
         .sidebar-footer, hidden under 900px — so phones had no way to sign
         out. Show it as the last strip of the open drawer. Beats both the
         (0,1,0) hide above and the sidebar-mini !important hide. */
      html:not([data-nav="top"]) body .sidebar-nav.open ~ .sidebar-footer,
      html:not([data-nav="top"]) body.sidebar-mini .sidebar-nav.open ~ .sidebar-footer {
        display: block !important; flex: 0 0 100%; width: 100%;
        padding: 10px 12px 6px; border-top: 1px solid var(--border);
      }
      .sidebar-nav.open ~ .sidebar-footer .sidebar-user {
        display: flex; flex-direction: row; align-items: center;
        justify-content: space-between; gap: 10px;
      }
      .sidebar-nav.open ~ .sidebar-footer .sidebar-legal { display: none; }
      .sidebar-nav.open ~ .sidebar-footer .btn-theme,
      .sidebar-nav.open ~ .sidebar-footer .btn-logout { min-height: 44px; }
      /* Top-nav mode is desktop-only; its toggle stays out of the drawer. */
      .sidebar-nav.open ~ .sidebar-footer #navToggle { display: none; }

      /* Top bar: logo + ☰ share line 1; the Vault/RE switcher, which was
         wrapping the hamburger onto its own row at 360px, gets line 2. */
      .sidebar-logo { order: 0; min-width: 0; }
      .mobile-menu-btn { order: 1; }
      .div-switcher { order: 2; flex: 0 0 100%; padding: 8px 0 0 !important; border-bottom: 0 !important; }
      /* On their own line the pills lost the row's 44px stretch. */
      .div-switcher a { min-height: 40px; display: flex; align-items: center; justify-content: center; }
      .sidebar-nav { order: 3; }
      .sidebar-footer { order: 4; }

      /* Toasts were 280–400px anchored 24px from the right: the first word
         of every long message was off the left edge of a 360px Galaxy. */
      #toast-container { left: 16px; right: 16px; bottom: 76px; }
      .toast { min-width: 0; max-width: none; width: 100%; }
      .help-panel { bottom: 76px; }

      /* Spacious density restated for phones: the (0,2,0) card padding was
         beating the 16px phone rule, and the tokens grew every card row. */
      [data-density="compact"] .card,
      [data-density="spacious"] .card { padding: 16px; }
      [data-density="spacious"] { --sp-6: 14px; --sp-7: 16px; }
    }

    /* Command palette on phones: the soft keyboard takes ~300px of a 700px
       viewport, so hug the top and cap the list against what is left. */
    @media (max-width: 700px) {
      #cmd-overlay { padding-top: 8px; }
      #cmd-box { max-width: none; width: calc(100% - 16px); }
      #cmd-results { max-height: min(340px, 45vh); }
      #cmd-kbd-hint { display: none; }
    }
    #cmd-kbd-hint { flex-wrap: wrap; }

    /* Galaxy S (360px) / S Ultra (384px): the KPI money and the hero lead
       value clipped their last digit; the 390px iPhone width hid it. */
    .kpi-tile { min-width: 0; }
    @media (max-width: 420px) {
      .kpi-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
      .kpi-tile { padding: 12px 12px; }
      .kpi-tile.kpi-lead .k-value { font-size: 20px; }
    }

    /* Galaxy Z Fold inner screen (~673px) and small tablets: card mode with
       one 640px card per row is mostly empty. Two-up between 560 and 700px;
       each card is then ~320px wide — the same geometry as a 360px phone. */
    @media (max-width: 700px) {
      @media (min-width: 560px) {
        .data-table.is-cards tbody {
          display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
          gap: 1px; background: var(--border);
        }
        .data-table.is-cards tbody tr,
        .data-table.is-cards tbody tr:nth-child(even) { background: var(--surface); border-bottom: 0; }
        .data-table.is-cards tbody tr:last-child:nth-child(odd) { grid-column: 1 / -1; }
      }
    }

    /* Drive-by form: size the field grid to its card, not the viewport —
       between 701px and ~1090px the page grid halves the card and the Date /
       Looks / Condition fields shrank to 40–86px. */
    #drive-bys { container-type: inline-size; }
    @container (max-width: 480px) {
      .db-fields { grid-template-columns: 1fr 1fr; }
      .db-fields .form-group:nth-child(3), .db-card-check { grid-column: 1 / -1; }
    }
    @media (max-width: 1100px) and (min-width: 701px) {
      .db-fields { grid-template-columns: 1fr 1fr; }
      .db-fields .form-group:nth-child(3), .db-card-check { grid-column: 1 / -1; }
    }
    /* 360px Galaxy: three 92px tiles were tight for a gloved thumb. The
       front shot spans the row; B and C sit under it. */
    @media (max-width: 420px) {
      .db-slots { grid-template-columns: 1fr 1fr; gap: 12px; }
      .db-slot[data-slot="A"] { grid-column: 1 / -1; aspect-ratio: 16 / 9; }
    }

    /* ── iOS Safari only ──────────────────────────────────────────────────
       -webkit-touch-callout is unimplemented in Blink, so this @supports is
       false on Windows Chrome/Edge, Chrome for Android and Samsung Internet:
       Android keeps its compact 12px fields. Safari zooms the page on any
       focused field under 16px and stays zoomed; !important because ~96
       fields carry an inline font-size. */
    @supports (-webkit-touch-callout: none) {
      @media (pointer: coarse) {
        input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]),
        select, textarea, .search-input, #cmd-input { font-size: 16px !important; }
      }
      /* iOS draws an empty date field as a blank sliver with no placeholder
         (WebKit bug 198959); drop the native pill so our field styling
         applies and keep the inner value box from collapsing. */
      input[type="date"], input[type="datetime-local"], input[type="month"], input[type="time"] {
        -webkit-appearance: none; appearance: none; min-width: 9.5em;
      }
      input[type="date"]::-webkit-date-and-time-value,
      input[type="datetime-local"]::-webkit-date-and-time-value,
      input[type="month"]::-webkit-date-and-time-value,
      input[type="time"]::-webkit-date-and-time-value { text-align: left; min-height: 1.3em; }
    }

    /* Hover decoration that used to be inline onmouseenter/onmouseleave
       handlers (blocked by the nonce CSP). Hover-capable pointers only. */
    @media (hover: hover) {
      .hover-lift:hover { border-color: var(--border-bright) !important; transform: translateY(-1px); }
      .hover-fill:hover { border-color: var(--border-bright) !important; background: var(--surface3) !important; }
      .hover-outline:hover { border-color: var(--emerald-dim) !important; }
    }

    /* Symbol icons: ask for the text (monochrome) presentation so ⚠ ✉ ⚙ ☀
       do not turn into colour emoji on Samsung/iOS. Scoped to icon slots,
       never :root. */
    .nav-icon, .toast-icon, .flash-icon, .help-tip-dot, .cmd-item-icon,
    .myday-icon, .stat-icon, .contact-link-icon, #themeIcon, #navIcon {
      font-variant-emoji: text;
    }

    /* ═══ TASKS: board · calendar · detail (2026-09) ═══════════════════════
       Planner-style task console. Classes are prefixed tb- (board), cal-
       (calendar) and tk- (shared task bits) — never mc-/is-cards, which the
       phone card-mode guard reserves. Phone rules sit in ONE 700px block at
       the end of this section. */

    /* toolbar under the topbar: list chips + grouping */
    .tk-toolbar { display:flex; gap:12px; align-items:flex-start; justify-content:space-between; flex-wrap:wrap; margin-bottom:14px; }
    .tk-toolbar .tk-lists { margin:0; }
    .tk-chip-n { font-family:var(--mono); font-size:9px; opacity:.7; margin-left:5px; }
    .tk-group { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
    .tk-group .form-label { margin:0; }
    .tk-group .form-control-sm { width:auto; }
    .tk-showdone { display:inline-flex; align-items:center; gap:6px; font-size:11px; color:var(--text-mid); white-space:nowrap; letter-spacing:.06em; }
    .tk-switcher .filter-btn { white-space:nowrap; }
    .tk-help { font-size:12px; color:var(--text-mid); line-height:1.5; margin:10px 0 0; }
    .tk-help-list { font-size:12px; color:var(--text-mid); line-height:1.6; margin:6px 0 0 18px; padding:0; }
    .tk-inline { display:inline-flex; align-items:center; gap:8px; font-size:12px; color:var(--text-mid); cursor:pointer;
      font-family:var(--font-sans); font-weight:500; letter-spacing:0; text-transform:none; line-height:1.5; }
    .tk-more > summary { font-size:11px; color:var(--text-dim); cursor:pointer; list-style:none; }
    .tk-more > summary::before { content:"▶"; display:inline-block; font-size:8px; margin-right:6px; transition:transform var(--transition); }
    .tk-more[open] > summary::before { transform:rotate(90deg); }

    /* the ✓ box — one control for list rows, cards, the detail header and checklist items */
    .tk-check {
      position:relative;
      width:22px; height:22px; flex:0 0 22px; border-radius:6px; padding:0;
      border:2px solid var(--border-bright); background:var(--surface2);
      color:transparent; font-size:13px; line-height:1; cursor:pointer;
      display:inline-flex; align-items:center; justify-content:center;
      transition:background var(--transition), border-color var(--transition), color var(--transition);
      min-height:0;
    }
    /* invisible 44px hit area — padding would paint the border/background bigger */
    .tk-check::after { content:""; position:absolute; inset:-11px; }
    .tk-check[aria-pressed="true"], .tk-check.is-static:not(:empty) { background:var(--emerald); border-color:var(--emerald); color:var(--on-accent); }
    .tk-check.is-static { cursor:default; }
    .tk-check.is-busy { opacity:.5; cursor:wait; }
    .tk-check.is-error { border-color:var(--red); }
    .tk-check-lg { width:28px; height:28px; flex-basis:28px; font-size:16px; }
    .tk-prog {
      font-size:9px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
      padding:2px 8px; border-radius:10px; border:1px solid var(--border); background:transparent;
      color:var(--text-dim); cursor:pointer; line-height:1.6; min-height:0; font-family:var(--font-sans);
    }
    .tk-prog[aria-pressed="true"], .tk-prog.is-static { color:var(--amber); border-color:var(--amber); background:rgba(224,158,64,.12); }
    .tk-prog.is-static { cursor:default; margin-left:6px; }
    .tk-prog.is-busy { opacity:.5; }
    .tk-row-title { color:inherit; text-decoration:none; }
    /* long URLs in notes: the table's paint containment clips anything that does not wrap */
    .data-table .contact-notes > summary, .data-table .contact-notes-body { overflow-wrap:anywhere; }

    /* ── board ── */
    .tb-board {
      display:flex; gap:12px; align-items:flex-start;
      overflow-x:auto; overscroll-behavior-x:contain; scroll-snap-type:x proximity;
      padding:2px 2px 12px; margin:0 -2px;
    }
    .tb-col {
      flex:0 0 272px; min-width:0; scroll-snap-align:start;
      background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
    }
    .tb-col-head {
      display:flex; align-items:center; gap:8px; padding:10px 12px;
      font-size:11px; font-weight:800; letter-spacing:.08em; text-transform:uppercase; color:var(--text-mid);
      cursor:pointer; list-style:none; user-select:none;
    }
    .tb-col-head::-webkit-details-marker { display:none; }
    .tb-col-head::before { content:"▶"; font-size:8px; color:var(--text-dim); transition:transform var(--transition); flex:0 0 auto; }
    .tb-col[open] > .tb-col-head::before { transform:rotate(90deg); }
    .tb-col-name { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    .tb-count { margin-left:0; }
    .tb-col-body { padding:0 8px 8px; display:flex; flex-direction:column; gap:8px; min-height:44px; }
    .tb-col.drop-target, .cal-day.drop-target, .cal-side-card.drop-target { box-shadow:inset 0 0 0 2px var(--gold); }
    .tb-card {
      background:var(--surface2); border:1px solid var(--border); border-radius:var(--radius-sm);
      padding:var(--sp-3) var(--sp-4); display:grid; grid-template-columns:auto 1fr; gap:4px 10px; align-items:start;
      transition:border-color var(--transition), opacity var(--transition);
    }
    .tb-card[draggable="true"] { cursor:grab; }
    .tb-card.dragging { opacity:.45; }
    .tb-card.is-busy { opacity:.6; }
    .tb-card.is-done { opacity:.55; }
    .tb-card.is-done .tb-title { text-decoration:line-through; }
    .tb-card.is-progress { border-left:3px solid var(--amber); }
    .tb-body { min-width:0; }
    .tb-title { display:block; font-size:13px; font-weight:600; color:var(--text); text-decoration:none; line-height:1.35; overflow-wrap:anywhere; }
    .tb-meta { display:flex; flex-wrap:wrap; gap:4px 8px; align-items:center; margin-top:4px; font-size:11px; color:var(--text-dim); }
    .tb-glyph { font-size:11px; color:var(--text-dim); }
    .tb-cl.is-full { color:var(--success); }
    .tb-who { font-size:11px; color:var(--text-mid); }
    .tb-due { font-size:11px; color:var(--text-dim); }
    .tb-tag {
      display:inline-block; font-size:9px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
      padding:1px 7px; border-radius:8px; background:var(--surface3); border:1px solid var(--border); color:var(--text-dim);
      max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; vertical-align:middle;
    }
    .tb-actions { display:flex; gap:8px; align-items:center; margin-top:8px; flex-wrap:wrap; }
    .tk-move { font-size:var(--fs-2); padding:3px 6px; min-height:0; flex:1 1 120px; max-width:100%; width:auto; }

    /* ── calendar ── */
    .cal-nav { display:flex; align-items:center; gap:6px; }
    .cal-month { font-size:16px; font-weight:800; margin:0 6px; min-width:150px; text-align:center; letter-spacing:.01em; }
    .cal-layout { display:grid; grid-template-columns:minmax(0,1fr) 220px; gap:16px; align-items:start; }
    .cal-grid { display:grid; grid-template-columns:repeat(7, minmax(0,1fr)); gap:4px; }
    .cal-dow { font-size:10px; font-weight:800; letter-spacing:.08em; text-transform:uppercase; color:var(--text-dim); text-align:center; padding:4px 0; }
    .cal-day {
      min-height:96px; min-width:0; padding:4px; background:var(--surface);
      border:1px solid var(--border); border-radius:var(--radius-sm);
      display:flex; flex-direction:column; gap:2px;
    }
    .cal-day.is-out { opacity:.45; }
    .cal-num { font-family:var(--mono); font-size:11px; color:var(--text-dim); width:22px; height:22px; display:inline-flex; align-items:center; justify-content:center; border-radius:999px; }
    .cal-day.is-today { border-color:var(--emerald); }
    /* --on-accent, not #fff: --emerald is a LIGHT fill in dark theme and a
       dark one in light, and the presets re-point it in both. White on the
       amber preset is 2.15:1; the token is 9.78:1. */
    .cal-day.is-today .cal-num { background:var(--emerald); color:var(--on-accent); font-weight:700; }
    .cal-chip {
      display:block; font-size:var(--fs-1); line-height:1.5; padding:1px 5px; border-radius:4px;
      background:var(--surface3); color:var(--text-mid); text-decoration:none;
      white-space:nowrap; overflow:hidden; text-overflow:ellipsis; border-left:2px solid var(--border-bright);
    }
    .cal-chip.is-hot { border-left-color:var(--amber); }
    .cal-chip.is-overdue { background:rgba(224,88,88,.14); color:var(--red); border-left-color:var(--red); }
    .cal-chip.is-done { text-decoration:line-through; opacity:.55; }
    .cal-chip[draggable="true"] { cursor:grab; }
    .cal-chip.dragging { opacity:.45; }
    .cal-more { font-size:var(--fs-1); color:var(--emerald); text-decoration:none; padding:0 5px; }
    .cal-agenda { display:none; }
    .cal-agenda-day { font-size:11px; font-weight:800; letter-spacing:.08em; text-transform:uppercase; color:var(--text-mid); margin:14px 0 6px; }
    .cal-agenda-day.is-today { color:var(--emerald); }
    .cal-agenda .tb-card { margin-bottom:8px; }
    .cal-side { display:flex; flex-direction:column; gap:12px; }
    .cal-side-card { text-align:center; padding:14px; }
    .cal-side-n { font-family:var(--mono); font-size:26px; font-weight:800; line-height:1; }
    .cal-side-l { font-size:10px; font-weight:800; letter-spacing:.1em; text-transform:uppercase; color:var(--text-dim); margin:4px 0 10px; }
    .cal-side-card.is-overdue .cal-side-n { color:var(--red); }
    .cal-side-hint { font-size:10px; color:var(--text-dim); margin-top:8px; }

    /* ── detail ── */
    .tk-detail { display:grid; grid-template-columns:minmax(0,3fr) minmax(260px,2fr); gap:16px; align-items:start; }
    .tk-detail-title { font-size:inherit; }
    .tk-detail-title.is-done { text-decoration:line-through; opacity:.6; }
    .tk-form { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
    .tk-field { min-width:0; }
    .tk-span { grid-column:1 / -1; }
    .tk-form-actions { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
    .tk-danger { color:var(--red); }
    .tk-static { font-size:13px; padding:6px 0; color:var(--text-mid); }
    .tk-readonly { display:grid; grid-template-columns:auto 1fr; gap:6px 14px; margin:0; font-size:13px; }
    .tk-readonly dt { font-size:10px; font-weight:800; letter-spacing:.08em; text-transform:uppercase; color:var(--text-dim); padding-top:3px; }
    .tk-readonly dd { margin:0; overflow-wrap:anywhere; }
    .tk-section-head { display:flex; align-items:center; gap:8px; margin-bottom:8px; }
    .tk-section-title { font-size:11px; font-weight:800; letter-spacing:.1em; text-transform:uppercase; color:var(--text-mid); }
    .tk-checklist { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:4px; }
    .tk-checklist.is-busy { opacity:.6; }
    .tk-checklist li { display:flex; align-items:center; gap:10px; padding:6px 4px; border-bottom:1px solid var(--border); }
    .tk-checklist li:last-child { border-bottom:0; }
    .tk-checklist li.is-done .tk-item-text { text-decoration:line-through; color:var(--text-dim); }
    .tk-item-text { flex:1 1 auto; min-width:0; font-size:13px; overflow-wrap:anywhere; }
    .tk-x { border:0; background:transparent; color:var(--text-dim); cursor:pointer; font-size:12px; padding:2px 6px; min-height:0; border-radius:4px; }
    .tk-x:focus-visible { outline:2px solid var(--emerald); }
    .tk-empty { font-size:12px; color:var(--text-dim); padding:6px 4px; }
    .tk-check-add { display:flex; gap:8px; margin-top:10px; }
    .tk-check-add .form-control { flex:1 1 auto; min-width:0; }
    .tk-people { display:grid; grid-template-columns:repeat(auto-fill, minmax(260px, 1fr)); gap:10px; }
    .tk-person { display:flex; flex-direction:column; gap:4px; font-size:12px; font-family:var(--font-sans);
      letter-spacing:0; text-transform:none; color:var(--text); }
    .tk-person-name { font-weight:600; }
    .tk-confirm { padding:16px 18px; }

    @media (hover: hover) {
      .tb-card:hover { border-color:var(--border-bright); }
      .cal-chip:hover { color:var(--text); }
      .tk-x:hover { color:var(--red); background:rgba(224,88,88,.12); }
    }
    @media (pointer: coarse) {
      .tk-prog { min-height:44px; padding:4px 12px; }
      .tk-x { min-height:44px; min-width:44px; }
      .tk-move { min-height:44px; font-size:16px; }
      .tk-check-add .btn { min-height:44px; }
    }
    @media (max-width: 900px) {
      .cal-layout { grid-template-columns:1fr; }
      .cal-side { flex-direction:row; }
      .cal-side-card { flex:1 1 0; }
      .tk-detail { grid-template-columns:1fr; }
    }
    @media (max-width: 700px) {
      .tb-board { flex-direction:column; overflow:visible; scroll-snap-type:none; margin:0; padding:0; }
      .tb-col { flex:none; width:100%; }
      .tb-col-head { min-height:44px; }
      .cal-grid { display:none; }
      .cal-agenda { display:block; }
      .cal-nav { width:100%; justify-content:space-between; }
      .cal-month { flex:1 1 auto; min-width:0; }
      .tk-form { grid-template-columns:1fr; }
      .tk-group { width:100%; }
      .tk-group .form-control-sm { flex:1 1 auto; }
      .tk-detail .tb-actions { justify-content:flex-start; }
      /* the inline edit row opens inside card mode: one full-width cell, not half a grid */
      .data-table.is-cards tbody tr.edit-expand { display:block; padding:0 var(--sp-7) var(--sp-6); }
      .data-table.is-cards tbody tr.edit-expand > td { display:block; width:100%; }
    }

    /* ═══════════════════════════════════════════
       HECM ANALYZER: selection chips (2026-09)
       A tape is bid a slice at a time, so the analyzer takes a multi-select
       across states / grades / recovery paths. Checkbox inside the label, so
       the whole chip is the hit area and it works with no JS at all.
    ═══════════════════════════════════════════ */
    .sel-summary {
      display:flex; align-items:center; gap:10px; cursor:pointer;
      list-style:none; min-height:32px;
    }
    .sel-summary::-webkit-details-marker { display:none; }
    /* An 11px dim glyph at the far end of a wide row does not read as a
       control at all. This is a real button-sized target with a border. */
    .sel-summary::after {
      content:"▾"; margin-left:auto;
      display:inline-flex; align-items:center; justify-content:center;
      width:30px; height:30px; flex:0 0 30px;
      font-size:17px; line-height:1;
      color:var(--text); background:var(--surface2);
      border:1px solid var(--border-bright); border-radius:8px;
      transition:transform var(--transition), color var(--transition),
                 border-color var(--transition);
    }
    .sel-summary:hover::after { color:var(--primary); border-color:var(--primary); }
    .sel-card[open] > .sel-summary::after { transform:rotate(180deg); }
    .sel-summary:focus-visible { outline:2px solid var(--primary); outline-offset:3px; }

    /* Each facet is its own section, with what is currently picked stated at
       the top of it — 49 state chips in one undivided wall gave no way to see
       a selection without hunting for the highlighted ones. */
    .sel-sec { padding:12px 0; border-top:1px solid var(--row-line); }
    .sel-sec:first-of-type { border-top:none; padding-top:4px; }
    .sel-sec-head {
      display:flex; align-items:baseline; gap:10px; flex-wrap:wrap; margin-bottom:8px;
    }
    .sel-sec-label {
      font-size:10px; letter-spacing:.11em; text-transform:uppercase;
      color:var(--text-dim); font-weight:700;
    }
    .sel-sec-count {
      font-size:10px; font-family:var(--mono); color:var(--primary); font-weight:700;
    }
    .sel-sec-vals {
      font-size:10px; font-family:var(--mono); color:var(--text-dim);
      overflow-wrap:anywhere;
    }
    /* Fixed columns so 49 chips line up instead of ragged-wrapping. */
    .chip-grid {
      display:grid; grid-template-columns:repeat(auto-fill, minmax(98px, 1fr)); gap:6px;
    }
    .chip-grid .chip-check { justify-content:space-between; }
    /* Apply/Clear stay reachable without scrolling back past 49 states. */
    .sel-actions {
      position:sticky; bottom:0; z-index:2;
      display:flex; gap:8px; flex-wrap:wrap; align-items:center;
      margin-top:14px; padding:12px 0 2px;
      background:var(--surface); border-top:1px solid var(--border);
    }

    .chip-group { display:flex; flex-wrap:wrap; gap:6px; }
    .chip-check {
      display:inline-flex; align-items:center; gap:7px; padding:5px 11px;
      border:1px solid var(--border); border-radius:999px; background:var(--surface2);
      font-size:11px; color:var(--text-mid); cursor:pointer; user-select:none;
      /* These are <label> elements, so without this they inherit the global
         label rule's uppercase + 0.14em and the HECM analyzer's Selection
         panel read "S A V E D  S N A P S H O T  O N L Y". The tracking also
         widened each chip enough to change how many fit per .chip-grid row. */
      text-transform:none; letter-spacing:normal; font-weight:500;
      transition:border-color var(--transition), color var(--transition),
                 background var(--transition);
    }
    .chip-check input { margin:0; accent-color:var(--primary); cursor:pointer; }
    .cc-n { text-transform:none; letter-spacing:normal; }
    .chip-check:hover { border-color:var(--border-bright); color:var(--text); }
    .chip-check:has(input:checked) {
      border-color:var(--primary); color:var(--text); background:rgba(110,163,255,.12);
    }
    .chip-check:focus-within { outline:2px solid var(--primary); outline-offset:2px; }
    .chip-check .cc-n { font-family:var(--mono); font-size:10px; color:var(--text-dim); }

    @media (pointer: coarse) {
      .chip-check { min-height:44px; padding:5px 14px; }
      .sel-summary { min-height:44px; }
      .sel-summary::after { width:44px; height:44px; flex:0 0 44px; font-size:20px; }
    }

    /* ── Document viewer ─────────────────────────────────────────────────
       A preview overlay for uploaded files, so reading a payoff letter does
       not mean downloading it, finding it, opening it and then deleting it
       from the Downloads folder. The frame's own CSP sandboxes the document;
       nothing here is a security boundary. */
    .doc-viewer {
      position: fixed; inset: 0; z-index: 1200;
      background: rgba(3, 7, 18, 0.72);
      backdrop-filter: blur(3px);
      display: flex; align-items: center; justify-content: center;
      padding: 28px;
    }
    .doc-viewer[hidden] { display: none; }
    .doc-viewer-card {
      background: var(--surface); border: 1px solid var(--border-bright);
      border-radius: 12px; box-shadow: 0 24px 64px rgba(0,0,0,0.45);
      width: min(1100px, 100%); height: min(88vh, 100%);
      display: flex; flex-direction: column; overflow: hidden;
    }
    .doc-viewer-bar {
      display: flex; align-items: center; gap: 10px;
      padding: 10px 12px; border-bottom: 1px solid var(--border);
      background: var(--surface2); flex-shrink: 0;
    }
    .doc-viewer-name {
      font-family: var(--mono); font-size: 12px; color: var(--text);
      font-weight: 600; overflow: hidden; text-overflow: ellipsis;
      white-space: nowrap;
    }
    .doc-viewer-meta {
      font-family: var(--mono); font-size: 11px; color: var(--text-dim);
      white-space: nowrap; margin-right: auto;
    }
    .doc-viewer-body { flex: 1; min-height: 0; background: var(--surface3); }
    .doc-viewer-body iframe { width: 100%; height: 100%; border: 0; display: block; }
    .doc-viewer-fallback {
      display: flex; align-items: center; justify-content: center;
      height: 100%; padding: 32px; text-align: center;
      font-family: var(--mono); font-size: 12px; color: var(--text-dim);
    }
    /* A phone has no room for a 28px gutter around a document. */
    @media (max-width: 700px) {
      .doc-viewer { padding: 0; }
      .doc-viewer-card { height: 100%; border-radius: 0; border: 0; }
      .doc-viewer-meta { display: none; }
    }

    /* ── Goals & projects ────────────────────────────────────────────────
       A goal is a number over a period, so its card is a bar and a figure
       rather than a row in a table; a project is a container, so its card
       leads with how much of it is done. */
    .pf-add { position: relative; }
    .pf-add > summary { list-style: none; cursor: pointer; }
    .pf-add > summary::-webkit-details-marker { display: none; }
    .pf-pop {
      position: absolute; right: 0; top: calc(100% + 8px); z-index: 40;
      width: min(620px, 88vw); padding: 16px;
      box-shadow: 0 18px 44px rgba(0,0,0,0.38);
    }
    .pf-grid {
      display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px;
    }
    @media (max-width: 700px) { .pf-grid { grid-template-columns: 1fr; }
                                .pf-pop { position: static; width: auto; } }

    .goal-card, .proj-card { margin-bottom: 12px; padding: 16px 18px; }
    .goal-quiet { opacity: 0.6; }
    .goal-head, .proj-head {
      display: flex; align-items: flex-start; gap: 14px; margin-bottom: 12px;
    }
    .goal-name, .proj-name {
      font-size: 14.5px; font-weight: 650; color: var(--text);
    }
    .proj-desc { font-size: 12.5px; color: var(--text-mid); margin-top: 2px; }
    .goal-sub {
      font-size: 11.5px; color: var(--text-dim); margin-top: 4px;
      font-family: var(--mono);
    }
    .goal-verdict {
      margin-left: auto; flex-shrink: 0; font-family: var(--mono);
      font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
      font-weight: 700; padding: 4px 9px; border-radius: 999px;
      border: 1px solid transparent;
    }
    .goal-verdict.goal-met   { color: var(--success); border-color: var(--success); }
    .goal-verdict.goal-over  { color: var(--gold);    border-color: var(--gold); }
    .goal-verdict.goal-on    { color: var(--blue);    border-color: var(--blue); }
    .goal-verdict.goal-behind{ color: var(--amber);   border-color: var(--amber); }
    .goal-verdict.goal-none  { color: var(--text-dim); border-color: var(--border); }

    .goal-bar {
      height: 8px; border-radius: 999px; background: var(--surface3);
      overflow: hidden;
    }
    .goal-fill { height: 100%; border-radius: 999px; transition: width 240ms; }
    .goal-fill.goal-met    { background: var(--success); }
    .goal-fill.goal-over   { background: var(--gold); }
    .goal-fill.goal-on     { background: var(--blue); }
    .goal-fill.goal-behind { background: var(--amber); }
    .goal-fill.goal-none   { background: var(--border-bright); }
    /* A project in progress is not a goal that has been met — 36% done in
       success-green reads as finished. It only turns green at 100%. */
    .goal-fill.proj-fill   { background: var(--blue); }

    .goal-figures {
      display: flex; align-items: baseline; gap: 8px; margin-top: 8px;
      font-family: var(--mono); font-size: 11.5px;
    }
    .goal-actual { font-size: 19px; font-weight: 700; color: var(--text); }

    .goal-record {
      display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
      margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);
      font-size: 11px; font-family: var(--mono);
    }
    .goal-history { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 10px; }
    .goal-chip {
      font-family: var(--mono); font-size: 10px; padding: 2px 7px;
      border-radius: 5px; background: var(--surface2); color: var(--text-dim);
      border: 1px solid var(--border);
    }
    .goal-chip-met { color: var(--success); border-color: var(--success); }
    .goal-edit { margin-top: 10px; }
    .goal-edit > summary { list-style: none; cursor: pointer; }
    .goal-edit > summary::-webkit-details-marker { display: none; }

    .proj-figures { margin-left: auto; text-align: right; flex-shrink: 0; }
    .proj-pct {
      font-family: var(--mono); font-size: 20px; font-weight: 700;
      color: var(--text);
    }
    .proj-links {
      display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
      margin-top: 12px;
    }
    .loose-row {
      display: flex; align-items: center; gap: 10px; padding: 8px 0;
      border-top: 1px solid var(--border);
    }
    .loose-row:first-of-type { border-top: 0; }
    .loose-name {
      font-family: var(--mono); font-size: 12px; color: var(--text);
      flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
      white-space: nowrap;
    }
